3 java timestamp date spring-data-jpa spring-boot
在我的 Java(使用 Spring Boot 和 Spring Data JPA)应用程序中,我通常使用 Instant。另一方面,我想对时间值使用最合适的数据类型。
您能帮我解释一下这些问题吗?在以下情况下,我应该选择哪种数据类型来保存日期和时间:
1.将时间精确地保留为时间戳(我不确定Instant是否是最佳选择)?
2.对于正常情况,当我只需要日期和时间时(据我所知,旧库已经过时,但不确定我应该使用哪个库)。
我还考虑了时区,但不确定使用 LocalDateTime 与 UTC 是否可以解决我的问题。
任何帮助,将不胜感激。
rzw*_*oot 10
假设我们需要涵盖整个日期和时间问题。如果您没有某种担忧,那么要么将各种类型折叠为“那么它们是可以互换的”,要么只是意味着您不需要使用 API 的特定部分。关键是,您需要了解这些类型代表什么,一旦了解了这一点,您就知道要应用哪一种。因为即使各种不同的java.time类型在技术上都可以实现您想要的功能,但如果您使用的类型代表了您希望它们实现的功能,则代码会更加灵活并且更易于阅读。出于同样的原因,String[] student = new String[] {"Joe McPringle", "56"};这也许是机械地表示学生姓名和年龄的一种方式,但如果您编写 aclass Student { String name; int age; }并使用它,事情就会简单得多。
想象一下您想在早上 07:00 起床。不是因为你有约会,你只是喜欢早起。
\n因此,您将闹钟设置为早上 07:00,然后去睡觉,闹钟会在 7 点准时响起。到目前为止,一切顺利。然而,您随后会跳上飞机从阿姆斯特丹飞往纽约。(纽约比纽约早 6 小时)。然后你再去睡觉。闹钟应该在晚上 01:00 响,还是早上 07:00 响?
\n两个答案都是正确的。问题是,如何“存储”该警报,要回答这个问题,您需要弄清楚警报试图代表什么。
\n如果意图是“07:00,无论闹钟响起时我在哪里”,正确的数据存储机制是java.time.LocalDateTime,它以人类的方式存储时间(年、月、日、小时、分钟和秒),而不是计算机术语(我们稍后会讲到),并且根本不包括时区。如果闹钟应该每天响起,那么您也不希望这样,因为 LDT 存储日期和时间,因此您可以使用该名称LocalTime。
那是因为您想要存储“闹钟应该在 7 点钟响起”的概念,仅此而已。你无意说:“当阿姆斯特丹的人们都同意现在是 07:00 时,闹钟就应该响起”,你也无意说:“当宇宙到达这个确切的时刻时,请敲响警报。”警报”。您的意图是说:“当 07:00 时,无论您现在身在何处,请拉响警报”,因此请存储该,它是LocalTime.
同样的原则也适用于LocalDate:它存储一个年/月/日元组,没有位置的概念。
这确实得出了一些可能不太可靠的结论:给定一个LocalDateTime对象,不可能询问LDT 到达需要多长时间。也不可能将任何特定时刻与 LDT 进行比较,因为这些东西都是苹果和橙子。“2023 年 2 月 18 日早上 7 点整”这个概念并不是一个单一的时间。毕竟,在纽约,这个“时刻”比在阿姆斯特丹早了整整 6 个小时。您只能比较 2 个 LocalDateTime。
相反,您必须首先通过询问 java.time API 将 LDT 转换为其他类型之一(ZonedDateTime 甚至 Instant),将其“放置”在某个地方:好的,我希望这个特定的 LDT 位于某个时区。
\n因此,如果您正在编写闹钟应用程序,则必须获取存储的闹钟(一个LocalTime对象),将其转换为即时(这就是“现在是什么时间,即”的本质System.currentTimeMillis()),方法是说:本地时间,当前本地时区当天的即时,然后比较这两个结果。
想象一下,就在飞往纽约之前,您预约了当地(阿姆斯特丹)的理发师。他们的议程有点忙,所以约会定在 2025 年 6 月 20 日 11:00。
\n如果你在纽约待了几年,你的日历提醒你一小时后与理发师预约的正确时间肯定不是纽约2025 年 6 月 20 日 10:00。到时候你就已经错过了约会。相反,你的手机应该在半夜 04:00 提醒你,你还有一个小时的时间去理发店(当然,从纽约出发有点棘手)。
\n听起来我们确实可以说理发师的预约是一个特定的时刻。然而,这是不正确的。欧盟已经通过了所有成员国一致同意的立法,要求所有欧盟国家废除夏令时。然而,这项法律并没有规定最后期限,更重要的是,没有规定每个欧盟成员国需要选择的时区。因此,荷兰将在某个时候更改时区。他们可能会选择坚持永久夏令时(在这种情况下,他们将永久使用 UTC+2,而目前的情况是夏季使用 UTC+2,冬季使用 UTC+1,值得注意的是,转换发生的日期与纽约不同!),或者永远保留冬令时,即 UTC+1。
\n假设他们选择永远坚持冬季。
\n荷兰议会大楼敲下木槌,将荷兰人三月份不再将时钟提前的那一天,就是你的约会推迟一小时的那一天。毕竟,您的理发师不会进入他们的预约簿并将所有预约推迟一个小时。不,您的预约将保留在 2025 年 6 月 20 日 11:00。如果你有一个正在倒计时直到预约理发师的时钟,那么当木槌落下时,它应该跳动 3600 秒。
\n这掩盖了这一点:理发师的任命确实不是一个单一的时刻。这是人类/政治协议,阿姆斯特丹普遍同意当前时间为 2025 年 6 月 20 日,11:00 \xe2\x80\x93,谁知道那一刻实际上会发生;这取决于政治选择。
\n因此,您无法通过存储即时时间来“解决”这个问题,并且它显示了“即时时间”和“特定时区的年/月/日时:分:秒”概念如何不能完全互换。
\n此概念的正确数据类型是ZonedDateTime. 这表示人类术语中的日期时间:年/月/日时:秒:分钟和时区。它不会通过在纪元或类似的时间中存储某个时刻来走捷径。如果木槌落下并且您的 JDK 更新了其时区定义,则询问“距离我的约会还有多少秒”将正确地移动 3600 秒,这正是您想要的。
因为这是用于约会的,并且只存储约会时间而不存储日期是没有意义的,所以不存在 aZonedDate或 a之类的东西ZonedTime。与第一个有 3 种口味(LocalDateTime、LocalDate和LocalTime)的东西不同,只有ZonedDateTime.
想象一下,您正在编写一个计算机系统来记录发生的事件。
\n该事件自然有一个与之关联的时间戳。事实证明,由于严重的政治动荡,该国的法律决定该国的时区与事件发生时您所想象的不同。应用与理发师案例相同的逻辑(当木槌落下时,实际时间跳跃 3600 秒)是不正确的。时间戳代表事情发生的时刻,而不是账本中的约会。它不应该跳到3600。
\n时区在这里确实没有任何意义。存储日志事件的“时间戳”的目的是让您知道它何时发生,无论它发生在哪里(或者如果发生了,这从根本上来说是一个单独的概念)。
\n正确的数据类型是java.time.Instant. 瞬间根本不知道时区,也不是人类的概念。这是“计算机时间”——自商定的纪元(午夜、UTC、1970、新年)以来存储为毫秒,这里不需要时区信息或理智的时区信息。当然,不存在仅时间或仅日期的变体,这个东西甚至不真正知道“日期”是什么 - 一些花哨的人类概念,计算机时间根本不关心。
您可以轻松地从 a 转到ZonedDateTimean Instant。有一个无参数方法可以做到这一点。但请注意:
导致不同的结果:两个时刻不相同,但 ZDT 相同。ZDT 代表理发师手册中的预约线(从未改变 - 2025 年 6 月 20 日,11:00),瞬间代表您应该出现的时间点,但确实发生了变化。
\n如果您将理发师的预约存储为java.time.Instant对象,那么您的理发师预约将会迟到一个小时。这就是为什么按原样存储东西很重要。理发师的预约是ZonedDateTime. 像其他任何东西一样存储它是错误的。
转换很少是真正简单的。没有一种方法可以将一种事物转换为另一种事物 - 您需要考虑这些事物代表什么,转换意味着什么,然后照着做。
\n示例:您正在编写一个日志系统。后端部分将日志事件存储到某种数据库中,前端部分读取该数据库并向管理员用户显示日志事件以供查看。因为管理员用户是一个人,所以他们希望以他们理解的方式查看它,例如根据 UTC 的时间和日期(这是一个程序员,他们倾向于喜欢这类事情)。
\n日志系统的存储应该存储Instant概念:纪元毫秒,并且没有时区,因为这是不相关的。
前端应该将这些读取为Instant(进行静默转换总是一个坏主意!) - 然后考虑如何将其呈现给用户,弄清楚用户希望这些作为本地到 UTC,因此你可以苍蝇,对于要打印到屏幕上的每个事件,将 Instant 转换为用户想要的区域中的 ZonedDateTime,然后从那里转换为 LocalDateTime,然后渲染它(因为用户可能不希望在UTC每一行上看到他们的屏幕空间有限)。
将时间戳存储为 UTC ZonedDateTimes 是不正确的,将它们存储为 LocalDateTimes 则更错误,这是通过在事件发生时询问 UTC 中的当前 LocalDT 然后存储它而派生的。从机械上讲,所有这些事情都可以工作,但数据类型都是错误的。这会让事情变得复杂。想象一下用户实际上想要查看欧洲/阿姆斯特丹时间的日志事件。
\n世界比少数时区更复杂。例如,几乎所有欧洲大陆目前都是“CET”(中欧时间),但有些人认为指的是欧洲冬季时间(UTC+1),有些东西指的是中欧当前的状态:UTC+1冬季,夏季 UTC+2。(还有 CEST,中欧夏令时间,这意味着 UTC+2 并且不含糊)。当欧盟国家开始应用新法律取消夏令时时,例如位于 CET 区域西边缘的荷兰可能会选择与东边缘的波兰不同的时间。因此,“整个中欧”过于宽泛。三字母缩写词也绝不是唯一的。许多国家都使用“EST”来表示“东部标准时间”,例如不仅仅是美国东部。
\n因此,表示时区名称的唯一正确方法是使用类似Europe/Amsterdam或 的字符串Asia/Singapore。如果你需要像09:00 PST美国西海岸的居民一样渲染这些,那就是渲染问题,所以,写一个渲染方法,变成America/Los_Angeles,PST这是本地化问题,与时间无关。
rzwitserloot 的答案是正确且明智的。另外,这里对各种类型进行了总结。欲了解更多信息,请参阅我的答案对类似问题的回答。
\n\n\n\n
\n- 要将时间精确地保留为时间戳(我不确定 Instant 是否是最佳选择)?
\n
如果您想跟踪某个时刻,即时间轴上的特定点:
\nInstantOffsetDateTimeZonedDateTime如果您只想跟踪日期和时间,而不需要偏移量或时区的上下文,请使用LocalDateTime。这个类并不代表某个时刻,也不是时间轴上的一个点。
\n\n\n
\n- 对于正常情况,当我只需要日期和时间时
\n
如果您绝对确定只需要带有当天时间的日期,但不需要偏移量或时区的上下文,请使用LocalDateTime。
\n\n将 LocalDateTime 与 UTC 结合使用
\n
这是矛盾的,也是没有道理的。类LocalDateTime没有 UTC 的概念,也没有任何与 UTC 或时区的偏移量的概念。
\n\nSpring数据JPA
\n
JDBC 4.2+ 规范将 SQL 标准数据类型映射到Java类。
\nTIMESTAMP WITH TIME ZONEJava 中的列映射OffsetDateTime。TIMESTAMP WITHOUT TIME ZONEJava 中的列映射LocalDateTime。DATE列映射到LocalDate.TIME WITHOUT TIME ZONE列映射到LocalTime.SQL标准也提到了TIME WITH TIME ZONE,但是这种类型是没有意义的(想想看!)。据我所知,SQL 委员会从未解释过他们的想法。如果必须使用此类型,Java 会定义ZoneOffset要匹配的类。
请注意,JDBC不会将任何 SQL 类型映射到Instantnor ZonedDateTime。您可以轻松地与映射类型进行转换OffsetDateTime。
Instant instant = myOffsetDateTime.toInstant() ;\nOffsetDateTime myOffsetDateTime = instant.atOffset( ZoneOffset.UTC ) ;\nRun Code Online (Sandbox Code Playgroud)\n\xe2\x80\xa6 和:
\nZonedDateTime zdt = myOffsetDateTime.atZoneSameInstant( myZoneId ) ;\nOffsetDateTime odt = zdt.toOffsetDateTime() ; // The offset in use at that moment in that zone.\nOffsetDateTime odt = zdt.toInstant().atOffset( ZoneOffset.UTC ) ; // Offset of zero hours-minutes-seconds from UTC.\nRun Code Online (Sandbox Code Playgroud)\n\n\n我也考虑时区
\n
该类TimeZone是可怕的遗留日期时间类的一部分,这些类在几年前被现代java.time类取代。替换为ZoneId和ZoneOffset。
| 归档时间: |
|
| 查看次数: |
4050 次 |
| 最近记录: |