40

java.time有一个Instant类,它封装了时间轴上的位置(或“时刻”)。虽然我知道这是一个秒/纳秒值,因此与时区或时间偏移量没有直接关系,但它toString返回格式为 UTC 日期/时间的日期和时间,例如 2014-05-13T20:05:08.556Z。此外anInstant.atZone(zone)anInstant.atOffset(offset)两者都会产生一个与将Instant视为具有隐含 UTC 时区/“零”偏移量一致的值。

因此,我本来期望:

  • ZoneOffset.from(anInstant)产生一个“零”ZoneOffset
  • OffsetDateTime.from(anInstant)生成具有“零”偏移的日期/时间
  • ZoneId.from(anInstant)(可能)产生一个UTCZoneId
  • ZonedDateTime.from(anInstant)(可能)产生ZonedDateTime带有 UTC 的ZoneId

的文档ZonedDateTime.from,正如我所读的,似乎支持这一点。

事实上ZoneOffset.from(anInstant)失败了DateTimeException,我想因为这个原因OffsetDateTime.from(anInstant)也失败了,其他两个也是如此。

这是预期的行为吗?

4

4 回答 4

54

简短的回答:

JSR-310 设计者不希望人们通过静态 from() 方法在机器时间和人类时间之间进行转换,例如ZoneIdZoneOffset、等类型。如果您仔细研究 javadoc,则会明确指定这一点OffsetDateTimeZonedDateTime而是使用:

OffsetDateTime#toInstant():Instant
ZonedDateTime#toInstant():Instant
Instant#atOffset(ZoneOffset):OffsetDateTime
Instant#atZone(ZoneId):ZonedDateTime

静态 from() 方法的问题在于,否则人们可以在不考虑时区的情况下在 aInstant和例如 a之间进行转换。LocalDateTime

长答案:

无论是考虑Instant作为计数器还是作为字段元组,JSR-310-team 给出的答案是严格区分所谓的机器时间和人工时间。事实上,他们打算进行严格的分离——参见他们的指导方针。所以最后他们只想Instant被解释为机器时间计数器。所以他们故意设计了一个你不能询问Instant年份、小时等字段的设计。

但事实上,JSR-310 团队并不一致。他们已经将该方法实现Instant.toString()为字段元组视图,包括年、...、小时、...和偏移符号 Z(用于 UTC 时区)(脚注:在 JSR-310 之外,这很常见,有一个字段-基于此类机器时间的外观 - 例如参见 Wikipedia 或其他有关TAI 和 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 

有趣的是,这里缺少什么,首先是缺少与区域相关的字段。作为一个原因,我们经常听到对象喜欢Instantjava.util.Date没有时区的说法。在我看来,这是一种过于简单化的观点。虽然这些对象在内部确实没有时区状态(也不需要具有这样的内部值),但这些对象必须与 UTC 时区相关,因为这是每个时区偏移计算和转换为本地类型的基础. 所以正确的答案是:AnInstant是一个机器计数器,计算自 UNIX 纪元以来在时区 UTC 中的秒数和纳秒数(根据规范)。最后一部分 - 与 UTC 区域的关系 - JSR-310-team 没有很好地指定,但他们不能否认。设计师希望取消时区方面,Instant因为它看起来与人类时间相关。但是,他们不能完全取消它,因为这是任何内部偏移计算的基本部分。所以你的观察关于

“而且 anInstant.atZone(zone)和 anInstant.atOffset(offset)都产生一个与将 Instant 视为具有隐含 UTC 时区/'零'偏移量一致的值。”

是正确的。

虽然它可能非常直观,但它ZoneOffset.from(anInstant)ZoneOffset.UTC引发异常,因为它的 from() 方法搜索不存在的OFFSET_SECONDS 字段。JSR-310 的设计者出于同样的原因决定在规范中这样做,即让人们认为 anInstant与 UTC 时区正式无关,即“没有时区”(但在内部他们必须接受这个基本事实所有内部计算!)。

出于同样的原因,OffsetDateTime.from(anInstant)ZoneId.from(anInstant)失败了。

关于ZonedDateTime.from(anInstant)我们阅读

“转换将首先从时间对象获取 ZoneId,必要时回退到 ZoneOffset。然后它将尝试获取 Instant,必要时回退到 LocalDateTime。结果将是 ZoneId 或 ZoneOffset 与即时或本地日期时间。”

因此,由于相同的原因,此转换将再次失败,因为既不能ZoneId也不ZoneOffset能从Instant. 异常消息内容如下:

“无法从 TemporalAccessor 获取 ZoneId:1970-01-01T00:00:00Z 类型为 java.time.Instant”

最后,我们看到所有静态 from() 方法都无法在人类时间和机器时间之间进行转换,即使这看起来很直观。在某些情况下,假设LocalDate和之间的转换Instant是有问题的。此行为已指定,但我预测您的问题不是此类问题的最后一个问题,许多用户将继续感到困惑。

在我看来,真正的设计问题是:

a) 人力时间和机器时间之间不应有明显的区别。像这样的时间对象Instant应该更好地表现得像两者一样。量子力学中的一个类比:您可以将电子视为粒子和波。

