Spring Boot 我应该在Java实体中使用即时、日期时间还是本地日期时间?[已关闭]

zvms9eto  于 2023-02-22  发布在  Spring
关注(0)|答案(3)|浏览(145)
    • 已关闭**。此问题为opinion-based。当前不接受答案。
    • 想要改进此问题吗?**请更新此问题,以便editing this post可以用事实和引文来回答。

7小时前关闭。
Improve this question
在我的Java(带有Spring Boot和Spring Data JPA)应用程序中,我通常使用Instant。另一方面,我希望对时间值使用最合适的数据类型。
您能解释一下这些问题吗?在以下情况下,我应该使用哪种数据类型来保存日期和时间:

    • 1.**要将时间精确地保存为时间戳(我不确定即时是否是最佳选项)?
    • 2.**对于我只需要日期和时间的正常情况(据我所知,旧库已经过时,但不确定我应该使用哪个库)。

我还考虑了时区,但不确定将LocalDateTime与UTC一起使用是否能解决我的问题。
任何帮助都将不胜感激。

gojuced7

gojuced71#

让我们假设我们需要涵盖日期和时间的全部范围。如果有一个特定的关注你没有,要么折叠各种类型到'那么他们是可互换的'或只是意味着你不需要使用API的某一部分。关键是,你需要了解这些类型 * 代表 *,一旦你知道,你知道该应用哪一个。因为 * 即使 * 各种不同的java.time类型都 * 技术上 * 做你想做的,如果你使用的类型表示你想要它们做的事情,代码就更灵活,读起来也更简单。出于同样的原因,String[] student = new String[] {"Joe McPringle", "56"};可能是机械地表示学生的但是如果您编写一个class Student { String name; int age; }并使用它,事情就会简单得多。

本地闹钟

想象一下,你想在早上7点起床,不是因为你有约会,而是因为你喜欢早起。
所以你把闹钟定在早上07:00去睡觉,闹钟在7点准时响了。到目前为止,一切都很好。但是,你随后跳上飞机,从阿姆斯特丹飞往纽约。(纽约早了6个小时)。然后你又去睡觉。闹钟应该在晚上01:00响,还是在早上07:00响?
两个答案都是正确的。问题是,你如何“存储”那个警报,要回答这个问题,你需要弄清楚警报试图代表什么。
如果意图是“07:00,闹钟应该响的时候我可能在哪里”,正确的数据存储机制是java.time.LocalDateTime,它以人类的方式存储时间(年、月、日、小时、分钟和秒),而不是以计算机的方式(我们稍后会讲到),并且根本不包括时区。如果闹钟应该每天响,那么你也不希望这样,因为LDT存储 datetime,因此得名,你应该使用LocalTime
那是因为你想把“闹钟应该在7点钟响”的概念储存起来,而仅此而已。你并不想说:“警报应该在阿姆斯特丹的人们同意目前是07:00时响起”,你也没有说:“当宇宙到达这个精确的时刻,拉响警报”。你的意图是说:“当它是07:00无论你现在在哪里,拉响警报”,所以存储 * 那 *,这是一个LocalTime
同样的原理也适用于LocalDate:它存储年/月/日元组,没有 where 的概念。
这确实得出了一些或许不可靠的结论:给定一个LocalDateTime对象,不可能问它需要多长时间才能到达LDT。也不可能将任何给定时刻与LDT进行比较,因为它们是苹果和桔子。“2023年2月18日,早上7点”的概念不是一个单一的时间。毕竟,在纽约,这一时刻比在阿姆斯特丹早了整整6个小时。您只能比较2个LocalDateTimes。
相反,您必须首先将LDT“放置”在某个地方,通过请求java.time API将其转换为其他类型之一(ZonedDateTime或甚至Instant):好的,我要这个特定的LDT在特定的时区。
因此,如果您正在编写闹钟应用,您必须获取存储的闹钟(LocalTime对象),将其转换为Instant(这是“现在是什么时间,即System.currentTimeMillis()”的本质),方法是:当前本地时区中当天的LocalTime作为一个瞬间,然后比较这两个结果。

人事任命

想象一下,就在飞往纽约之前,你在当地(阿姆斯特丹)的理发师那里预约了一个时间,他们的日程安排有点忙碌,所以预约时间定在2025年6月20日11点。
如果你在纽约待了几年,日历提醒你一小时后和理发店有个约会的正确时间肯定是而不是纽约2025年6月20日10点。到那时你已经错过了约会。相反,你的手机应该在半夜04:00提醒你还有一个小时去理发店(当然,从纽约来看,有点棘手)。
听起来我们确实可以说理发师的预约是一个特定的时刻。然而,这是不正确的。欧盟已经通过了所有成员国同意的立法,要求所有欧盟国家废除夏令时。然而,这项法律没有规定最后期限,而且至关重要的是,没有提供每个欧盟成员国需要选择的时区。因此,荷兰将在某个时候改变时区。他们可能会选择坚持永久的夏令时(在这种情况下,他们将永久地处于UTC+2,而不是他们当前的情况,即他们在夏季处于UTC+2,在冬季处于UTC+1,值得注意的是,与纽约相比,转换发生的日期不同!),或者保持冬季时间,即永远处于UTC+1。
假设他们选择永远坚持冬天的时间。

