ZonedDateTime 和 LocalDateTime 与 100 年前不同

ZonedDateTime & LocalDateTime are different for 100 years ago

我看到的行为非常奇怪 - 有时 LocalDateTime 等于 ZonedDateTime,其他时候相差 1 或 2 小时,有时相差 30 分钟。所有这些奇怪的差异都取决于我减去的年份。有人可以解释发生了什么吗?尝试了 jdk1.8.0_65jdk1.8.0_91MacOS 10.11.5。我使用 UTC:

ZoneOffset offset = ZoneOffset.UTC;

这里有一些实验。对于 1919 值可能会相差纳秒或毫秒,这是预期的:

assertEquals(                     
  LocalDateTime.now(offset).minusYears(85).toInstant(offset),                   
  ZonedDateTime.now().minusYears(85).withZoneSameInstant(offset).toInstant());

1919 年相差 1 小时:

assertEquals(                 
  LocalDateTime.now(offset).minusYears(86).toInstant(offset),                    
  ZonedDateTime.now().minusYears(86).withZoneSameInstant(offset).toInstant());

Expected :<1930-05-28T20:19:10.383Z> 
Actual   :<1930-05-28T21:19:10.383Z>

对于 1920 时差 2 小时:

assertEquals(                    
  LocalDateTime.now(offset).minusYears(95).toInstant(offset),                      
  ZonedDateTime.now().minusYears(95).withZoneSameInstant(offset).toInstant());

Expected :<1921-05-28T20:21:45.094Z> 
Actual   :<1921-05-28T18:21:45.094Z>

对于 1921 再次毫秒或纳秒差异

assertEquals(                      
  LocalDateTime.now(offset).minusYears(96).toInstant(offset),                    
  ZonedDateTime.now().minusYears(96).withZoneSameInstant(offset).toInstant());

最奇怪的是 - 1930 年 30 分钟的差异

assertEquals(                      
  LocalDateTime.now(offset).minusYears(97).toInstant(offset),                  
  ZonedDateTime.now().minusYears(97).withZoneSameInstant(offset).toInstant());

Expected :<1919-05-28T20:24:27.345Z> 
Actual   :<1919-05-28T19:53:08.346Z>

更新

正如@Tunaki 指出的那样,我必须指定 ZonedDateTime:

的偏移量
assertEquals(                   
  LocalDateTime.now(offset).minusYears(95).toInstant(offset),                    
  ZonedDateTime.now(offset).minusYears(95).withZoneSameInstant(offset).toInstant());

因为没有人回答这个...

创建 ZonedDateTime 时也必须发送偏移量。否则它将使用 Clock.systemDefaultZone() 并且时区会有所不同。

ZonedDateTime.now(offset).minusYears(95).withZoneSameInstant(offset).toInstant()

问题是它不知道时区:LocalDateTime.now(offset).minusYears(97).toInstant(offset)。目前只有 offset。但这知道时区:ZonedDateTime.now().minusYears(97).toInstant()

  • ZoneId包含地点信息和那个地方的时差。它知道 N 年前在那个特定时区的偏移量是 2 小时,而不是现在的 3 小时。
  • ZoneOffset 仅跟踪 hours/minutes 班次。它不知道特定国家的时间变化历史。它只是 "adds hours".

建议的(有点不正确)解决方案是让 ZonedDateTime 忘记区域并使用偏移量代替:ZonedDateTime.now(offset).minusYears(97)。现在这将与具有相同偏移量的 LocalDateTime 一致 - 两者将显示相同的 不正确 信息。但他们会同意,因为 "just add hours" 而不是了解那个地方在那个历史点的时差:

ZoneOffset offset = ZoneId.of("Europe/Moscow").getRules().getOffset(Instant.now());
assertEquals(
    LocalDateTime.now(offset).minusYears(97).toInstant(offset),           
    ZonedDateTime.now(offset).minusYears(97).toInstant());

或者我们可以设置 LocalDateTime 以显示那个时间那个地方的正确值:

ZonedDateTime zoned = ZonedDateTime.now(ZoneId.of("Europe/Moscow")).minusYears(97);
ZoneOffset offset = zoned.getOffset();//agrees with history
assertEquals(
        LocalDateTime.now().minusYears(97).toInstant(offset),
        zoned.toInstant());

PS:这一切都表明 ZonedDateTime 在不同情况下的工作方式不同 - 有时它知道时区,其他时候它只是 "adds hours" 就像你对 LocalDateTime 所做的那样手动设置偏移量。对我来说这是一个奇怪的实现。 JodaTime 可能仍然是最好的 Java 实现。至少不用学几天就能看懂。