ZonedDateTime 和 OffsetDateTime 有什么区别?

我已经阅读了文档,但是我仍然不知道什么时候应该使用其中之一:

根据文档 OffsetDateTime应该使用时写日期数据库,但我不明白为什么。

63466 次浏览

问: java 8 ZonedDateTime 和 OffsetDateTime 有什么区别?

Javadocs 说:

OffsetDateTimeZonedDateTimeInstant都在时间线上存储精确到纳秒的瞬间。Instant是最简单的,只表示瞬间。OffsetDateTime增加了与 UTC/Greenwich 的偏移量,这允许获取本地日期时间。ZonedDateTime增加了完整的时区规则。”

资料来源: https://docs.oracle.com/javase/8/docs/api/java/time/OffsetDateTime.html

因此,OffsetDateTimeZonedDateTime的区别在于后者包括了涵盖夏时制调整和各种其他异常的规则。

简单地说:

时区 = (偏移-从-协调世界时 + 异常规则)


问: 根据文档,在向数据库写日期时应该使用 OffsetDateTime,但我不明白为什么。

具有当地时间偏移量的日期始终表示相同的时间实例,因此具有稳定的顺序。相比之下,具有完整时区信息的日期的含义在对各个时区的规则进行调整时是不稳定的。(这些确实会发生,例如将来的日期-时间值。)因此,如果你存储然后检索一个 ZonedDateTime,那么实现就有一个问题:

  • 它可以存储计算出的偏移量... 然后检索到的对象可能会有与区域 ID 的当前规则不一致的偏移量。

  • 它可以丢弃计算出来的偏移量,然后检索到的物体代表绝对/通用时间线上的一个不同点,而不是存储的那个。

如果使用 Java 对象序列化,Java9实现采用第一种方法。这可以说是处理这个问题的“更正确”的方法,但似乎没有文档记录。(JDBC 驱动程序和 ORM 绑定可能正在做出类似的决定,并且有望做对。)

但是,如果您正在编写一个手动存储日期/时间值的应用程序,或者依赖于 java.sql.DateTime,那么处理区域 ID 的复杂性... 可能是需要避免的。所以才给你这个建议。

请注意,其含义/顺序随时间不稳定的日期 也许吧会对应用程序造成问题。而且,由于区域规则的变化是一个边缘案例,这些问题很容易在意想不到的时候出现。


建议的第二个(可能的)原因是 ZonedDateTime的构造在某些点上是模糊的。例如,在“将时钟拨回”的时间段中,将本地时间和区域 ID 结合起来可以得到两个不同的偏移量。ZonedDateTime会一直选择其中一个... 但这并不总是正确的选择。

现在,对于以这种方式构造 ZonedDateTime值的任何应用程序来说,这都可能是一个问题。但是,从构建企业应用程序的角度来看,如果 ZonedDateTime值是持久的(可能是不正确的)并在以后使用,那么问题就更大了。

接受的答案给出了非常完整的解释,也许下面的代码示例可以为您提供简短而清晰的图片:

Instant instant = Instant.now();
Clock clock = Clock.fixed(instant, ZoneId.of("America/New_York"));
OffsetDateTime offsetDateTime = OffsetDateTime.now(clock);
ZonedDateTime zonedDateTime = ZonedDateTime.now(clock);


System.out.println(offsetDateTime); // 2019-01-03T19:10:16.806-05:00
System.out.println(zonedDateTime);  // 2019-01-03T19:10:16.806-05:00[America/New_York]
System.out.println();


OffsetDateTime offsetPlusSixMonths = offsetDateTime.plusMonths(6);
ZonedDateTime zonedDateTimePlusSixMonths = zonedDateTime.plusMonths(6);


System.out.println(offsetPlusSixMonths); // 2019-07-03T19:10:16.806-05:00
System.out.println(zonedDateTimePlusSixMonths); // 2019-07-03T19:10:16.806-04:00[America/New_York]
System.out.println(zonedDateTimePlusSixMonths.toEpochSecond() - offsetPlusSixMonths.toEpochSecond()); // -3600


System.out.println();
System.out.println(zonedDateTimePlusSixMonths.toLocalDateTime()); // 2019-07-03T19:10:16.806
System.out.println(offsetPlusSixMonths.toLocalDateTime()); // 2019-07-03T19:10:16.806

简而言之,只有当你想在夏令时节约时间时才使用 ZonedDateTime,通常会有一个小时的差异,正如你可以看到上面的例子,ZonedDateTime的偏移量从 -5:00变为 -04:00,在大多数情况下,你的业务逻辑可能会以 bug 结束。

(代码复制从 https://www.youtube.com/watch?v=nEQhx9hGutQ)