当时间需要在本地时间保持不变时,在 Java 中使用 OffsetDateTime 处理夏令时,而不管 DST

Posted

技术标签:

【中文标题】当时间需要在本地时间保持不变时,在 Java 中使用 OffsetDateTime 处理夏令时,而不管 DST【英文标题】:Handling Daylight Saving Time with OffsetDateTime in Java when time needs to remain the same in local time regardless of DST 【发布时间】:2021-06-24 22:56:42 【问题描述】:

我正在使用 Spring Boot (Java 8) 为餐厅应用程序开发后端,该应用程序公开了一些 REST API。与日期和时间相关的所有内容都使用OffsetDateTime 对象和通过我使用的应用程序处理,默认情况下,"Europe/Rome" 的偏移量。这些对象也保存在数据库中。一切正常,除了......夏令时开始,现在餐厅的所有营业时间都关闭了一小时。

这显然是因为"Europe/Rome" 的偏移量在夏令时前是+01:00,而夏令时后是+02:00。所以,举个例子,餐厅的营业时间(保存在数据库中的夏令时前)是08:30:00+01:00,但是当从 API 访问夏令时后时,它变成了09:30:00+02:00(因为它会自动转换为默认值)系统区域)。无论夏令时如何,我都需要餐厅的营业时间在当地时间保持不变。

我该如何处理这种情况并向我的用户提供一致的数据?

【问题讨论】:

餐厅的营业时间存储在分区或偏移数据类型中没有任何意义。在我看来,你应该使用LocalDateTime 【参考方案1】:

时差与时区

"Europe/Rome" 是时区的名称,而不是偏移量。

与 UTC 的偏移只是比 UTC 的基线提前或落后几个小时-分钟-秒。当您向东移动时,当地时间比 UTC 时间越来越早,而向西移动到美洲时,当地时间比 UTC 时间越来越晚。

时区要多得多。时区是特定地区的人们使用的偏移量的过去、现在和未来变化的历史记录。

Continent/Region 的格式指定proper time zone name,例如America/MontrealAfrica/CasablancaPacific/Auckland。切勿使用 2-4 个字母的缩写,例如 ESTIST,因为它们不是真正的时区,没有标准化,甚至不是唯一的(!)。

你说:

与日期和时间相关的所有内容都由OffsetDateTime 对象处理

不,你不应该在所有情况下都使用那个单一的类。 OffsetDateTime 类用于表示您知道偏移量但不知道时区的时刻。当您知道时区时,应使用ZonedDateTime。而对于特定于 UTC 的时刻(偏移量为零),通常最好使用 Instant

OffsetDateTime 很少是合适的选择。时区总是比单纯的偏移更可取。所以通常你会想要ZonedDateTimeInstant。一般来说,程序员和系统管理员应该在 UTC 中思考、工作、记录和调试,因此使用 Instant。要在预期时区或业务规则要求的情况下向用户展示,请使用ZonedDateTime。一个大的例外是JDBC 用于数据库访问——JDBC 4.2 specification 莫名其妙地需要兼容drivers 来支持OffsetDateTime,而不是InstantZonedDateTime。幸运的是,类之间的转换非常简单易行。

时刻

您需要了解片刻与非片刻之间的区别。片刻是时间线上的特定点。忘记时区和偏移量,如果摩洛哥卡萨布兰卡的某个人给日本东京的某个人打电话,他们分享的是同一时刻。要跟踪某个时刻,请使用上面蓝色块中显示的三个类之一:InstantOffsetDateTimeZonedDateTime

Instant phoneCallStartedUtc = Instant.now() ;  // A moment as seen in UTC.
ZonedDateTime phoneCallStartedCasablanca = phoneCallStartedUtc.atZone( ZoneId.of( "Africa/Casablanca" ) ) ;
ZonedDateTime phoneCallStartedTokyo = phoneCallStartedUtc.atZone( ZoneId.of( "Asia/Tokyo" ) ) ;

看到这个code run live at IdeOne.com。

phoneCallStartedUtc.toString(): 2021-03-28T20:33:58.695669Z
phoneCallStartedCasablanca.toString(): 2021-03-28T21:33:58.695669+01:00[Africa/Casablanca]
phoneCallStartedTokyo.toString(): 2021-03-29T05:33:58.695669+09:00[Asia/Tokyo]

所有这三个对象,phoneCallStartedUtcphoneCallStartedCasablancaphoneCallStartedTokyo,都代表同一个时刻。他们唯一的区别是通过不同的挂钟时间来查看(人们抬头看墙上的时钟时所看到的)。

不是一刻

未来的约会,例如餐厅何时开门,或患者下次看牙医的时间,无法跟踪 >.

我们可能知道我们打算餐厅开业的日期和时间,但我们无法知道那一刻。这是因为时区使用的时钟时间是由政客定义的。

世界各地的政治家都表现出改变其管辖范围内时区的时钟定义规则的倾向。他们将时钟作为一种政治声明,以将自己与邻近的司法管辖区区分开来,或者相反,以与邻居保持一致。政客们将他们的区域规则更改为manipulate financial markets。出于象征和后勤原因,他们在战争和占领时期移动时钟。政客们在加入时尚时会改变时钟,例如Daylight Saving Time (DST)。后来,当他们加入不同的时尚时,他们会再次调整时钟,例如全年都在 DST。

这里要吸取的教训是时区规则会发生变化,它们会以惊人的频率变化,而且变化很小甚至no forewarning。因此,例如,下周二的下午 3 点实际上可能不会在您期望的周三出现。下一个下午 3 点可能比现在预计的早一个小时,比您现在预计的晚半小时,没人知道。

要跟踪由时钟跟踪的未来约会,例如您的餐厅开业,请使用 LocalTime 表示一天中的时间,使用 LocalDate 表示日期。在适当的情况下,您可以将这两者组合成LocalDateTime 对象。单独跟踪预期的时区。然后在运行时需要计算事件的调度时,将时区应用到LocalDateTime 以获得ZonedDateTimeZonedDateTime 将决定一个时刻,但你不能存储那个时刻,因为政客可能会在你身上移动时钟,从你身下撕下地毯。

// Store these three values.
ZoneId zoneIdRome = ZoneId.of( "Europe/Rome" ) ;
LocalTime restaurantOpening = LocalTime.of( 9 , 30 ) ;  // 09:30 AM.
LocalDate nextTuesday = LocalDate.now( zoneIdRome ).with( TemporalAdjusters.next( DayOfWeek.TUESDAY ) ) ;

// Do NOT store this next value. Use this next value only on-the-fly at runtime.
ZonedDateTime momentWhenRestaurantIsExpectedToOpenNextTuesday = 
    ZonedDateTime.of( nextTuesday , restaurantOpening , zoneIdRome ) ;

看到code run live at IdeOne.com。

restaurantOpening.toString(): 09:30
nextTuesday.toString(): 2021-03-30
momentWhenRestaurantIsExpectedToOpenNextTuesday.toString(): 2021-03-30T09:30+02:00[Europe/Rome]

如果您想跟踪餐厅实际营业的时间,那么您将跟踪时刻。然后您将使用上面讨论的三个类之一,可能是Instant

Instant momentWhenRestaurantActuallyOpened = Instant.now() ;  // 09:41 AM, eleven minutes late because the manager could not find the door keys that had dropped to the floor.

顺便说一句,一些未来的约会不是由时钟驱动的。例如,rocket launches 是根据自然、预期的天气条件安排的。政客们可能会改变时钟,改变时区规则,但这不会改变火箭发射的时刻。政客们对时区的这种改变将改变媒体对计划发射的报道方式,但发射本身不会早晚发生。因此,对于火箭发射等,我们将使用上面讨论的三个面向时刻的类之一,几乎可以肯定Instant 类只关注 UTC 并完全避开所有时区。

这些概念和类都已经在 Stack Overflow 上多次讨论过。搜索以了解更多信息。

【讨论】:

是的,我知道Europe/Rome 是一个时区,这就是为什么我说“"Europe/Rome" 的偏移量”。除此之外……我无语了。你基本上写了我读过的关于现代 Java 时间处理的最佳指南。你的答案应该在教科书中。我永远不会感谢你的努力和令人难以置信的答案。我会立即将您的答案标记为已接受。

以上是关于当时间需要在本地时间保持不变时,在 Java 中使用 OffsetDateTime 处理夏令时,而不管 DST的主要内容,如果未能解决你的问题,请参考以下文章

java重定向时如何保持地址栏不变

如何在objective-c中使对象在整个应用程序中保持活动状态

当用户在 Mozilla Firefox 上放大和缩小页面时,如何保持 <div> 的大小不变

CSS/HTML 使文本相对于图像大小保持不变?

在 docker 中使 node_modules 保持最新

微调器:当所选项目保持不变时,不调用 onItemSelected