使用ZoneId.of("UTC")而不是ZoneOffset.UTC有什么理由吗?

17

使用ZoneId.of("UTC")而非ZoneOffset.UTC是否有任何理由?

我们了解两者之间的区别,可以参考什么是ZoneOffset.UTC和ZoneId.of("UTC")之间的区别?

<tl;dr>版本:

  • ZoneOffset.UTC返回一个ID为“Z”,偏移为0且默认区域规则的简单ZoneOffset
  • ZoneId.of("UTC")返回一个ID为“UTC”、包含ZoneOffset.UTCZoneRegion

</tl;dr>

对于这个问题,我假设只是为了更容易地处理日期和时间而将时区设置为UTC,而不是因为某些实际业务原因需要将其作为实际的ZoneRegion。

例如,在处理ZonedDateTime时,我发现唯一的区别是打印输出的格式不同。

2021-06-10T15:28:25.000000111Z
2021-06-10T15:28:25.000000111Z[UTC]

我们正在进行代码审查讨论,所以我想这种冲突并不罕见。

以下是我的思考

ZoneOffset.UTC

  • 它是一个常量(而且进一步地,它的偏移值(0)甚至被缓存了)。
  • 由于缺少区域信息,它具有较少的开销。
  • 在UTC中,没有夏令时或历史偏移等要考虑的情况,就像在任何其他时区一样。因此,对于我到目前为止遇到的所有用例(例如将特定区域区域的某个夏令时状态与特定日光节约状态转换),0的偏移量通常足够了。

ZoneId.of("UTC")

  • 总的来说,由于一系列额外的位置特定数据(如特定的夏令时或历史时间偏移),ZoneRegions优先于ZoneOffsets。
  • 然而,在UTC的情况下,ZoneId.of("UTC")只是一个围绕ZoneOffset.UTC的区域包装器,我到目前为止找不到任何好处。据我所知,在UTC中,除了继承的ZoneOffset.UTC规则之外,不存在任何区域相关数据。
  • 需要每次解析。

因此,我的_猜测_是:

除非您出于某种(例如业务)原因真正需要UTC时区/区域,否则应优先考虑ZoneOffset.UTC。它在性能上具有(轻微的)优势,并且UTC ZoneRegion似乎没有提供我可以看到或想到的任何好处。

然而,由于Java日期时间API的复杂性,很难确定我在这个讨论中是否遗漏了什么。
有没有任何理由使某人使用ZoneId.of("UTC")


3
我同意你的猜测。 - Ole V.V.
2个回答

5

我可以想到一种情况,在这种情况下使用 ZoneId.of("UTC") 可能比 ZoneOffset.UTC 更可取。如果您使用 jackson-modules-java8 从 JSON 反序列化 ZonedDateTime,则会得到带有 ZoneId 的日期。

例如,假设我们有以下 POJO(为简洁起见省略了 getter/setter):

class Example {
    private ZonedDateTime date = ZonedDateTime.now(ZoneOffset.UTC).truncatedTo(ChronoUnit.DAYS);
}

如果打印类的实例,您会得到:
Example example1 = new Example();
System.out.println(example1);

// output:
// Example(date=2022-06-17T00:00Z) - as expected

当您反序列化包含上述字符串 2022-06-17T00:00Z 的JSON时会发生什么?
String json = "{\"date\":\"2022-06-17T00:00Z\"}";
Example example2 = mapper.readValue(json, Example.class);
System.out.println(example2);
// output:
// Example(date=2022-06-17T00:00Z[UTC]) // now with ZoneId(!)

现在我们有了两个变体,一个是使用ZoneOffset,另一个是使用ZoneId:

  • 2022-06-17T00:00Z
  • 2022-06-17T00:00Z[UTC]

它们是不相等的。例如:

Instant instant = Instant.now();
ZonedDateTime z1 = ZonedDateTime.ofInstant(instant, ZoneOffset.UTC);
ZonedDateTime z2 = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC"));

System.out.println(z1.equals(z2)); // => false

作为结果,example1.equals(example2) 也会返回 false。这可能是微妙错误的源头。
注意:如果将 new Example() 序列化为JSON字符串并将其反序列化回对象,则会得到相同的结果。您将会得到一个带有 ZoneId 的日期。
使用 Jackson 2.13.1 进行测试。
编辑:我最终提交了一个关于此问题的错误报告:https://github.com/FasterXML/jackson-modules-java8/issues/244

2
这些信息非常有帮助。谢谢! - Vankog
我的两分钱:还有Etc/UTC,它是根据IANA注册表的规范名称UTC时区。根据此注册表,UTC和其他一些名称(目前为7个)只是指向此Etc/UTC区域的链接。因此,它们基本上是相同的时区。但是,ZoneId的实现方式将它们视为不同的。具有相同瞬间但使用不同UTC ZoneId变体的ZonedDateTime也将不同(不相等)。因此,不幸的是,这个问题比仅仅是ZoneOffset.UTCZoneId.of("UTC")之间的问题更广泛。 - Ruslan Stelmachenko

2

有没有任何理由让某人使用ZoneId.of("UTC")?

我现在想不到任何好的理由。

但是,这样做并没有太大的问题。执行查找可能会产生一些运行时成本,但这很可能不重要。而且这两种形式(我认为)同样易读。


但是,如果区域名称是参数,您可能会发现代码如下:

   String zoneName = "UTC";  // my default
   // Try to get a non-default zone
   // ...
   id = ZoneId.of(zoneName);

我认为下面的方式比这个更好:

   String zoneName = null;
   // Try to get a non-default zone
   // ...
   id = zoneName == null ? ZoneId.UTC : ZoneId.of(zoneName);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接