【问题标题】:Whats the difference between java.util.Date and Zoneddatetime?java.util.Date 和 Zoneddatetime 有什么区别?
【发布时间】:2019-07-21 12:42:41
【问题描述】:

虽然使用 util.date 并从浏览器提供日期和服务时间,然后保存到数据库并将其取回,但它会提供不同的日期时间,而不是直接从服务中设置 zoneddatetime。

任何帮助将不胜感激..

【问题讨论】:

  • @Michael 不是真正的重复。 LocalDate 对象表示仅日期,没有时间和时区。相比之下,此处列出的DateZonedDateTime 都代表一个时刻,而不是仅代表一个日期。
  • @BasilBourque 实际上有无数种形式的问题组合,例如“X 类和 Y 类有什么区别”。 String 和 InputStream 有什么区别? Integer 和 ExpressionFieldProxyEventBroadcaster 有什么区别?这个问题的核心总结在这里:“我应该使用哪个日期时间库?”
  • @BasilBourque 我的意思是,是否存在精确的文字匹配并不重要。如果这是标准,我们最终会得到:Date 和 LocalDate 之间有什么区别?日期和本地日期时间有什么区别?日期和即时有什么区别?日期和本地时间有什么区别?你明白了。通过迎合这种懒惰的问题,你并没有让好信息更容易找到,你只会让它变得更加支离破碎。
  • 我不了解您的设置,也不了解您要做什么。一个Minimal, Complete, and Verifiable example,好吗?答案是正确的,你应该避开Date 类和朋友,并使用java.time,现代日期和时间API。

标签: java datetime java.util.date zoneddatetime


【解决方案1】:

tl;博士

java.util.Date 和 Zoneddatetime 有什么区别?

  • Date 代表 UTC 的时刻,而 ZonedDateTime 代表特定时区的时刻。
  • Date 是一个糟糕的类,充满了设计缺陷,不应该被使用,而ZonedDateTime 是一个来自 java.time 包的现代类,你会发现它非常有用。

Java 带有两个非常不同的框架来处理日期时间工作:一组非常笨拙且失败的遗留类,以及一组现代行业领先的类,可在 java.time 包中找到。

传统➙现代:

  • java.util.Datejava.time.Instant 取代
    • 两者都代表 UTC 时间。
  • java.util.GregorianCalendarjava.time.ZonedDateTime 取代
    • 两者都代表在特定时区看到的时刻。

java.util.Date

Date 类代表 UTC 中的一个时刻。也就是说,日期、时间以及 UTC 的上下文。

在内部,它是自 UTC 1970 第一刻的纪元参考日期 1970-01-01T00:00:00Z 以来的毫秒数。

把事情复杂化:

  • 创建时有一个时区捕获,存储在内部深处,没有 getter 或 setter。因此,在大多数情况下,我们可以忽略这个区域,尽管它确实适用于诸如此类的 equals 的实现。
  • 当调用toString 时,该类具有非常令人困惑的行为,即在生成表示该对象值的文本时动态应用 JVM 的当前时区。虽然出于善意,但这种反特性给试图学习日期时间处理的 Java 程序员带来了无法估量的痛苦。

困惑?是的,这个类令人困惑,糟糕的设计决策一团糟。后来添加了java.util.CalendarGregorianCalendar

所有这些与最早的 Java 版本捆绑在一起的麻烦的日期时间类现在完全被 java.time 类所取代。

特别是,java.util.Datejava.time.Instant 替换。两者都代表从 1970 UTC 时代开始的 UTC 时刻。但是Instant 的分辨率更高,nanoseconds 而不是milliseconds

您可以通过调用添加到旧类的新方法在旧类Date 和现代类Instant 之间来回转换。通常,您将避免使用Date。但是当与尚未更新到 java.time 的旧代码交互时,您可能需要进行转换。

java.time.ZonedDateTime

现代类ZonedDateTime 代表某个地区(时区)人们使用的挂钟时间中看到的时刻。

所以InstantZonedDateTime 的相似之处在于它们都代表一个时刻,即时间线上的一个特定点。不同之处在于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[亚洲/东京]

关键概念:InstantZonedDateTime 都代表同一时刻,时间轴上的同一同时点。它们的挂钟时间不同。例如,如果日本的某人打电话给冰岛的某人(他们的时钟一直使用 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::setObjectResultSet::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 ) ; 

【讨论】:

  • 感谢您的解释。我无法理解一件事。为什么要在数据库中存储时将ZoneDateTime 转换为OffsetDateTime?我们可以很容易地将它存储为Instant + Offset,这样我们就可以将它映射到 SQL TIMESTAMP WITH TIME ZONE 类型。
  • @Martin JDBC 4.2 规范需要支持 OffsetDateTime,但奇怪的是,不需要 需要支持两种更常用的类型:Instant & @ 987654410@。您的 JDBC 驱动程序可能支持也可能不支持其他两个类。如果是这样,您可以使用这些类型。如果您想编写更便携的代码,请坚持使用OffsetDateTime。我将在此答案中添加一个图形表,其中粉红色条纹显示 JDBC 4.2 所需的类型。
  • 优秀的帖子。小挑剔:在我看来,它不应该在OffsetDateTimeZonedDateTime 旁边说TIMESTAMP WITH TIME ZONE,因为它强化了TIMESTAMP WITH TIME ZONE 存储 时区的误解。在 SQL 中TIMESTAMP WITH TIME ZONE 仅表示可以在这样的列上进行时区转换,它实际上并不持有任何特定的时区。
  • @AndreKR 好点。当我修改图形时,我会记住这一点。
  • @Basil Bourque,非常感谢您对 Java 世界中处理日期的详细而写得好的总结。我正在使用带有我无法控制的 Date 对象的服务器响应。尤其是 Date 对象的 toString() 值使事情变得非常复杂。我现在有一套很好的基本规则。转换为即时,不再触摸:)
【解决方案2】:

java.util.Date 是一个几乎不推荐使用的类,它提供了毫秒值的包装器。它不提供有关时区的任何信息,也没有任何有用的方法,您应该始终避免使用它。

ZonedDateTime 是来自新的 JDK8 日期 API 的类,它在 ISO-8601 日历系统中提供带有时区的日期时间。你应该检查

Oracle's explanation for new date format

【讨论】:

    【解决方案3】:

    16 年前,我为JavaWorld 杂志写了一篇题为What's Your Time Zone 的文章,讨论了您在使用java.util.Date 类时所描述的问题。为了“查看”类java.util.Date 的实例的值,您必须将其转换为String。在 java 1.8 之前,这种转换总是也考虑了时区。这就是为什么在 Java 1.8 中引入了新的 Date-Time API 的原因之一。为 Java 1.8 及更高版本编写的新代码应该使用新的 API。如果可能的话,我相信您还应该考虑将旧代码转换为使用新 API。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-01-14
      • 2010-12-11
      • 2019-08-03
      • 2010-10-02
      • 2011-12-12
      • 2010-09-16
      • 2012-03-14
      • 2012-02-06
      相关资源
      最近更新 更多