b) 所有静态 from() 方法都太公开了。在我看来,Ihat 太容易访问了,最好从公共 API 中删除或使用比TemporalAccessor. 这些方法的缺点是人们可能会忘记在此类转换中考虑相关的时区,因为他们以本地类型开始查询。例如考虑:(LocalDate.from(anInstant)在哪个时区???)。但是,如果您直接询问Instant它的日期,例如instant.getDate(),我个人会认为 UTC-timezone 中的日期是有效答案,因为这里查询是从 UTC 角度开始的。

c) 结论:我绝对与 JSR-310 团队分享避免在Instant不指定时区的情况下在本地类型和全局类型之间进行转换的好主意。我只是在 API 设计方面有所不同,以防止用户进行这种无时区转换。我首选的方法是限制 from() 方法,而不是说全局类型不应该与日历日期或墙壁时间或 UTC 时区偏移等人类时间格式有任何关系。

无论如何,由于保留了向后兼容性,这种(无关紧要的)机器时间和人类时间分离的设计现在已经确定下来,每个想要使用新的 java.time-API 的人都必须接受它。

抱歉,回答很长,但是很难解释 JSR-310 的选择设计。

于 2014-05-14T14:02:44.017 回答
19

该类Instant没有时区。它像在 UTC 区域中一样打印,因为它必须在某个区域中打印(您不希望它以刻度打印,对吗?),但这不是它的时区 - 如下例所示:

Instant instant=Instant.now();
System.out.println(instant);//prints 2014-05-14T06:18:48.649Z
System.out.println(instant.atZone(ZoneId.of("UTC")));//prints 2014-05-14T06:18:48.649Z[UTC]

任何时间对象的签名ZoneOffset.fromOffsetDateTime.from接受,但它们对于某些类型会失败。java.time似乎没有具有时区或偏移量的时间接口。这样的接口可以声明getOffsetgetZone。由于我们没有这样的接口,因此这些方法在多个地方分别声明。

如果你有 aZonedDateTime你可以称它为getOffset. 如果你有一个,OffsetDateTime你也可以称它为偏移量——但这是两种不同getOffset的方法,因为这两个类没有从一个公共接口获取该方法。这意味着,如果您有一个Temporal对象可能是两者之一,则必须测试它们是否instanceof都是它们才能获得偏移量。

OffsetDateTime.from通过为您执行此操作来简化流程 - 但由于它也不能依赖通用接口,因此它必须接受任何Temporal对象并为那些没有偏移量的对象抛出异常。

于 2014-05-14T06:26:42.483 回答
13

只是一个示例 wrt 转换,我相信有些人会遇到异常

(java.time.DateTimeException: 无法从 TemporalAccessor 获取 LocalDateTime: 2014-10-24T18:22:09.800Z of type java.time.Instant)

如果他们尝试——

LocalDateTime localDateTime = LocalDateTime.from(new Date().toInstant());

要解决问题,请传入区域 -

LocalDateTime localDateTime = LocalDateTime.from(new Date().toInstant().atZone(ZoneId.of("UTC")));

于 2014-10-24T18:33:17.657 回答
0

从 Instant 获取 LocalDateTime 的关键是提供系统的 TimeZone:

LocalDateTime.ofInstant(myDate.toInstant(), ZoneId.systemDefault())

作为旁注,请注意多区域云服务器,因为时区肯定会改变。

于 2020-07-13T19:55:44.270 回答