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

时间:2017-09-05 14:15:44

标签: java android date datetime-parsing java.util.calendar

作为输入,我有一个字符串,它是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实例中显示如何选择时区吗?

3 个答案:

答案 0 :(得分:3)

TL;博士

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

详细

  

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

不要将偏移与时区混淆。

-08:00代表offset-from-UTC,而不是time zone。时区是特定地区的人们在过去,现在和将来使用的各种偏移的历史。时区以大陆,斜杠和区域命名,例如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及更高版本中。这些类取代了麻烦的旧legacy日期时间类,例如java.util.DateCalendar和& SimpleDateFormat

现在位于Joda-Timemaintenance mode项目建议迁移到java.time类。

要了解详情,请参阅Oracle Tutorial。并搜索Stack Overflow以获取许多示例和解释。规范是JSR 310

从哪里获取java.time类?

答案 1 :(得分:2)

创建Calendar时,它会占用JVM的默认时区。当您将String解析为Date时,它只会设置一个值:自纪元(1970-01-01T00:00Z)以来的毫秒数。一个Date doesn't have any timezone information,只有这个毫秒值。所以你需要在日历中设置时区。

在格式化程序中,您将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's answer已经说过的那样,-08:00 is an offset, not a timezoneTimeZone类将偏移视为时区,这是一种解决方法/糟糕的设计选择。)

Java新日期/时间API

旧类(DateCalendarSimpleDateFormat)有lots of problemsdesign issues,并且它们被新API取代

在Android中,您可以使用ThreeTen Backport,这是Java 8新日期/时间类的绝佳后端。您还需要ThreeTenABP才能使其正常运行(更多关于如何使用它here)。

@BasilBourque's answer已经告诉您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偏移。

如果你想从偏移中获取时区,我担心它并不那么简单。不止一个时区can use the same offset,因此您无法确定使用哪个时区(您可以做的最好的事情就是获取可能的候选人名单)。

java.util.Date

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

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

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

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

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

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

答案 2 :(得分:1)

我想从雨果的答案中分享一个关键的理解,我的进一步搜索正在跟随。如果我错了,请纠正我:

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

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