在 Java 中使用 OffsetDateTime 处理夏令时,无论夏令时如何,时间都需要在本地时间保持不变
Handling Daylight Saving Time with OffsetDateTime in Java when time needs to remain the same in local time regardless of DST
我正在使用 Spring Boot (Java 8) 开发餐厅应用程序的后端,该应用程序公开了一些 REST APIs。与日期和时间相关的所有内容都由 OffsetDateTime
对象处理,并通过我使用的应用程序处理,默认情况下,偏移量为 "Europe/Rome"
。这些对象也保存在数据库中。一切正常,除了...开始实行夏令时,现在餐厅的所有营业时间都延迟一小时。
这显然是因为夏令时之前 "Europe/Rome"
的偏移量是 +01:00
,而 post-DST 是 +02:00
。因此,举个例子,餐厅的营业时间(保存在 DST 之前的数据库中)是 08:30:00+01:00
,但是当从 post-DST 访问时它变成 09:30:00+02:00
=25=](因为是用系统默认的zone自动转换的)。我需要餐厅的营业时间与当地时间保持一致,无论夏令时如何。
我该如何处理这种情况并为我的用户提供一致的数据?
偏移量与时区
"Europe/Rome"
是时区名称,不是时差。
与 UTC 的偏移量只是提前或落后 UTC 基线的小时-分钟-秒数。当你向东移动时,当地时间越来越早于 UTC 小时,而向西移动到美洲,当地时间越来越晚于 UTC 小时。
时区更多。时区是特定地区的人们使用的偏移量的过去、现在和未来变化的历史。
以Continent/Region
的格式指定proper time zone name,例如America/Montreal
、Africa/Casablanca
或Pacific/Auckland
。切勿使用 EST
或 IST
等 2-4 字母缩写,因为它们 不是 真正的时区,不是标准化的,甚至不是唯一的(!)。
你说:
Everything related to dates and times is handled with OffsetDateTime
objects
不,您不应该在所有情况下都使用那个 class。 OffsetDateTime
class 表示您知道偏移量但不知道时区的时刻。 ZonedDateTime
应该在您知道时区时使用。对于特别是 UTC 的时刻(偏移量为零),通常最好使用 Instant
.
OffsetDateTime
很少是正确的class选择。时区总是优于单纯的偏移量。所以通常你会想要 ZonedDateTime
或 Instant
。一般来说,程序员和系统管理员应该在 UTC 中思考、工作、记录和调试,因此为此使用 Instant
。要在用户预期的时区或业务规则需要时向用户展示,请使用 ZonedDateTime
。一个很大的例外是 JDBC work for database access — the JDBC 4.2 specification inexplicably requires compliant drivers 支持 OffsetDateTime
但不支持 Instant
或 ZonedDateTime
。幸运的是 classes 之间的转换非常简单和容易。
时刻
您需要了解片刻和非片刻之间的区别。片刻是时间轴上的特定点。忘记时区和时差,如果摩洛哥卡萨布兰卡的某人打电话给日本东京的某人,他们正在共享同一时刻。要跟踪某个时刻,请使用上面蓝色块中显示的三个 classes 之一:Instant
、OffsetDateTime
、ZonedDateTime
.
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]
所有这三个对象,phoneCallStartedUtc
、phoneCallStartedCasablanca
和 phoneCallStartedTokyo
,代表了同一个时刻。他们唯一的区别是通过不同的挂钟时间查看(人们抬头看墙上的时钟时看到的内容)。
一刻也没有
未来的约会,例如餐厅何时开门,或患者下次看牙医的时间,不能被追踪为一个瞬间.
我们可能知道打算餐厅开张的日期和时间,但我们无法知道片刻。那是因为时区使用的时钟时间是由政治家定义的。
世界各地的政客都表现出了改变其辖区时区时钟定义规则的倾向。他们转动时钟作为一种政治声明,以将自己与邻近的司法管辖区区分开来,或者相反,以与邻国保持一致。政客们将他们的区域规则更改为 manipulate financial markets. They move the clock during times of war and occupation for symbolic as well as logistical reasons. Politicians move the clock when they join in fads, such as Daylight Saving Time (DST)。后来,当他们加入不同的时尚时,他们会再次调整他们的时钟,例如全年保持夏令时。
这里要吸取的教训是时区规则会发生变化,它们以惊人的频率变化,而且变化很小甚至 no forewarning。因此,例如,下周二下午 3 点实际上可能不会出现在您预期的周三此时。下一个下午3点可能比现在预计早一个小时,比现在预计晚半小时,谁也不知道。
要跟踪由时钟跟踪的未来约会,例如您的餐厅开业,请使用 LocalTime
作为一天中的时间,并使用 LocalDate
作为日期。在适当的情况下,您可以将这两者组合成 LocalDateTime
对象。单独跟踪预期的时区。然后在运行时需要计算事件的调度时,将时区应用到 LocalDateTime
得到一个 ZonedDateTime
。 ZonedDateTime
将决定一个时刻,但你无法存储那个时刻,因为政客们可能会在你身上移动时钟,撕掉你脚下的地毯。
// 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]
如果您想跟踪餐厅实际营业的时间,那么您就是在跟踪时刻。那么您将使用上面讨论的三个 classes 之一,可能 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 是根据自然、预期的天气状况安排的。政客们可以改变时钟,改变时区规则,但这不会改变火箭发射的时刻。政客们对时区的这种改变将改变媒体对计划发射的报道方式,但发射本身不会提前或推迟。因此,对于火箭发射等,我们将使用上面讨论的三个面向时刻的 classes 之一,几乎可以肯定 Instant
class 仅关注 UTC 并完全避开所有时区。
这些概念和 classes 已经在 Stack Overflow 上多次讨论过。搜索以了解更多信息。
我正在使用 Spring Boot (Java 8) 开发餐厅应用程序的后端,该应用程序公开了一些 REST APIs。与日期和时间相关的所有内容都由 OffsetDateTime
对象处理,并通过我使用的应用程序处理,默认情况下,偏移量为 "Europe/Rome"
。这些对象也保存在数据库中。一切正常,除了...开始实行夏令时,现在餐厅的所有营业时间都延迟一小时。
这显然是因为夏令时之前 "Europe/Rome"
的偏移量是 +01:00
,而 post-DST 是 +02:00
。因此,举个例子,餐厅的营业时间(保存在 DST 之前的数据库中)是 08:30:00+01:00
,但是当从 post-DST 访问时它变成 09:30:00+02:00
=25=](因为是用系统默认的zone自动转换的)。我需要餐厅的营业时间与当地时间保持一致,无论夏令时如何。
我该如何处理这种情况并为我的用户提供一致的数据?
偏移量与时区
"Europe/Rome"
是时区名称,不是时差。
与 UTC 的偏移量只是提前或落后 UTC 基线的小时-分钟-秒数。当你向东移动时,当地时间越来越早于 UTC 小时,而向西移动到美洲,当地时间越来越晚于 UTC 小时。
时区更多。时区是特定地区的人们使用的偏移量的过去、现在和未来变化的历史。
以Continent/Region
的格式指定proper time zone name,例如America/Montreal
、Africa/Casablanca
或Pacific/Auckland
。切勿使用 EST
或 IST
等 2-4 字母缩写,因为它们 不是 真正的时区,不是标准化的,甚至不是唯一的(!)。
你说:
Everything related to dates and times is handled with
OffsetDateTime
objects
不,您不应该在所有情况下都使用那个 class。 OffsetDateTime
class 表示您知道偏移量但不知道时区的时刻。 ZonedDateTime
应该在您知道时区时使用。对于特别是 UTC 的时刻(偏移量为零),通常最好使用 Instant
.
OffsetDateTime
很少是正确的class选择。时区总是优于单纯的偏移量。所以通常你会想要 ZonedDateTime
或 Instant
。一般来说,程序员和系统管理员应该在 UTC 中思考、工作、记录和调试,因此为此使用 Instant
。要在用户预期的时区或业务规则需要时向用户展示,请使用 ZonedDateTime
。一个很大的例外是 JDBC work for database access — the JDBC 4.2 specification inexplicably requires compliant drivers 支持 OffsetDateTime
但不支持 Instant
或 ZonedDateTime
。幸运的是 classes 之间的转换非常简单和容易。
时刻
您需要了解片刻和非片刻之间的区别。片刻是时间轴上的特定点。忘记时区和时差,如果摩洛哥卡萨布兰卡的某人打电话给日本东京的某人,他们正在共享同一时刻。要跟踪某个时刻,请使用上面蓝色块中显示的三个 classes 之一:Instant
、OffsetDateTime
、ZonedDateTime
.
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]
所有这三个对象,phoneCallStartedUtc
、phoneCallStartedCasablanca
和 phoneCallStartedTokyo
,代表了同一个时刻。他们唯一的区别是通过不同的挂钟时间查看(人们抬头看墙上的时钟时看到的内容)。
一刻也没有
未来的约会,例如餐厅何时开门,或患者下次看牙医的时间,不能被追踪为一个瞬间.
我们可能知道打算餐厅开张的日期和时间,但我们无法知道片刻。那是因为时区使用的时钟时间是由政治家定义的。
世界各地的政客都表现出了改变其辖区时区时钟定义规则的倾向。他们转动时钟作为一种政治声明,以将自己与邻近的司法管辖区区分开来,或者相反,以与邻国保持一致。政客们将他们的区域规则更改为 manipulate financial markets. They move the clock during times of war and occupation for symbolic as well as logistical reasons. Politicians move the clock when they join in fads, such as Daylight Saving Time (DST)。后来,当他们加入不同的时尚时,他们会再次调整他们的时钟,例如全年保持夏令时。
这里要吸取的教训是时区规则会发生变化,它们以惊人的频率变化,而且变化很小甚至 no forewarning。因此,例如,下周二下午 3 点实际上可能不会出现在您预期的周三此时。下一个下午3点可能比现在预计早一个小时,比现在预计晚半小时,谁也不知道。
要跟踪由时钟跟踪的未来约会,例如您的餐厅开业,请使用 LocalTime
作为一天中的时间,并使用 LocalDate
作为日期。在适当的情况下,您可以将这两者组合成 LocalDateTime
对象。单独跟踪预期的时区。然后在运行时需要计算事件的调度时,将时区应用到 LocalDateTime
得到一个 ZonedDateTime
。 ZonedDateTime
将决定一个时刻,但你无法存储那个时刻,因为政客们可能会在你身上移动时钟,撕掉你脚下的地毯。
// 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]
如果您想跟踪餐厅实际营业的时间,那么您就是在跟踪时刻。那么您将使用上面讨论的三个 classes 之一,可能 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 是根据自然、预期的天气状况安排的。政客们可以改变时钟,改变时区规则,但这不会改变火箭发射的时刻。政客们对时区的这种改变将改变媒体对计划发射的报道方式,但发射本身不会提前或推迟。因此,对于火箭发射等,我们将使用上面讨论的三个面向时刻的 classes 之一,几乎可以肯定 Instant
class 仅关注 UTC 并完全避开所有时区。
这些概念和 classes 已经在 Stack Overflow 上多次讨论过。搜索以了解更多信息。