spring—在java中处理带有offsetdatetime的夏令时,无论dst如何,时间都需要在本地时间保持不变

eqfvzcg8  于 2021-07-13  发布在  Java
关注(0)|答案(1)|浏览(387)

我正在使用springboot(java8)为一个餐馆应用程序开发后端,该应用程序公开了一些restapi。所有与日期和时间有关的事情都由 OffsetDateTime 对象,并通过我使用的应用程序,默认情况下,偏移 "Europe/Rome" . 这些对象也会持久化到数据库中。一切正常,除了。。。夏令时开始了,现在餐厅的所有营业时间都缩短了一个小时。
这显然是因为在dst之前 "Europe/Rome"+01:00 ,而后dst是 +02:00 . 所以,举个例子,餐厅的营业时间(保存在db pre dst中)是 08:30:00+01:00 ,但它变成了 09:30:00+02:00 从api访问post dst时(因为它会自动转换为系统的默认区域)。我需要餐厅的营业时间保持不变,在当地时间,无论夏令时。
如何处理这种情况并向用户提供一致的数据?

628mspwn

628mspwn1#

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

与utc的偏移量只是utc基准线之前或之后的小时分秒数。当你向东移动时,当地时间比utc提前了越来越多的小时,而向西移动到美洲时,当地时间比utc晚了越来越多的小时。
时区更重要。时区是过去、现在和将来某个特定地区的人们所使用的偏移量的历史。
以以下格式指定正确的时区名称 Continent/Region ,例如 America/Montreal , Africa/Casablanca ,或 Pacific/Auckland . 切勿使用2-4个字母的缩写,例如 EST 或者 IST 因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。
你说:
所有与日期和时间有关的事情都由 OffsetDateTime 物体
不,您不应该在所有情况下都使用这个类。 OffsetDateTime 类用于表示一个时刻,您知道偏移量,但不知道时区。 ZonedDateTime 应该在你知道时区的时候使用。对于utc(偏移量为零)的特定时刻,通常最好使用 Instant .
这个 OffsetDateTime 很少是正确的选择。时区总是比单纯的偏移更可取。所以通常你会想要 ZonedDateTime 或者 Instant . 一般来说,程序员和系统管理员应该在utc中思考、工作、日志记录和调试,所以对于这种用途 Instant . 对于在预期时区或业务规则需要的地方向用户演示,请使用 ZonedDateTime . 一个大的例外是jdbc用于数据库访问—JDBC4.2规范莫名其妙地需要兼容的驱动程序来支持 OffsetDateTime 但不是 Instant 也不是 ZonedDateTime . 幸运的是,类之间的转换非常简单和容易。

时刻

你需要理解片刻和非片刻的区别。一个时刻是时间线上的一个特定点。忘记时区和补偿,如果有人在卡萨布兰卡摩洛哥打电话给某人在东京日本,他们是共享同一时刻。要跟踪某个时刻,请使用上面以蓝色方框显示的三个类之一: 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" ) ) ;

请在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 ,表示同一时刻。他们唯一的区别是通过不同的挂钟时间(人们抬头看墙上的钟时所看到的)。

一刻也没有

未来的预约,比如餐馆什么时候开门,或者病人什么时候去看牙医,都不能作为一个时刻来追踪。
我们可能知道餐厅开张的日期和时间,但不知道具体时间。这是因为时区中使用的时钟时间是由政治家定义的。
世界各地的政治家都表现出了改变其管辖区时区时钟定义规则的倾向。他们把时钟作为一种政治声明来来回移动,以区别于邻近的司法管辖区,或者相反,与邻国保持一致。政客们改变他们的区域规则来操纵金融市场。在战争和占领时期,他们移动时钟是出于象征意义和后勤原因。当政客们加入到夏时制(dst)等时尚活动中时,他们就会调整时间。后来,当他们加入一种不同的时尚时,比如全年都呆在夏令时,他们会再次移动时钟。
这里要吸取的教训是,时区规则会发生变化,它们以惊人的频率发生变化,而且它们的变化很少甚至没有预先警告。因此,例如,下周二下午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 ) ;

看到代码在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.

顺便说一句,有些未来的约会不是由时间决定的。例如,火箭发射是根据自然、预期的天气条件来安排的。政客们可能会改变时钟,改变时区规则,但这不会改变火箭发射的时刻。政客们在时区上的这种改变将改变媒体对计划发射的报道方式,但发射本身不会早或晚。所以对于火箭发射,我们会使用上面讨论的三个面向力矩的类中的一个,几乎可以肯定 Instant 类只关注utc并完全避免所有时区。
这些概念和类在堆栈溢出时已经被多次处理过了。搜索以了解更多信息。

相关问题