tl;dr
java.util.Date 和 Zoneddatetime 之间有什么区别?
-
Date
代表 UTC 中的一个时刻,而ZonedDateTime
代表特定时区的某个时刻。
-
Date
是一个糟糕的类,充满了设计缺陷,永远不应该使用,而ZonedDateTime
是一个现代班级java.time您会发现非常有用的软件包。
Java 带有两个截然不同的框架来处理日期时间工作:一组非常尴尬且失败的遗留类,以及一组在java.time包裹。
传统➙现代:
-
java.util.Date
was replaced by java.time.Instant
-
java.util.GregorianCalendar
was replaced by java.time.ZonedDateTime
java.util.Date
The Date
类代表 UTC 中的一个时刻。即,日期、时间以及 UTC 上下文。
在内部,它是自 UTC 1970 年第一个时刻的纪元参考日期 1970-01-01T00:00:00Z 以来的毫秒数。
让事情变得复杂:
- 创建时有一个时区捕获,存储在内部深处,没有 getter 或 setter。因此,在大多数情况下,我们可以忽略这个区域,尽管它确实适用于此类的实现
equals
.
- 打电话时
toString
这个类有very在生成文本来表示该对象的值时,动态应用 JVM 当前时区的行为令人困惑。虽然本意是好的,但这种反特性给试图学习日期时间处理的 Java 程序员带来了难以估量的痛苦。
使困惑?是的,这个类令人困惑,是糟糕的设计决策的一团糟。加上后来的补充java.util.Calendar
& GregorianCalendar
.
所有这些与最早版本的 Java 捆绑在一起的麻烦的日期时间类现在都完全被java.time类。
尤其,java.util.Date
被替换为java.time.Instant
。两者都代表 UTC 中的一个时刻,从 1970 UTC 纪元开始计数。但Instant
具有更精细的分辨率,纳秒而不是毫秒.
您可以在遗留类之间来回转换Date
和现代阶级Instant
通过调用添加到旧类中的新方法。通常,您会避免使用Date
。但是当与尚未更新的旧代码交互时java.time,您可能需要转换。
java.time.ZonedDateTime
现代阶级ZonedDateTime
代表某个地区(时区)人们使用的挂钟时间中看到的时刻。
So Instant
and ZonedDateTime
相似之处在于它们都代表一个时刻,即该时间线上的特定点。不同之处在于ZonedDateTime
了解时区的规则。所以一个ZonedDateTime
知道如何解释夏令时 (DST) 等异常现象或政客要求的其他计时变更。
您可以将其视为:
ZonedDateTime
= ( Instant
+ ZoneId
)
我们可以通过应用时区(ZoneId
) to a Instant
object.
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).
看到这个代码在 IdeOne.com 上实时运行。请注意不同的日期和不同的时间,但同时发生的时刻相同。
instant.toString(): 2019-02-27T19:32:43.366Z
zdt.toString(): 2019-02-28T04:32:43.366+09:00[亚洲/东京]
关键概念:Instant
and ZonedDateTime
两者都代表同一时刻,时间轴上同一同时点。它们的挂钟时间不同。例如,如果日本的某人打电话给冰岛的某人(他们的时钟始终使用 UTC),并且他们都抬头看着挂在各自墙上的时钟,他们会看到不同的时间,并且可能甚至月历上的不同日期。同一时刻,不同的挂钟时间。
至于遗留类,相当于ZonedDateTime
is GregorianCalendar,具体实现java.util.Calendar。确实,老班GregorianCalendar
获得新的转换方法to/from ZonedDateTime
.
ZonedDateTime zdt = myGregorianCalendar.toZonedDateTime(); // Convert from legacy to modern class.
…and…
GregorianCalendar gc = GregorianCalendar.from( zdt ) ; // Convert from modern to legacy class.
结论
So a Date
相当于一个Instant
,都是 UTC 中的时刻。但一个ZonedDateTime
与两者的不同之处在于,时区通过应用该地区人们的挂钟时间调整的镜头来调整对时刻的感知。
Tips:
-
切勿使用
Date
.当递给一个Date
,立即转换为Instant
。然后继续您的业务逻辑。
-
您的大部分工作都在 UTC 中完成。跟踪时刻、调试、记录、交换日期时间值以及保存到数据库通常应在 UTC 中完成。当程序员工作时,学会忘记自己狭隘的时区。将办公桌上的第二个时钟设置为 UTC。
Database
该问题提到了数据库工作。这是一个快速总结。搜索 Stack Overflow 以获取更多详细信息,因为这已经被处理过很多次了。
从 JDBC 4.2 开始,我们可以直接交换java.time与数据库的对象。使用PreparedStatement::setObject
and ResultSet::getObject
。无需再次接触可怕的事物java.sql.*
类如java.sql.Timestamp
.
You may能够交换一个Instant
但 JDBC 规范并不要求这样做。相反,规范要求OffsetDateTime
.
恢复。
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;
和存储。
myPreparedStatement.setObject( … , odt ) ;
如果你有一个Instant
手中,转换为OffsetDateTime使用常数ZoneOffset.UTC.
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();
大多数数据库为 SQL 标准类型的列存储 UTC 时刻TIMESTAMP WITH TIMESTAMP
。当提交您的OffsetDateTime
对于数据库,您的 JDBC 驱动程序可能会调整偏移量OffsetDateTime
到零小时-分钟-秒(UTC 本身)。但我喜欢明确地这样做。它使调试变得更加容易,并向读者展示了我对存储在 UTC 中的时刻的理解。
OffsetDateTime odt = zdt.toOffsetDateTime().withOffsetSameInstant( ZoneOffset.UTC ) ;