如何从ISO 8601格式字符串中选择时区到日历实例中

作为输入,我有一个字符串,它是ISO 8601中表示日期的字符串。 例如:

“2017-04-04T09:00:00-08:00”

String的最后一部分,即“-08:00”表示TimeZone Offset。 我将此字符串转换为Calendar实例,如下所示:

 Calendar calendar = GregorianCalendar.getInstance(); Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US).parse(iso8601Date); calendar.setTime(date); 

iso8601Date是“2017-04-04T09:00:00-08:00”

但这并没有选择时区,如果我从Calendar实例获取时区,它会提供当前设置的笔记本电脑实例,并且不会从ISO 8601字符串中获取时间戳。 我通过日历实例检查时区为:

 calendar.getTimeZone().getDisplayName() 

有人可以在Calendar实例中显示如何选择时区吗?

创建Calendar ,它采用JVM的默认时区。 当你将一个String解析为一个Date ,它只设置一个值:自纪元以来的毫秒数( 1970-01-01T00:00Z )。 Date 没有任何时区信息 ,只有这个毫秒值。 因此,您需要在日历中设置时区。

在格式化程序中,您将Z视为文字,因为它在引号内( 'Z' )。 这会忽略偏移量并在JVM默认时区中获取日期(如果相应的偏移量不是-08:00,则会有不同的值)。

在JDK> = 7中,您可以使用X模式来解析偏移量:

 Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX", Locale.US).parse(iso8601Date); 

但这并没有在日历中设置时区(它仍将使用JVM的默认值)。 因此,“更好”的方法是从输入中去除偏移量并单独处理它:

 Calendar calendar = GregorianCalendar.getInstance(); String iso8601Date = "2017-04-04T09:00:00-08:00"; // get the offset (-08:00) String offset = iso8601Date.substring(19); TimeZone tz = TimeZone.getTimeZone("GMT" + offset); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US); // set the offset in the formatter sdf.setTimeZone(tz); // parse just date and time (without the offset) Date date = sdf.parse(iso8601Date.substring(0, 19)); // set the offset in the calendar calendar.setTimeZone(tz); calendar.setTime(date); 

这样,日历将设置偏移-08:00 。 正如@ BasilBourque的回答已经说过的那样, -08:00是一个偏移,而不是一个时区 ( TimeZone类就像处理时区一样处理偏移,这是一种解决方法/糟糕的设计选择)。


Java新的日期/时间API

旧类( DateCalendarSimpleDateFormat )存在许多问题和设计问题 ,并且它们正被新的API取代。

在Android中,您可以使用ThreeTen Backport ,这是Java 8新日期/时间类的绝佳后端 。 你还需要ThreeTenABP来使它工作(更多关于如何在这里使用它)。

@ BasilBourque的答案已经告诉你关于OffsetDateTime 。 但要转换为Calendar ,您可以使用org.threeten.bp.ZonedDateTime并使用org.threeten.bp.DateTimeUtils类进行转换:

 String iso8601Date = "2017-04-04T09:00:00-08:00"; ZonedDateTime zdt = ZonedDateTime.parse(iso8601Date); Calendar cal = DateTimeUtils.toGregorianCalendar(zdt); 

日历将已使用-08:00偏移设置。


如果你想从偏移量中获取时区,我恐怕不是那么简单。 多个时区可以使用相同的偏移量 ,因此您无法确定使用哪个时区(您可以做的最好的事情是获取可能的候选者列表)。


java.util.Date

关于java.util.Date的更详细说明。 这个链接解释了很多,所以我真的建议你阅读它。

如上所述, Date没有时区信息。 它只保留自纪元以来的毫秒数(即1970-01-01T00:00Z ,或1970年1月1 在UTC的午夜 )。

这个价值在世界各地都是一样的。 示例:在我写这篇文章时,当前时间的millis值为1504632865935 。 对于世界上任何人来说,这个数字是相同的,无论他们使用的是什么时区,我都能在当时获得当前时间。

不同的是与此millis值对应的本地日期和时间 。 在UTC中,它对应于2017-09-05T17:34:25.935Z ,在纽约,日期是相同的(2017年9月5日),但时间不同(13:34),在东京是9月6 2017年凌晨02:34。

虽然Date对象是相同的(因为每个人的millis值为1504632865935 ),但相应的日期和时间会根据使用的时区而变化。

人们倾向于认为Date有一个时区,因为在打印它时(使用System.out.println或通过loggging)或在调试器中检查时,它隐含使用toString()方法,这会将日期转换为JVM的默认值时区(还会打印区域名称)。 这给人的印象是Date有一个格式和时区设置,但它没有。

TL;博士

 OffsetDateTime.parse( "2017-04-04T09:00:00-08:00" ) 

细节

String的最后一部分是“-08:00”表示TimeZone Offset。

不要将偏移与时区混淆。

-08:00表示与UTC的偏移 ,而不是时区 。 时区是特定地区的人们在过去,现在和将来使用的各种偏移的历史。 时区以大陆,斜线和区域命名,例如America/Los_AngelesPacific/AucklandAsia/Kolkata

您正在使用现在由java.time类取代的麻烦的旧日期时间类。 对于Android,请参阅ThreeTen- BackportThreeTenABP项目。

您的输入仅指示偏移但不指示区域。 所以我们解析为OffsetDateTime

 OffsetDateTime odt = OffsetDateTime.parse( "2017-04-04T09:00:00-08:00" ) ; 

如果您完全确定预期的时区,请进行分配。

 ZoneId z = ZoneId.of( "America/Los_Angeles" ) ; ZonedDateTime zdt = odt.atZoneSameInstant() ; 

关于java.time

java.time框架内置于Java 8及更高版本中。 这些类取代了麻烦的旧遗留日期时间类,如java.util.DateCalendarSimpleDateFormat

现在处于维护模式的Joda-Time项目建议迁移到java.time类。

要了解更多信息,请参阅Oracle教程 。 并搜索Stack Overflow以获取许多示例和解释。 规范是JSR 310 。

从哪里获取java.time类?

  • Java SE 8Java SE 9及更高版本
    • 内置。
    • 带有捆绑实现的标准Java API的一部分。
    • Java 9增加了一些小function和修复。
  • Java SE 6Java SE 7
    • 许多java.timefunction都被反向移植到ThreeTen-Backport中的 Java 6和7。
  • Android的
    • ThreeTenABP项目特别适用于Android的ThreeTen-Backport (如上所述)。
    • 请参见如何使用ThreeTenABP ….

我希望从雨果的答案中分享一个关键的理解,我的进一步搜索是如下。 如果我错了,请纠正我:

日期并不关心时区。 它表示自纪元以来经过的毫秒数。

关于从提供的ISO 8061格式中找到时区,Date类不能说明这一点,我们必须使用@Hugo和@Basil Bourque指定的一些替代方法。