Java SE 8 TemporalAccessor.from与java.time.Instant对象一起使用时出现问题

时间:2014-05-13 21:24:31

标签: java java-8 java-time

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)(可能)生成UTC ZoneId
  • ZonedDateTime.from(anInstant)(可能)使用UTC生成ZonedDateTime ZoneId

我读到的ZonedDateTime.from文档似乎支持这一点。

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

这是预期的行为吗?

4 个答案:

答案 0 :(得分:40)

简答:

JSR-310-设计师不希望人们通过静态from() - 类似ZoneIdZoneOffset,{{1}等类型的方法在机器时间和人工时间之间进行转换}},OffsetDateTime等。如果您仔细研究javadoc,则会明确指定。而是使用:

ZonedDateTime

静态from() - 方法的问题在于,人们可以在OffsetDateTime#toInstant():Instant ZonedDateTime#toInstant():Instant Instant#atOffset(ZoneOffset):OffsetDateTime Instant#atZone(ZoneId):ZonedDateTime 之间进行转换,例如Instant,而无需考虑时区。

答案很长:

是否将LocalDateTime视为计数器或字段元组,JSR-310团队给出的答案是所谓的机器时间与人类时间之间的严格分离。事实上,他们打算严格分离 - 见guidelines。所以最后他们希望Instant只被解释为机器时间计数器。所以他们故意设计一个你不能向Instant询问年,小时等字段的设计。

但实际上,JSR-310团队根本不一致。他们已经将方法Instant实现为字段元组视图,包括年份,...,小时,...和偏移符号Z(对于UTC时区)(脚注:在JSR-310之外,这很常见)在这样的机器时间上进行基于字段的查看 - 例如在维基百科或其他网站上查看TAI and UTC)。一旦规范引导S. Colebourne在对threeten-github-issue的评论中说:

"如果我们真的很难,那么Instant的toString就是1970-01-01Z的秒数。我们选择不这样做,并输出一个更友好的toString来帮助开发人员,但它并没有改变Instant只是一个秒数的基本事实,并且不能转换为年/月/日某种时区。"

人们可以喜欢这个设计决定(和我一样),但结果是你不能问Instant.toString()年,...,小时,......和偏移。另请参阅支持字段的文档:

Instant

这里有趣的是缺少的,首先缺少与区域相关的字段。 作为一个原因,我们经常听到NANO_OF_SECOND MICRO_OF_SECOND MILLI_OF_SECOND INSTANT_SECONDS Instant等对象没有时区的声明。在我看来,这是一个过于简单化的观点。虽然这些对象确实在内部没有时区状态(并且也不需要具有这样的内部值),但这些对象必须与UTC时区相关,因为这是每个时区偏移计算和转换为本地类型的基础。 。所以正确的答案是:java.util.Date是一个机器计数器,用于计算自时区UTC(每个规范)的UNIX纪元以来的秒和纳秒。最后一部分 - 与UTC区域的关系 - 没有得到JSR-310团队的明确规定,但他们无法否认。 设计师希望废除Instant的时区方面,因为它看起来与人类时间有关。然而,他们无法彻底废除它,因为这是任何内部的基本部分偏移计算。所以你对

的观察

"此外,InstantInstant.atZone(zone)都会产生与将Instant视为具有隐含的UTC时区/'零&#一致的值39;偏移"

是对的。

尽管Instant.atOffset(offset)可能会产生ZoneOffset.from(anInstant)可能非常直观,但它会抛出异常,因为它的from() - 方法会搜索不存在的OFFSET_SECONDS-field。由于同样的原因,JSR-310的设计者决定在规范中做到这一点,即让人们认为ZoneOffset.UTC与UTC时区正式无关,即"没有时区" (但在内部,他们必须在所有内部计算中接受这个基本事实!)。

出于同样的原因,InstantOffsetDateTime.from(anInstant)也会失败。

关于ZoneId.from(anInstant)我们read

"转换首先从时态对象获取ZoneId,必要时回退到ZoneOffset。然后,它将尝试获取Instant,如果需要,可以回退到LocalDateTime。结果将是ZoneId或ZoneOffset与Instant或LocalDateTime的组合。"

因此,由于相同的原因,此转化将再次失败,因为ZonedDateTime.from(anInstant)ZoneId都无法从ZoneOffset获得。异常消息显示为:

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

最后我们看到所有来自()方法的静态都会导致无法在人类时间和机器时间之间进行转换,即使这看起来很直观。在某些情况下,让我们说InstantLocalDate之间的转换是值得怀疑的。此行为已指定,但我预测您的问题不是此类问题,许多用户将继续感到困惑。

我认为真正的设计问题是:

a)人的时间和机器时间之间不应该有明显的分离。像Instant这样的时态对象应该更像两者。量子力学中的一个类比:你可以将电子视为粒子和波。

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

c)总之:我绝对与JSR-310团队分享了一个好主意,即避免在不指定时区的情况下避免本地类型和全局类型(如instant.getDate())之间的转换。我只是在API设计方面有所不同,以防止用户进行 timezone-less 转换。我首选的方法是限制from() - 方法,而不是说全局类型不应该与人工时间格式有任何关系,如calendar-date或wall-time或UTC-timezone-offset。

无论如何,由于保留了向后兼容性,机器时间和人类时间之间的这种(不相关的)分离设计现在已经完成,并且每个想要使用新的java.time-API的人都必须忍受它。

很抱歉得到一个很长的答案,但要解释所选择的JSR-310设计非常困难。

答案 1 :(得分:17)

Instant班没有时区。它会像在UTC区一样被打印出来,因为它必须在某个区域打印(你不希望它以蜱形式打印,不是吗?),但那不是它&# #39;时区 - 如以下示例所示:

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。由于我们没有这样的界面,因此这些方法在多个地方单独声明。

如果您有ZonedDateTime,则可以将其称为getOffset。如果您有一个OffsetDateTime,您也可以将其称为偏移量 - 但这些是两种不同的getOffset方法,因为这两个类没有从公共接口获取该方法。这意味着如果你有一个Temporal对象,你可能需要测试它们是否instanceof来获得偏移量。

OffsetDateTime.from通过为您完成此过程简化了过程 - 但由于它也不能依赖于公共接口,因此它必须接受任何Temporal对象并为那些对象抛出异常没有抵消。

答案 2 :(得分:9)

仅仅是w.r.t转换的一个例子,我相信有些人会得到以下异常

  

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

如果他们尝试 -

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

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

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

答案 3 :(得分:0)

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

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

请注意,由于时区肯定会发生变化,因此请注意多区域云服务器。