我已经红色了多篇文章和讨论,但我仍然有一些不确定性:我不确定是否应该使用Instant
或任何其他类型来存储预订-在“在线预订”的意义上(因此来自不同国家/时区的参与者需要在时间线上的同一时刻会面)。我倾向于使用LocalDateTime
,因为DB和Backend设置为UTC,并且由于传入的“创建预订”json消息包含ISO 8601(带偏移量)start DateTime
让我们采用以下设置:1.数据库(UTC、Oracle、MSSQL)、Java11后端(UTC、SpringBoot、Spring JDBC模板)、Angular 13前端。
资料来源:
现在清楚的是:
下一步:
例如,第2条)告诉我们:许多应用程序只能使用LocalDate、LocalTime和Instant编写,并在UI层添加时区。
3)说明:所以商业应用程序开发人员最常使用Instant和ZonedDateTime类。几乎所有的后端、数据库、业务逻辑、数据持久性、数据交换都应该在UTC中。但是为了向用户展示,您需要调整到用户期望的时区。这就是ZonedDateTime类的目的。
我们来总结一下
LocalTime
<代码>本地日期
本地日期时区(在单独的db列中)
LocalDateTime
圣诞节于2015年12月25日午夜开始
- 预约–在“本地”牙医预约的意义上
即时
也不对于在给定位置的约会(无论是视频电话会议还是简单的理发师约会),您需要ZonedDateTime。
故事时间!
假设你计划2024年夏天在阿姆斯特丹的某个地方,出于某种奇怪的原因,你想确保自己看起来很棒,所以你有点疯狂,决定在理发店预约,时间是2024年5月2日14:00(那将是一个星期三),在阿姆斯特丹的某个特定理发店。
我们可以精确地计算出你在理发店的约会结束前需要经过多少秒:总有一天,你可以告诉这个星球上的任何人等待那些精确的秒数,然后鼓掌。当他们拍手的时候?你预约的确切时间。当然,如果有人在加利福尼亚鼓掌,那将是加利福尼亚州的一大早,但你可以这么做。这就是问题所在——你说的是一个特定的时刻,这意味着这将是一天中不同的时间,这取决于地球上的位置。
但是,这甚至不是假设:欧盟不久前已经决定欧盟应该停止夏令时。欧盟目前正忙于其他一些事情,因此该指令的执行仍在讨论中,但已经决定了——所以现在的争论是关于欧盟何时停止夏季时间,以及绝大多数欧盟国家使用的“中欧时间”(这是一个非常大的时区)需要变成哪个时区。
值得注意的是,欧盟尚未就此做出决定。事实上,到目前为止,每个国家都可以自由选择他们喜欢的任何东西。因此,很有可能,例如荷兰,位于这个非常大的区域的西部边缘,在你的理发店预约的地方,会决定坚持目前的“冬季”,因为德国很可能会这样做,而且两国的公民已经习惯了这两个国家的同一时间。但是,坚持夏季也是很有可能的。我们只是还不知道。
总有一天,在议会,一把锤子会落下。当锤子落下时,正是荷兰时区定义正式改变的时刻,让我们假设他们选择坚持冬季,在锤子落下的那一刻,你的理发师约会只移动了1个小时。现在仍然是2024年5月2日14:00,在阿姆斯特丹,但离你的任命还有3600秒的时间,就在哈默在海牙的荷兰议会降落的那一刻。如果您将这一时刻及时存储在UTC中,那么现在就错了。如果您以还剩的秒数或UTC epoch midnight newyears 1970的秒数来存储这一时刻,那么现在就错了。因此,不要以这些方式存储约会。
这解释了即时时区和ZonedDateTime对象之间的语用差异。
ZonedDateTime对象表示2024年5月2日14:00在Europe/Amsterdam
中“我有一个约会”的概念。
考虑到这正是它所代表的,如果您的tzdata文件被更新以反映锤子的下落,那没关系。现在还是2024年5月2日14:00。就“离约会还有多少秒”而言,当JVM更新了新的tz信息时,该对象所代表的时间将偏移1小时。
Instant
是不同的。Instant用“距离那一刻还有多少秒”来表示一个精确的时间时刻。将约会存储为Instant
的行为将涉及计算直到它发生的秒数,然后存储它。正如我们刚刚确定的那样,这是不正确的,这意味着您将错过约会。这不是约会的工作方式(它以年、月、日、小时、分钟和秒以及可能隐含的时区为单位工作——任何人类使用的议程都不是基于“你的一天134823958150154秒后看起来像什么?”的想法)-您应该始终根据数据类型的工作方式使用数据类型(和存储数据),而不是对其进行某种转换,因为如果您存储转换,您会将对它的依赖“编码”到您的存储中,如果您的转换不再有效,则会出现问题。这就是为什么将其存储为Instant
是这里的错误。
这个故事还解释了为什么存储时区信息的唯一不愚蠢的方法是像Europe/Amsterdam
这样的方法——因为像“CET”(中欧时间)这样的东西可能在两年后甚至不存在。绝对不能保证当欧盟指令生效时,今天参加CET的许多国家中的每一个都会选择同一个区域,无论何时。因此,如果您将约会存储为“在CET区域”,那么到2024年,您所知道的就是,您现在不知道理发店的约会实际上是什么时候。并不那么有用。
每隔一段时间,你就想安排一个会面的时间,而不是一个会面的地点——例如,虚拟会议。通常这些仍然是“本地化的”(某人被视为主持人,他们计划在他们的时间,比如17:30举行会议,所有参与者都存储ZonedDateTime
构造,他们的议程系统会告诉他们何时在适当的时间登录该会议,无论他们在地球上的哪个地方)。所以我就这么做了。
但考虑到会议是虚拟的,“时区”不再必然是真实的。在这种情况下,可以将事物存储为即时(而区域为UTC的区域与即时的概念非常接近,基本上没有任何区别),在这种情况下,例如荷兰的某个人会看到他们的约会在锤子落下的那一刻移动了一个小时。假设他们做得对,并向日历工具指示约会在UTC区域内。
你可能想要这个。但更常见的是走“托管区”路线。(没有太大区别;日历工具需要支持使用时区保存约会的概念(并且不要将其转换为本地区域中的正确时间,因为如果主区域或目标区域决定更改其tz数据,这将中断,这种情况随时都会发生-一旦日历工具支持这一点,您可以选择使用UTC作为此类虚拟会议的主区域)。
你或多或少正确地得出结论,有三个根本不同的,因此完全不相容的日期和时间概念;你不能把一个转换成另一个而不在转换过程中丢失一些东西。这些是:
不幸的是,大多数语言(包括java过时的java.util.Date
和java.util.Calendar
方法)都搞砸了,没有意识到时间就是这么复杂,并且上述3个概念不能在不丢失重要细微差别的情况下转换。
因此,DBs有时也会打得又快又松。更重要的是,JDBC通常是java应用程序与DBs对话的“粘合剂”,它有一个涉及java类型的API。sql。日期和java。sql。时间戳,两者都基于j.u.Date(它们对其进行了扩展),j.u.Date,尽管名称不同,但等同于瞬间,它存储一个时刻,一开始不存储时区,如果时区中断并更改您的定义,它将中断。
因此,您必须使用实际的JDBC调用来获取正确的数据类型,这很棘手。理论上正确的答案是LDT和ZDT直接:
ResultSet rs = somePreparedStatement.query();
/* WRONG */ {
Date x = rs.getDate(1);
ZonedDateTime y = convertToZDT(x);
}
/* RIGHT */ {
ZonedDateTime y = rs.getObject(1, ZonedDateTime.class);
}
然而,许多JDBC驱动程序不支持这一点。非常痛苦。同样,要用日期替换准备好的语句中的问号,正确的移动方式是ps.setObject(1,someZdtInstance),而不是使用j.u.Date实例,甚至不是java。sql。日期实例。不管他们支持不支持。你必须检查——许多人没有。
我建议您查看DB文档,了解它们支持的日期类型以及它实际如何存储这些日期。如果听起来不错(也就是说,它在一列中存储了实际的年、月、日、时、分、秒和完整时区名称),请使用它。如果没有任何数据类型支持这一点,那么您可以自己做,并创建7列。
假设您找到了一种执行此任务的数据类型,请确定是否为。getObject(x,ZDT.class)
工作,而ps.setObject(x,zdtInstance)。但愿如此。如果有,那就是你的答案。如果没有,那么就要绞尽脑汁找出解决方法:编写代码来转换并检查到达的内容——毕竟,如果DB存储的是实际的y/m/d/h/m/s/zone信息,而您给它一个没有该信息的j.u.Timestamp对象,那么DB将不得不再次将其转换回ymdhmsz信息。假设结果很好,那么这个变通方法不会对您造成影响。即使约会是在阿姆斯特丹,而且从现在到那时,锤子都会落下。
商店预订——从“在线预订”的意义上来说(因此来自不同国家/时区的参与者需要在时间表上的同一时刻会面)。我倾向于使用LocalDateTime,因为DB和Backend设置为UTC,并且传入的“create booking”json消息包含ISO 8601(带偏移量)startDateTime
你没有真正定义你所说的“预订”是什么意思。
如果您的意思是精确定位时间线上的某个时刻、某个特定点,那么使用的类就完全错误了。
例如,假设一家导弹发射公司在日本、德国和美国德克萨斯州休斯顿设有办事处。所有办事处的工作人员都希望在发射期间参加小组电话会议。我们将指定发射时刻,其偏移量为UTC的零。为此,我们使用Instant
类。下面字符串中的Z
表示零的偏移量,00:00
,发音为“Zulu”。
Instant launch = Instant.parse( "2022-05-23T05:31:23.000Z" ) ;
让我们告诉每个办公室他们加入电话的截止日期。
ZonedDateTime tokyo = launch.atZone( ZoneId.of( "Asia/Tokyo" ) ) ;
ZonedDateTime berlin = launch.atZone( ZoneId.of( "Europe/Berlin" ) ) ;
ZonedDateTime chicago = launch.atZone( ZoneId.of( "America/Chicago" ) ) ; // Houston time zone is "America/Chicago".
我们有四个日期时间对象,都代表同一个同时时刻。
至于数据库会话和后端服务器的当前默认时区,这些应该与您的编程无关。作为一名程序员,这些都是您无法控制的,并且可能会发生更改。因此,请始终指定所需/预期的时区。
至于你其余的问题,我看不出具体的问题。所以我所能做的就是对你的例子进行评论。
当地时间
公司有一个政策,午餐时间从下午12:30开始,在世界各地的每个工厂。
是的,LocalTime
对于表示世界上任何地方的时间都是正确的。
让我们问问印度德里的工厂午餐是否已经开始。
LocalTime lunchStart = LocalTime.of( 12 , 30 ) ;
ZoneId zKolkata = ZoneId.of( "Asia/Kolkata" ) ;
ZonedDateTime nowKolkata = ZonedDateTime.now( zKolkata ) ;
boolean isBeforeLunchKolkata = nowKolkata.toLocalTime().isBefore( lunchStart ) ;
事实上,我们有一个微妙的错误。在某些区域的某些日期,时间可能不存在。例如,假设该日期的时区有夏时制(DST)转换,“提前一小时”从中午到下午1点。那样的话,就没有12:30了。13:30将是“新的12:30”。
要修复此错误,让我们从nowKolkata调整为使用一天中不同的时间。在此调整过程中,java。time会考虑任何异常,如DST,并对其进行处理。
请注意,移动到一天中的新时间会导致一个新的单独的ZonedDateTime
。java.time类使用不可变对象。
ZonedDateTime lunchTodayKolkata = nowKolkata.with( lunchStart ) ;
现在我们应该重写午餐是否开始的测试。
boolean isBeforeLunchKolkata = nowKolkata.isBefore( lunchTodayKolkata ) ;
让我们问问离午餐还有多久<代码>持续时间(code>Duration)类表示未附加到时间线的时间跨度,以小时分秒为单位。
Duration duration = Duration.between( nowKolkata , lunchTodayKolkata ) ;
我们可以用这段时间作为另一种确定工厂是否已经开始午餐的方法。如果持续时间为负,我们知道我们已经过了午餐开始时间,这意味着我们必须回到过去才能看到午餐开始。
boolean isAfterLunchKolkata = duration.isNegative() ;
boolean isBeforeLunchKolkata = ! duration.isNegative() ;
<代码>本地日期
LocalDate类只表示一个日期,没有一天中的时间,也没有时区上下文或UTC偏移量。
请注意,在任何特定时刻,全球各地的日期因地区而异。所以现在日本东京可能是“明天”,而美国俄亥俄州托莱多也可能是“昨天”。因此,LocalDate在时间轴方面本质上是不明确的。
生日——如果知道某人的年龄到当天都至关重要,那么仅有日期值是不够的。在日本东京,一个看起来满18岁的人在美国俄亥俄州托莱多仍然是17岁。
如果你想准确地知道某人的年龄,除了出生日期和出生时区外,你还需要他们的出生时间。仅仅是出生地的日期和时区不足以将他们的年龄缩小到18岁的第一刻。
美国俄亥俄州托莱多的时区是美国/纽约。
LocalDate birthDate = LocalDate.of( 2000 , Month.JANUARY , 23 ) ;
LocalTime birthTime = LocalTime.of( 3 , 30 ) ;
ZoneId birthZone = ZoneId.of( "America/New_York" ) ;
ZonedDateTime birthMoment = ZonedDateTime.of( birthDate , birthTime , birthZone ) ;
要准确确定此人是否为18岁,请在出生时加上18岁。然后与当前力矩进行比较。
ZonedDateTime turning18 = birthMoment.plusYears( 18 ) ;
ZonedDateTime nowInBirthZone = ZonedDateTime.now( birthZone ) ;
boolean is18 = turning18.isBefore( nowInBirthZone ) ;
合同中的到期日。如果只注明日期,日本的一个利益相关者会认为任务逾期,而托莱多的另一个利益相关者则认为任务按时完成。
真实。
需要时刻精度的合约必须说明日期、一天中的时间和时区。
比如说,一份合同将在明年5月23日之后到期,正如在美国伊利诺伊州芝加哥所看到的那样。
我建议避免“午夜”这个不精确的概念。专注于某个地区某一天的第一个时刻。所以“23日之后”意味着24日的第一个时刻。
某些区域中的某些日期可能从00:00以外的时间开始。让我们来看看java。时间决定一天的第一刻。
LocalDate ld = LocalDate.of( 2023 , Month.MAY , 23 ) ;
ZoneId zChicago = ZoneId.of( "America/Chicago" ) ;
ZonedDateTime expiration = ld.plusDays( 1 ).atStartOfDay( zChicago ) ;
boolean isContractInEffect = ZonedDateTime.now( zChicago ).isBefore( expiration ) ;
LocalDateTime
圣诞节从2015年12月25日午夜开始。
当您指的是在任何地方看到的日期和时间时,请使用
LocalDateTime
。
圣诞节从2015年12月25日的第一刻开始,在每个地方。因此,可以使用LocalDateTime。
LocalDateTime xmas2015 = LocalDateTime.of( 2015 , Month.DECEMBER , 25 , 0 , 0 , 0 , 0 ) ;
该类故意缺少时区或UTC偏移的上下文。因此,此类的对象在时间轴方面本质上是不明确的。此类不能表示一个时刻。
换言之,圣诞老人在抵达日本之前到达基里巴斯(最先进的时区,比UTC提前14小时),然后到达印度,欧洲,等等。红色雪橇整夜在世界各地的一个又一个区域中追逐新的一天。
让我们确定圣诞节在约旦开始的时刻。
ZoneId zAmman = ZoneId.of( "Asia/Amman" ) ;
ZonedDateTime xmasAmman = xmas2015.atZone( zAmman) ;
将该时刻调整为美国丹佛科罗拉多州的时区。
ZoneId zDenver = ZoneId.of( "America/Denver" ) ;
ZonedDateTime xmasStartingInAmmanAsSeenInDenver = xmasAmman.withZoneSameInstant( zDenver ) ;
如果你询问丹佛的这个对象,你会发现当约旦的圣诞节开始时,丹佛的日期仍然是24日,也就是圣诞节的前一天。科罗拉多州要过几个小时才能迎来圣诞节。
预约–在“本地”牙医预约的意义上
未来,无论时区规则发生任何变化,旨在坚持其指定时间的约会必须作为三个单独的部分进行跟踪:
日期与一天中的时间
- 解释该日期的时区
让我们预约明年1月23日下午3点在新西兰。
LocalDateTime ldt = LocalDateTime.of( 2023 , 1 , 23 , 15 , 0 , 0 , 0 ) ;
ZoneId z = ZoneId.of( "Pacific/Auckland" ) ;
Duration d = Duration.ofHours( 1 ) ;
这三项中的每一项都必须存储在您的数据库中。
第一个可以存储在一列中,该列的类型类似于无时区的SQL标准类型时间戳。注意“没有”,而不是“有”
当需要创建时间表时,必须确定一个时刻。要做到这一点,请组合您的各个部分。
LocalDateTime ldt = myResultSet.getObject( … , LocalDateTime.class ) ;
ZoneId z = ZoneId.of( myResultSet.getString( … ) ) ;
Duration d = Duration.parse( myResultSet.getString( … ) ) ;
ZonedDateTime start = ldt.atZone( z ) ;
ZonedDateTime end = start.plus( d ) ;
我和其他人已经多次解释了这一点,所以搜索以了解更多信息。关键的一点是,世界各地的政治家都表现出了频繁改变其管辖范围内时区规则的倾向。而他们这样做却没有多少预警。因此,下午3点的约会时间可能比你现在预期的提前一个小时,或者推迟半个小时,谁知道会发生什么。期望您感兴趣的时区保持稳定,这将注定您的软件最终会失败。你可能认为我偏执。让我们回头看看几年后……当您预订应用程序中的所有客户都在错误的时间到达时。
瞬间的
一些关键功能必须在每天凌晨1:30(LocalDateTime)启动
不,不正确,不是
Instant
。如果您想在每天凌晨1:30运行任务,您需要使用LocalTime
与ZoneId
相结合。这在概念上类似于上面的约会讨论。
如果我们希望芝加哥办事处的服务器在凌晨运行该任务,我们将定义日期时间和区域。
ZoneId z = ZoneId.of( "America/Chicago" ) ;
LocalTime targetTime = LocalTime.of( 1 , 30 ) ;
要确定下一次运行,请捕获当前时刻。
ZonedDateTime now = ZonedDateTime.now( z ) ;
看看我们是否在目标时间之前,然后计算等待的时间。我们用“不在之后”作为“如果在之前或等于”的简短表达方式。想想当前时刻是否恰好是上午1:30:00.000000000。
if( ! now.toLocalTime().isAfter( targetTime ) ) { // If before or equal to the target time.
Duration d = Duration.between( now , now.with( targetTime ) ) ;
…
}
如果今天的目标时间已过,请添加一天。然后计算到下一次运行的时间。
else { // Else, after target time. Move to next day.
LocalDate tomorrow = now.toLocalDate().plusDays( 1 ) ;
ZonedDateTime nextRun = ZonedDateTime.of( tomorrow , targetTime , z ) ;
Duration d = Duration.between( now , nextRun ) ;
…
}
分区DateTime。如果您指定的时间在该区域的该日期不存在,则方法会自动调整。
如果您使用执行器服务在同一本地时间运行您的任务,则不能将
schduledExecutorService
与方法schduleAtFixed速率
或schduleSusFixedDelay
一起使用。使用具有单个延迟的调度
方法。
将对该执行器服务的引用传递给任务的构造函数,即可运行的。然后,以这样的方式编写任务,即它所做的最后一件事以新计算的延迟提交给执行者服务。大多数情况下,延迟时间为24小时。但在夏时制(DST)转换等异常天气,延迟可能约为23小时或25小时。因此,每次任务运行都会安排下一次任务运行。
问题内容: 我有这个Java代码段。我是Java的菜鸟。 错误: 码: 问题答案: 是的,这是问题所在: 在课程的最高级别,您只能拥有: 实例初始化程序块() 静态初始值设定块() 变量声明 构造函数声明 方法声明 嵌套类型声明 终结器声明 这些都不是。如果你 的意思 来声明一个变量,你应该这样做: 如果这 不是 您想要的,则应解释您的意图。 编辑:修复此问题后,此编译器错误似乎很明显: Conf
我的问题是关于cucumber特性文件的并行执行。在Selenium Java中,可以通过一个runner类并行运行多个cucumber特性文件吗? 我尝试过不同的方法,但都没有成功。
当我运行程序而不是找到骑士之旅时,我收到了一个StackOverflow错误。任何想法是什么导致了这一点,以及我如何改变我的代码,实际上找到骑士之旅,并摆脱这个错误。项目是为我的CS280课程,并在周五到期,请帮助。谢谢!!
一个能够让程序猿快速开发的炒鸡脚手架,核心技术Spring、JPA、Shiro。 基础环境 JDK1.8、Maven、Mysql、Redis、IntelliJ IDEA、minio、fastdfs 相关组件 - ok-admin - vue - iView - echarts - clipboard - cropperjs - lightbox - nprogress - webuploader
我使用的是,它最终扩展自。当我将look and feel更改为时,文本区域不会像GUI的其他部分那样受到影响,这看起来不太好。 这是我使用的主题代码: 我想知道需要哪个键来修改文本区域的背景色,例如,将左侧中的紫色设置为背景色,而不是白色? 下面是我找到的键的列表,但在这种情况下,它们中有没有哪一个有用?
嗨,我正在编写一个井字游戏。我已经在代码中的注释中详细说明了我需要什么。我现在遇到的问题是制作一个getMobile方法。我想我需要在按下行和列后的if/else语句中调用getMobile方法? 我不确定如何从获取行/列编号并将其放入我的板上以获取用户输入的内容。 以下是我的代码: