简答:
JSR-310 设计者不希望人们通过 ZoneId、ZoneOffset、OffsetDateTime、ZonedDateTime 等类型的静态 from() 方法在机器时间和人类时间之间进行转换。如果您仔细研究 javadoc,则会明确指定这一点。而是使用:
OffsetDateTime#toInstant():Instant
ZonedDateTime#toInstant():Instant
Instant#atOffset(ZoneOffset):OffsetDateTime
Instant#atZone(ZoneId):ZonedDateTime
静态 from() 方法的问题在于,否则人们可以在 Instant 和 LocalDateTime 之间进行转换,而无需考虑时区。
长答案:
无论将Instant 视为计数器还是字段元组,JSR-310-team 给出的答案是严格区分所谓的机器时间和人工时间。事实上,他们打算进行严格的分离——请参阅他们的guidelines。所以最后他们希望Instant 仅被解释为机器时间计数器。所以他们故意设计了一个你不能向Instant询问年份、小时等字段的设计。
但实际上,JSR-310 团队并不一致。他们已经将方法Instant.toString() 实现为字段元组视图,包括年、...、小时、...和偏移符号 Z(对于 UTC 时区)(脚注:在 JSR-310 之外,这很常见对此类机器时间的基于字段的外观 - 例如参见 Wikipedia 或其他网站上关于 TAI and UTC)。一旦规范负责人 S. Colebourne 在threeten-github-issue 的评论中说:
“如果我们真的很严格,那么 Instant 的 toString 就是从 1970-01-01Z 开始的秒数。我们选择不这样做,并输出一个更友好的 toString 来帮助开发人员,但这并没有改变 Instant 只是秒数的基本事实,并且在没有某种时区的情况下无法转换为年/月/日。”
人们可以喜欢或不喜欢这个设计决定(比如我),但结果是你不能向Instant 询问年份、...、小时、...和偏移量。另请参阅支持字段的文档:
NANO_OF_SECOND
MICRO_OF_SECOND
MILLI_OF_SECOND
INSTANT_SECONDS
有趣的是这里缺少什么,最重要的是缺少一个与区域相关的字段。 作为一个原因,我们经常听到像Instant 或java.util.Date 这样的对象没有时区的说法。 在我看来,这是一种过于简单化的观点。虽然这些对象在内部确实没有时区状态(也不需要具有这样的内部值),但这些对象必须与 UTC 时区相关,因为这是每个时区偏移计算和转换为本地类型的基础.所以正确的答案是:Instant 是一个机器计数器,计算自 UNIX 纪元以来在时区 UTC 中的秒数和纳秒数(根据规范)。最后一部分 - 与 UTC 区域的关系 - JSR-310-team 没有很好地指定,但他们不能否认。 设计者希望从Instant 中取消时区方面,因为它看起来与人类时间相关。 但是,他们不能完全取消它,因为这是任何内部偏移计算的基本部分。所以你对
的观察
“同样,Instant.atZone(zone) 和 Instant.atOffset(offset) 都会产生一个值,该值与将 Instant 视为具有隐含的 UTC 时区/‘零’偏移量是一致的。”
是对的。
虽然ZoneOffset.from(anInstant) 可能会产生ZoneOffset.UTC 可能非常直观,但它会引发异常,因为它的 from() 方法搜索不存在的OFFSET_SECONDS-field。 JSR-310 的设计者出于同样的原因决定在规范中这样做,即让人们认为Instant 与 UTC 时区正式无关,即“没有时区”(但在内部他们必须接受这一点所有内部计算的基本事实!)。
出于同样的原因,OffsetDateTime.from(anInstant) 和 ZoneId.from(anInstant) 也失败了。
关于ZonedDateTime.from(anInstant)我们read:
“转换将首先从时间对象获取 ZoneId,如有必要,将回退到 ZoneOffset。然后它将尝试获取 Instant,如有必要,将回退到 LocalDateTime。结果将是ZoneId 或 ZoneOffset 与 Instant 或 LocalDateTime 的组合。”
因此,由于同样的原因,此转换将再次失败,因为无法从 Instant 获得 ZoneId 和 ZoneOffset。异常消息内容如下:
“无法从 TemporalAccessor 获取 ZoneId:1970-01-01T00:00:00Z of type java.time.Instant”
最后,我们看到所有静态 from() 方法都无法在人类时间和机器时间之间进行转换,即使这看起来很直观。在某些情况下,假设LocalDate 和Instant 之间的转换是有问题的。指定了这种行为,但我预测您的问题不会是此类问题的最后一个问题,许多用户将继续感到困惑。
我认为真正的设计问题是:
a) 人类时间和机器时间之间不应有明显的区别。像Instant 这样的时间对象应该更好地表现得像两者一样。量子力学中的一个类比:您可以将电子视为粒子和波。
b) 所有静态 from() 方法都太公开了。在我看来,Ihat 太容易访问了,最好从公共 API 中删除或使用比TemporalAccessor 更具体的参数。这些方法的缺点是人们可能会忘记在此类转换中考虑相关的时区,因为他们以本地类型开始查询。例如:LocalDate.from(anInstant)(在哪个时区???)。但是,如果您直接向 Instant 询问其日期,例如 instant.getDate(),我个人会认为 UTC 时区中的日期是有效答案,因为这里的查询是从 UTC 角度开始的。
c) 总之:我绝对与 JSR-310 团队分享避免在不指定时区的情况下在本地类型和全局类型(如 Instant)之间进行转换的好主意。我只是在 API 设计上有所不同,以防止用户进行这种 timezone-less 转换。我更喜欢的方法是限制 from() 方法,而不是说全局类型不应该与日历日期或墙壁时间或 UTC-timezone-offset 等人类时间格式有任何关系。
无论如何,由于保留了向后兼容性,这种(无关紧要的)机器时间和人类时间分离设计现在已经确定下来,每个想要使用新 java.time-API 的人都必须接受它。
抱歉,回答太长了,但很难解释 JSR-310 的选择设计。