法槌在荷兰议会大厦敲响的那一天,荷兰将不再把3月份的时钟提前**,也就是你的预约时间提前一小时的那一天。毕竟,你的理发师不会翻到他们的预约簿,把所有预约时间都提前一小时。不,你的预约时间将保留在2025年6月20日11:00。如果你有一个正在运行的时钟,在你预约理发前滴答作响,当法槌敲下时,它应该跳3600秒。
这与事实不符:那个理发师的约会真的不是一个单一的时刻,而是一个人类/政治的协议,你的约会是阿姆斯特丹普遍同意的时间,现在是2025年6月20日11点,谁知道那个时刻什么时候会真正发生;这取决于政治选择 *。
所以,你不能通过存储一个瞬间来“解决”这个问题,它显示了“瞬间”和“某个时区的年/月/日时:分:秒”的概念是如何不太可互换的
这个概念的正确数据类型是ZonedDateTime,它表示人类使用的日期时间:year/month/day hour:second:minute,时区。它不会通过在epochmillis或类似的文件中存储某个时刻来缩短时间。如果敲下木槌,您的JDK更新了它的时区定义,询问“离我的约会还有多少秒”将正确地移动3600秒,这正是您想要的。
因为这是用于约会的,只存储约会时间而不存储日期是没有意义的,所以没有ZonedDateZonedTime这样的东西,不像第一个东西有3种风格(LocalDateTimeLocalDateLocalTime),只有ZonedDateTime

宇宙/日志时间

假设你正在编写一个记录某个事件发生的计算机系统。
这个事件,自然地,有一个时间戳与之相关。结果是由于严重的政治动荡,这个国家的法律决定,“追溯”这个国家在事件发生时所处的时区与你想象的不同。应用与理发师案例相同的逻辑(当小木槌落下时,实际时刻跳变3600秒)是不正确的。时间戳表示事情发生的时刻,而不是账本上的约会不应该跳到3600
时区在这里真的没有任何意义。为日志事件存储“时间戳”的目的是让你知道它是什么时候发生的,它在哪里发生并不重要(或者如果它发生了,那基本上是一个单独的概念)。
正确的数据类型是java.time.Instant。一个瞬间根本不知道时区,也不是人类的概念。这是“计算机时间”--从约定的纪元开始以米利斯为单位存储(午夜,UTC,1970年,新年),这里不需要时区信息。自然地,不存在仅时间或仅日期变量,这东西甚至不知道“日期”是什么--一些花哨的人类概念,计算机时间一点也不关心。

转换

ZonedDateTimeInstant的转换很简单,有一个无参数的方法可以完成,但是要注意:
1.创建分区日期时间。
1.把它藏起来。
1.将其转换为“即时”,并将其存储。
1.更新JDK并获取新的时区信息
1.加载ZDT。
1.再次将其转换为“即时”。
1.比较2个ZDT和2个瞬间。
结果不同:这两个时刻可能不一样,但ZDT是一样的。ZDT代表理发师的预约线(从未改变- 2025年6月20日11:00),时刻代表你应该出现的时间点,但确实改变了。
如果你将理发师的约会存储为java.time.Instant对象,你将在理发师的约会上迟到一个小时。这就是为什么按原样存储很重要。理发师的约会是一个ZonedDateTime。将它存储为任何其他对象都是错误的。
转换很少是真正简单的,没有一种方法可以将一个事物转换成另一个事物--你需要思考这些事物代表什么,转换意味着什么,然后照着做。
示例:您正在编写一个日志系统。后端部分将日志事件存储到某种数据库中,而前端部分读取该数据库并将日志事件显示给管理员用户以供查看。因为管理员用户是人类,他们希望以他们理解的方式查看日志,例如,根据UTC的时间和日期(这是程序员,他们倾向于喜欢这类事情)。
日志记录系统的存储器应存储Instant概念:纪元米利斯没有时区,因为这是无关紧要的。

