4

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

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

的最后一部分String,即“-08:00”表示时区偏移。我将此字符串转换为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实例中选择时区吗?

4

3 回答 3

4
于 2017-09-05T15:58:09.823 回答
2

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

在您的格式化程序中,您将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. 但是要转换为 a Calendar,您可以使用 aorg.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偏移量。


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


java.util.日期

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

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

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

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

虽然Date对象是相同的(因为它的毫秒值1504632865935适用于所有人),但对应的日期和时间会根据使用的时区而变化。

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

于 2017-09-05T16:12:13.160 回答
1

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

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

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

于 2017-09-05T17:19:27.937 回答