为什么 ZonedDateTime 和 Calendar 对 2050 年的时间不一致?

Ale*_*der 9 java datetime calendar java-8 zoneddatetime

考虑以下代码:

ZoneId zoneId = ZoneId.of("America/Los_Angeles");
long currMillis = 2530778400000L;
Instant curr = Instant.ofEpochMilli(currMillis);
LocalDateTime dt = LocalDateTime.ofInstant(curr, zoneId); //the local one just for completeness
ZonedDateTime zdt = ZonedDateTime.ofInstant(curr, zoneId);
Calendar calendar = GregorianCalendar.from(zdt);

System.out.println(String.format("%-30s %s", "java-8 LocalDateTime hour:", dt.toLocalTime().getHour()));
System.out.println(String.format("%-30s %s", "java-8 ZonedDateTime hour:", zdt.toLocalTime().getHour()));
System.out.println(String.format("%-30s %s", "Calendar hour:", calendar.get(Calendar.HOUR_OF_DAY)));
Run Code Online (Sandbox Code Playgroud)

打印:

java-8 LocalDateTime hour:     3
java-8 ZonedDateTime hour:     3
Calendar hour:                 2
Run Code Online (Sandbox Code Playgroud)

似乎在这个小时左右,日历从 2 点跳到 4 点(如果对应于 DST 更改,一般来说不一定是问题)。

我正在使用 AdoptOpenJDK 1.8.0_242,但我也检查了 HotSpot 1.8.0_181 - 同样的问题。

为什么日历报告的时间与 ZonedDateTime 不同?
这种不匹配是一个已知问题吗?
在这种情况下,我应该更信任谁 - ZonedDateTime 还是 Calendar?

Swe*_*per 8

假设规则(转换为 DST 发生在 3 月 8 日或之后的第一个星期日 02:00)在 2050 年没有改变,则该时刻是发生间隙转换的时刻(3 月 13 日),时钟从 01 跳:59 到 03:00,所以 02:00 实际上并不存在。Calendar这里是完全错误的。

您可以Calendar通过检查每个时区类对相关时刻的说明来进一步了解错误有多大。ZonedDateTime使用ZoneId,而Calendar使用TimeZone. 我使用以下代码将各种方法的输出ZoneId与对应方法的输出进行了比较:TimeZone

ZoneId zoneId = ZoneId.of("America/Los_Angeles");
long currMillis = 2530778400000L;
Instant curr = Instant.ofEpochMilli(currMillis);
TimeZone tz = TimeZone.getTimeZone(zoneId);

// what's the actual offset at that instant?
System.out.println(zoneId.getRules().getOffset(curr).getTotalSeconds());
System.out.println(tz.getOffset(currMillis) / 1000);

// is DST observed at that instant?
System.out.println(zoneId.getRules().isDaylightSavings(curr));
System.out.println(tz.inDaylightTime(new Date(currMillis)));

// what's the standard offset at that instant?      
System.out.println(zoneId.getRules().getStandardOffset(curr).getTotalSeconds());
System.out.println(tz.getRawOffset() / 1000);

// how many seconds does DST add to the standard offset at that instant?
System.out.println(zoneId.getRules().getDaylightSavings(curr).getSeconds());
Calendar calendar = GregorianCalendar.from(ZonedDateTime.ofInstant(curr, zoneId));
System.out.println(calendar.get(Calendar.DST_OFFSET) / 1000);
Run Code Online (Sandbox Code Playgroud)

结果如下:

-25200
-28800
true
true
-28800
-28800
3600
0
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,他们都认为遵守了 DST,但TimeZone认为 DST 在标准偏移量上添加了 0 秒,这使得它认为实际偏移量仍然是 -8 小时。

但谁知道30年后会发生什么?希望大家都能摆脱夏令时:)

  • 很好的答案。但关于你关于取消夏令时的评论:只要有政客,时区就会发生变化。DST 只是区域更改的原因之一。历史表明政治家改变时区的其他政治、军事、外交和文化原因。我强烈建议所有程序员学会在代码中处理时区,并进行防御性编程,以始终预期时区变化,无论 DST 目前在其管辖范围内是否存在问题。 (3认同)
  • @OleV.V。我刚刚查看了“northamerica”的“tzdata2019c”文件。我没有看到“2038”的出现。您发现了一个恰好从 2038 年开始的不同情况,这让我想知道这是否可能是与 32 位[2038 年问题](https://en.wikipedia.org/wiki/Year_2038_problem) 相关的编程问题。 (2认同)
  • @OleV.V。那么,还有一个避免遗留日期时间类的*另一个*理由吗? (2认同)