时差与时区
"Europe/Rome" 是时区的名称,而不是偏移量。
与 UTC 的偏移只是比 UTC 的基线提前或落后几个小时-分钟-秒。当您向东移动时,当地时间比 UTC 时间越来越早,而向西移动到美洲时,当地时间比 UTC 时间越来越晚。
时区要多得多。时区是特定地区的人们使用的偏移量的过去、现在和未来变化的历史记录。
以Continent/Region 的格式指定proper time zone name,例如America/Montreal、Africa/Casablanca 或Pacific/Auckland。切勿使用 2-4 个字母的缩写,例如 EST 或 IST,因为它们不是真正的时区,没有标准化,甚至不是唯一的(!)。
你说:
与日期和时间相关的所有内容都由OffsetDateTime 对象处理
不,你不应该在所有情况下都使用那个单一的类。 OffsetDateTime 类用于表示您知道偏移量但不知道时区的时刻。当您知道时区时,应使用ZonedDateTime。而对于特定于 UTC 的时刻(偏移量为零),通常最好使用 Instant。
OffsetDateTime 很少是合适的选择。时区总是比单纯的偏移更可取。所以通常你会想要ZonedDateTime 或Instant。一般来说,程序员和系统管理员应该在 UTC 中思考、工作、记录和调试,因此使用 Instant。要在预期时区或业务规则要求的情况下向用户展示,请使用ZonedDateTime。一个大的例外是JDBC 用于数据库访问——JDBC 4.2 specification 莫名其妙地需要兼容drivers 来支持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" ) ) ;
看到这个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。出于象征和后勤原因,他们在战争和占领时期移动时钟。政客们在加入时尚时会改变时钟,例如Daylight Saving Time (DST)。后来,当他们加入不同的时尚时,他们会再次调整时钟,例如全年都在 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]
如果您想跟踪餐厅实际营业的时间,那么您将跟踪时刻。然后您将使用上面讨论的三个类之一,可能是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 上多次讨论过。搜索以了解更多信息。