java 的 ZoneOffset 与 ZoneId

关于时区常见的问题:如何在java8及更高版本中获取默认的ZoneOffset?

tl;dr

OffsetDateTime.now().getOffset()

但是,建议使用时区(ZoneId) 而不是UTC的偏移量 (ZoneOffset)。

偏移量 VS 时区

一个UTC偏移量 offset-from-UTC 仅仅只记录了时分秒而已,除此之外没有任何其他信息。举个例子 ,+08:00的意思时超前于UTC八个小时,而 -05:45 意思是落后于UTC五小时四十五分钟。

而时区对于特定地区的人来说是过去,现在,未来的偏移量的历史集合。像夏令时 这样的异常会导致特定时间段内的偏移量会随时间变化,无论是过去已经发生的还是 未来政客们宣布计划的改变。

所以,当你了解时最好还是使用时区。

很多地区的偏移量都会随时间变化。比如,美国的夏令时 会导致约半年左右的时间内都会再原来的基础上偏移一个小时,然后再下半年再调整回这一个小时的偏移量。时区的意义就是记录这些所有的会造成偏移的情况。

因此,没有结合日期计算的偏移量没有意义的。举个例子,法国时间 (Europe/Paris),在一年的一小部分时间内偏移量为+01:00,而由于夏令时的缘故,大部分时间(三月末到十月末)的偏移量都是+02:00

// 指定区域,构造时间: 法国巴黎时间的当前时间,五月有DST,偏移量+2
LocalDateTime now = LocalDateTime.of(LocalDate.of(2020, 5, 31), LocalTime.now())
ZonedDateTime zonedDateTime = ZonedDateTime.of(now, ZoneId.of("Europe/Paris"));
zonedDateTime.getOffset(); // +02:00

// 手动调整的二月份,此时没有DST,偏移量是+1
ZonedDateTime thirdMonth = zonedDateTime.minusMonths(3);
thirdMonth.getOffset(); // +01:00

OffsetDateTime

让我们来定义一个OffsetDateTime,然后获取其中的 ZoneOffset.

OffsetDateTime odt = OffsetDateTime.now();
ZoneOffset zoneOffset = odt.getOffset();

odt.toString(); // 2020-05-31T23:59:24.753+08:00
zoneOffset.toString(); // +08:00

now方法其实隐含应用了jvm当前的默认时区。我的建议是 应该永远显式的指定你需要的时区,即便你就是要获取当前的默认时区。这么做的目的就是明确代码的意图,从而消除模棱两可的不确定性,比如代码到底是要获取默认时区还是写代码的人忘记考虑了时区的因素,这种情况在代码里是经常发生的。默认时区可以通过调用 ZoneId.systemDefault获取。

OffsetDateTime odt = OffsetDateTime.now(ZoneId.systemDefault());
ZoneOffset zoneOffset = odt.getOffset();

ZoneId.systemDefault().toString(); // Asia/Shanghai
odt.toString(); // 2020-05-31T23:59:24.753+08:00
zoneOffset.toString(); // +08:00

注意,如果代码依赖了jvm的默认时区则需要小心,因为jvm中的任何一个线程都可以随时更改时区。如果时区对于代码十分重要,建议保存用户的时区到Session或者持久化存储,以便代码行为的统一性。

同时,你也可以通过偏移量获取以秒为单位的偏移秒数。

int offsetSeconds = zoneOffset.getTotalSeconds();
offsetSeconds; // 28800

ZonedDateTime

另外一个例子:也许你想知道今年圣诞节的时候的Québec(魁北克)偏移量是多少。定义一个America/Montreal的时区,获取ZonedDateTime,然后通过ZonedDateTime获取对应的UTC偏移对象ZoneOffset.

ZoneId z = ZoneId.of( "America/Montreal" );
LocalDate ld = LocalDate.of(2020, 12, 25);
ZonedDateTime zdtXmas = ld.atStartOfDay(z);
ZoneOffset zoneOffsetXmas = zdtXmas.getOffset();

zdtXmas.toString(); // 2020-12-25T00:00-05:00[America/Montreal]
zoneOffsetXmas.toString(); // -05:00
zoneOffsetXmas.getTotalSeconds(); // -18000

ZoneId

正如yanys在评论中所提到的,你可以通过给ZoneId传递一个时刻Instant来获取对应的偏移量。 Instant 类代表UTC的时间轴上的一个时刻,可以精确到纳秒级别(nanoseconds,可以达到小数点后九位)。

这是获取偏移量的另外一种方式。就像在OffsetDateTimeZonedDateTime中讨论的一样,我们可以定义一个时区,然后通过一个时刻来获取偏移量。

Instant instant = zdtXmas.toInstant();
ZoneOffset zo = z.getRules().getOffset( instant );

For ZoneId: America/Montreal at instant: 2020-12-25T05:00:00Z the ZoneOffset is: -05:00

点击查看完整的演示: code live at IdeOne.com.

ZoneOffset.systemDefault – Bug还是特性?

ZoneOffset 类, 是 ZoneId的一个子类, 通过继承,拥有父类的 systemDefault 方法。然而,实际上它并不能按照预期工作.

 // 编译失败
ZoneOffset zoneOffset = ZoneOffset.systemDefault();

error: incompatible types: ZoneId cannot be converted to ZoneOffset

不确定这个编译失败是bug还是特性。如同上文所提到的,脱离日期时间获取默认的偏移量没有意义。所以也许 ZoneOffset.systemDefault 确实需要失败,但是也应该在文档中提到或者提供详细的解释。

我尝试提交一个关于这个问题的bug,最后还是放弃了,因为我不知道在哪里如何提交这样一个bug报告。

本文中的第一人称"我"指的是原作者,也就是原贴的回答。点击查看原帖:How to get default ZoneOffset in java8?

你可能感兴趣的:(java 的 ZoneOffset 与 ZoneId)