前端应将这些 * 读取为Instant *(执行静默转换总是一个坏主意!)-然后考虑如何将其呈现给用户,弄清楚用户希望将其作为本地到UTC的转换,因此,您可以在运行中,对于要打印到屏幕的每个事件,将Instant转换为用户所需区域中的ZonedDateTime,然后从那里转换到LocalDateTime,然后呈现(因为用户可能不希望在每一行都看到UTC,所以他们的屏幕空间有限)。
将时间戳存储为UTC ZonedDateTimes是不正确的,而将它们存储为LocalDateTimes(通过在事件发生时以UTC格式查询当前LocalDT并将其存储而获得)则更是错误的。从机械上讲,所有这些事情都可以 * 工作 *,但数据类型都是错误的。这将使事情变得复杂。想象一下,用户实际上希望以欧洲/阿姆斯特丹时间查看日志事件。
关于时区的说明
世界比少数几个时区要复杂得多。例如,几乎所有的欧洲大陆目前都是'CET'(中欧时间),但有些人认为那指的是欧洲冬季时间(UTC+1),有些东西指的是中欧目前的状态:UTC+1在冬天,UTC+2在夏天。(还有CEST,中欧夏令时,意思是UTC+2,没有歧义)。当欧盟国家开始应用新的法律来摆脱夏令时,它很可能是,例如,荷兰在CET区的西部边缘选择一个不同的时间比波兰在东部边缘。因此,“所有的中欧”太宽泛了。3个字母的首字母缩写词也不是唯一的。许多国家都用“EST”来表示“东部标准时间”,不仅仅是美国东部。
因此,表示时区名称的唯一正确方法是使用Europe/AmsterdamAsia/Singapore这样的字符串。如果您需要为美国西海岸的居民将这些字符串渲染为09:00 PST,这是一个渲染问题,因此,编写一个将America/Los_Angeles转换为PST的渲染方法,这是一个本地化问题,与时间无关。

imzjd6km

imzjd6km2#

Answer by rzwitserloot是正确和明智的。此外,这里是各种类型的摘要。有关详细信息,请参阅my Answer上的类似问题。
1.保持时间精确为时间戳(我不确定即时是否是最佳选择)?
如果要跟踪某个时刻,即时间线上的特定点:

  • Instant

与UTC的偏差为零时-分-秒的时刻。此类是 * java. time * 框架的基本构建块。

  • OffsetDateTime

以特定偏移量看到的时刻,比UTC的时间子午线早或晚若干小时-分钟-秒。

  • ZonedDateTime

一个特定时区所看到的时刻。时区是过去、现在和将来的变化的命名历史,其偏移量由特定地区的政治家决定,由该地区的人民使用。
如果你只想跟踪日期和时间,而不想跟踪偏移量或时区的上下文,那么使用LocalDateTime。这个类 * 不 * 代表一个时刻,也 * 不是 * 时间线上的一个点。
1.对于只需要日期和时间的正常情况
如果您完全确定只需要带有时间的日期,但不需要偏移量或时区的上下文,请使用LocalDateTime
将LocalDateTime与UTC一起使用
这是一个矛盾的概念,没有任何意义。LocalDateTime类没有UTC的概念,也没有UTC偏移量或时区的概念。
Spring数据JPA
JDBC 4.2+规范将SQL标准数据类型Map到Java类。

  • TIMESTAMP WITH TIME ZONE列Map到Java中的OffsetDateTime
  • TIMESTAMP WITHOUT TIME ZONE列Map到Java中的LocalDateTime
  • DATE列Map到LocalDate
  • TIME WITHOUT TIME ZONE列Map到LocalTime

SQL标准也提到了TIME WITH TIME ZONE,但是这个类型是没有意义的(想想看吧!),据我所知,SQL委员会从来没有解释过他们的想法,如果你必须使用这个类型,Java定义了ZoneOffset类来匹配。
请注意,JDBC不会将任何SQL类型Map到InstantZonedDateTime。您可以轻松地与Map类型OffsetDateTime进行转换。

Instant instant = myOffsetDateTime.toInstant() ;
OffsetDateTime myOffsetDateTime = instant.atOffset( ZoneOffset.UTC ) ;

...以及:

ZonedDateTime zdt = myOffsetDateTime.atZoneSameInstant( myZoneId ) ;
OffsetDateTime odt = zdt.toOffsetDateTime() ;  // The offset in use at that moment in that zone.
OffsetDateTime odt = zdt.toInstant().atOffset( ZoneOffset.UTC ) ;  // Offset of zero hours-minutes-seconds from UTC.

我还考虑了时区
TimeZone类是可怕的遗留日期-时间类的一部分,这些类在几年前就被现代的 * java.time * 类所取代,被ZoneIdZoneOffset所取代。

ttisahbt

ttisahbt3#

您应该查看一下Java 8中引入的 Java Date and Time APIInstantLocalDateTimeZonedDateTime等每个类都有一个JavaDoc文档。如果您在理解文档时遇到问题,请提供更具体的问题。

相关问题