tl;博士
java.util.Date 和 Zoneddatetime 有什么区别?
-
Date 代表 UTC 的时刻,而 ZonedDateTime 代表特定时区的时刻。
-
Date 是一个糟糕的类,充满了设计缺陷,不应该被使用,而ZonedDateTime 是一个来自 java.time 包的现代类,你会发现它非常有用。
Java 带有两个非常不同的框架来处理日期时间工作:一组非常笨拙且失败的遗留类,以及一组现代行业领先的类,可在 java.time 包中找到。
传统➙现代:
-
java.util.Date 被 java.time.Instant 取代
-
java.util.GregorianCalendar 被 java.time.ZonedDateTime 取代
java.util.Date
Date 类代表 UTC 中的一个时刻。也就是说,日期、时间以及 UTC 的上下文。
在内部,它是自 UTC 1970 第一刻的纪元参考日期 1970-01-01T00:00:00Z 以来的毫秒数。
把事情复杂化:
- 创建时有一个时区捕获,存储在内部深处,没有 getter 或 setter。因此,在大多数情况下,我们可以忽略这个区域,尽管它确实适用于诸如此类的
equals 的实现。
- 当调用
toString 时,该类具有非常令人困惑的行为,即在生成表示该对象值的文本时动态应用 JVM 的当前时区。虽然出于善意,但这种反特性给试图学习日期时间处理的 Java 程序员带来了无法估量的痛苦。
困惑?是的,这个类令人困惑,糟糕的设计决策一团糟。后来添加了java.util.Calendar 和GregorianCalendar。
所有这些与最早的 Java 版本捆绑在一起的麻烦的日期时间类现在完全被 java.time 类所取代。
特别是,java.util.Date 被 java.time.Instant 替换。两者都代表从 1970 UTC 时代开始的 UTC 时刻。但是Instant 的分辨率更高,nanoseconds 而不是milliseconds。
您可以通过调用添加到旧类的新方法在旧类Date 和现代类Instant 之间来回转换。通常,您将避免使用Date。但是当与尚未更新到 java.time 的旧代码交互时,您可能需要进行转换。
java.time.ZonedDateTime
现代类ZonedDateTime 代表某个地区(时区)人们使用的挂钟时间中看到的时刻。
所以Instant 和ZonedDateTime 的相似之处在于它们都代表一个时刻,即时间线上的一个特定点。不同之处在于ZonedDateTime 知道时区的规则。所以ZonedDateTime 知道如何解释诸如夏令时 (DST) 等异常情况或政治家要求的其他计时更改。
你可以这样想:
ZonedDateTime = (Instant + ZoneId)
通过将时区 (ZoneId) 应用于 Instant 对象,我们可以轻松地从 UTC 调整到某个时区。
Instant instant = Instant.now() ; // Capture the current moment as seen in UTC.
ZoneId z = ZoneId.of( "Asia/Tokyo" ) ;
ZonedDateTime zdt = instant.atZone( z ) ; // Apply a time zone to see the same moment through the wall-clock time in use by the people of a particular region (a time zone).
看到这个code run live at IdeOne.com。请注意不同的日期和不同的时间,但同时存在相同的时刻。
instant.toString(): 2019-02-27T19:32:43.366Z
zdt.toString(): 2019-02-28T04:32:43.366+09:00[亚洲/东京]
关键概念:Instant 和 ZonedDateTime 都代表同一时刻,时间轴上的同一同时点。它们的挂钟时间不同。例如,如果日本的某人打电话给冰岛的某人(他们的时钟一直使用 UTC),并且他们都抬头看挂在各自墙上的时钟,他们会看到不同的时间,并且可能甚至月历上的不同日期。同一时刻,不同的挂钟时间。
对于遗留类,ZonedDateTime 的等价物是GregorianCalendar,是java.util.Calendar 的具体实现。确实,旧类GregorianCalendar 获得了新的转换方法to/fromZonedDateTime。
ZonedDateTime zdt = myGregorianCalendar.toZonedDateTime(); // Convert from legacy to modern class.
……和……
GregorianCalendar gc = GregorianCalendar.from( zdt ) ; // Convert from modern to legacy class.
结论
所以Date 等同于Instant,两者都是UTC 中的时刻。但ZonedDateTime 与两者的不同之处在于,时区通过应用地区人民挂钟时间调整的镜头调整了对时刻的感知。
提示:
-
永远不要使用
Date。 当收到Date 时,立即转换为Instant。然后继续您的业务逻辑。
-
您的大部分工作都使用 UTC。 跟踪时刻、调试、记录、交换日期时间值以及保存到数据库通常应该使用 UTC。在作为程序员工作时,学会忘记自己狭隘的时区。将桌面上的第二个时钟设置为 UTC。
数据库
问题提到了数据库工作。这是一个快速的总结。搜索 Stack Overflow 以获取更多详细信息,因为这已经处理了很多次了。
从 JDBC 4.2 开始,我们可以直接与数据库交换 java.time 对象。使用PreparedStatement::setObject 和ResultSet::getObject。无需再接触糟糕的java.sql.* 类,例如java.sql.Timestamp。
您可能能够交换Instant,但 JDBC 规范并不要求这样做。相反,规范需要OffsetDateTime。
检索。
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;
还有存储。
myPreparedStatement.setObject( … , odt ) ;
如果您有一个Instant,请使用常量ZoneOffset.UTC 转换为OffsetDateTime。
OffsetDateTime odt = Instant.atOffset( ZoneOffset.UTC ) ;
要通过某个地区的挂钟时间查看那一刻,请申请ZoneId。
ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = odt.atZoneSameInstant( z ) ;
要将ZonedDateTime 存储到数据库中,请转换为OffsetDateTime。这会去除时区信息(过去、现在和未来对该地区人民使用的偏移量所做的更改的历史,由他们的政治家决定),留下日期、时间和偏移量-from-UTC(小时-分钟-秒数)。
OffsetDateTime odt = zdt.toOffsetDateTime();
大多数数据库在 UTC 中存储 SQL 标准类型 TIMESTAMP WITH TIMESTAMP 的列的时刻。当您将OffsetDateTime 提交到数据库时,您的JDBC 驱动程序可能会将OffsetDateTime 中的偏移量调整为零时分秒(到UTC 本身)。但我喜欢明确地这样做。它使调试更容易,并向读者展示了我对存储在 UTC 中的时刻的理解。
OffsetDateTime odt = zdt.toOffsetDateTime().withOffsetSameInstant( ZoneOffset.UTC ) ;