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

How to Pick Timezone from ISO 8601 format String into a Calendar instace

作为输入,我有一个字符串,它是 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);

iso8601日期为“2017-04-04T09:00:00-08:00”

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

calendar.getTimeZone().getDisplayName()

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

tl;博士

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

详情

The last part of String which is "-08:00" denotes TimeZone Offset.

不要将偏移量与时区混淆。

-08:00表示一个offset-from-UTC, not a time zone. A time zone is a history of various offsets used in the past, present, and future by the people of a particular region. A time zone is named with a continent, slash, and region such as America/Los_Angeles or Pacific/Auckland or Asia/Kolkata

您使用的是麻烦的旧日期时间 类,现在已被 java.time 类 取代。对于 Android,请参阅 ThreeTen-Backport and ThreeTenABP 项目。

您的输入仅表示偏移量,不表示区域。所以我们解析为 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 framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

Joda-Time project, now in maintenance mode, advises migration to the java.time 类.

要了解更多信息,请参阅 Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310

在哪里获取java.time类?

当您创建 Calendar 时,它采用 JVM 的默认时区。当您将 String 解析为 Date 时,它只会设置一个值:自纪元 (1970-01-01T00:00Z) 以来的毫秒数。一个Datedoesn'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 的默认设置)。因此,"better" 方法是从输入中剥离偏移量并单独处理:

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。由于 already said, TimeZone class 将偏移量视为时区,这是 workaround/bad 设计选择)。


Java新Date/TimeAPI

旧的 classes(DateCalendarSimpleDateFormat)有 lots of problems and design issues,它们正在被新的 APIs.

在Android中你可以使用ThreeTen Backport, a great backport for Java 8's new date/time classes. You'll also need the ThreeTenABP to make it work (more on how to use it ).

已经告诉你 OffsetDateTime。但是要转换为 Calendar,您可以使用 org.threeten.bp.ZonedDateTime 并使用 org.threeten.bp.DateTimeUtils class:

进行转换
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,或 January 1st 1970 年午夜 UTC)。

这个值在世界各地都是一样的。示例:在我写这篇文章的那一刻,当前时间的毫秒值是 1504632865935。对于世界上与我同时获取当前时间的任何人来说,这个数字都是相同的,无论他们使用的是哪个时区。

不同的是本地日期和时间对应这个毫秒值。在UTC中,对应2017-09-05T17:34:25.935Z,在纽约,日期相同(September 5th 2017)但时间不同(13:34),而在东京是 2017 年 9 月 6th 02:34 AM。

虽然Date对象是一样的(因为它的毫秒值对每个人来说都是1504632865935),但是对应的日期和时间根据使用的时区。

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

我想从 Hugo 的回答中分享一个关键的理解,我的进一步搜索如下。如有错误请指正:

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

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