使用FormatStyle为LONG或FULL时,OffsetDateTime使用本地化格式化程序失败

时间:2017-09-16 00:53:12

标签: java date-formatting java-time timezone-offset

TL;博士

这失败了。

OffsetDateTime.now()
              .format( 
                  DateTimeFormatter.ofLocalizedDateTime( FormatStyle.LONG )
              )  // throws DateTimeException.

ZonedDateTime中同一偏移的同一时刻起作用。

为什么?

详细

java.time通过OffsetDateTime自动本地化DateTimeFormatter.ofLocalizedDateTime的字符串表示形式时,如果格式化程序带有format SHORT,则调用FormatStyle会有效}或MEDIUM。但是当格式化程序带有LONGFULL时,会抛出DateTimeException。然而ZonedDateTime成功地使用相同的时刻offset。为什么呢?

DateTimeFormatter f = DateTimeFormatter.ofLocalizedDateTime( FormatStyle.LONG ) ;

OffsetDateTime odt = OffsetDateTime.now( ZoneId.systemDefault() ) ;
ZonedDateTime zdt = odt.atZoneSameInstant( odt.getOffset() ) ;  // Generate a `ZonedDateTime` with same moment and same offset as the `OffsetDateTime`.

// Succeeds.
String outputZdt = zdt.format( f ) ;
System.out.println( "outputZdt: " + outputZdt ) ;

// Fails. Throws exception.
if ( false ) {
String outputOdt = odt.format( f ) ;  // Throws exception.
System.out.println( "outputOdt: " + outputOdt ) ;
} 

请参阅此code run live at IdeOne.com

跑步时......

好的。

  

outputZdt:2017年9月16日上午8:42:14 Z

坏。

Exception in thread "main" java.time.DateTimeException: Unable to extract value: class java.time.OffsetDateTime
    at java.time.format.DateTimePrintContext.getValue(DateTimePrintContext.java:282)
    at java.time.format.DateTimeFormatterBuilder$ZoneTextPrinterParser.format(DateTimeFormatterBuilder.java:3682)
    at java.time.format.DateTimeFormatterBuilder$CompositePrinterParser.format(DateTimeFormatterBuilder.java:2179)
    at java.time.format.DateTimeFormatterBuilder$LocalizedPrinterParser.format(DateTimeFormatterBuilder.java:4347)
    at java.time.format.DateTimeFormatterBuilder$CompositePrinterParser.format(DateTimeFormatterBuilder.java:2179)
    at java.time.format.DateTimeFormatter.formatTo(DateTimeFormatter.java:1746)
    at java.time.format.DateTimeFormatter.format(DateTimeFormatter.java:1720)
    at java.time.OffsetDateTime.format(OffsetDateTime.java:1674)
    at Ideone.main(Main.java:28)

我编写了该代码的核心来解决抛出的异常odt.atZoneSameInstant( odt.getOffset() )。然后我意识到,为什么java.time内部没有做同样的事情?为什么OffsetDateTime无法格式化具有相同时刻和相同偏移的ZonedDateTime成功的位置?我为什么需要从OffsetDateTime转换为ZonedDateTime

{OffsetDateTime格式化的这种行为是不是一个错误或一个功能?

我会提交错误报告,但我想确保我误解了一些内容。

2 个答案:

答案 0 :(得分:4)

看起来Javadoc错误报告了here。在提供的示例中,他们使用LocalDateTime,但行为是相同的。

使用FormatStyle.LONGFormatStyle.FULL seems要求ZoneId OffsetDateTime没有<{p}}

  

请查看java.time javadoc改进以突出显示常见内容   关于格式化需要时区的元素的误解   除了时间。

     

使用特定于语言环境的格式时,如果使用语言环境,则可能会有效   格式化不需要时区或如果语言环境失败   格式化需要时区,并且不提供时区。

这就是they clarified the javadoc提到

的原因
* The {@code FULL} and {@code LONG} styles typically require a time-zone.
* When formatting using these styles, a {@code ZoneId} must be available,
* either by using {@code ZonedDateTime} or {@link DateTimeFormatter#withZone}.

您可以使用DateTimeFormatter的{​​{1}}创建OffsetDateTime

ZoneOffset

在这种情况下,DateTimeFormatter f = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG) .withZone(odt.getOffset()); 将在格式化之前转换为OffsetDateTime

答案 1 :(得分:1)

调试代码,我发现格式化程序最终在this line中(grepcode的行与我的JDK安装的编号不完全相同,但代码是):

ZoneId zone = context.getValue(TemporalQueries.zoneId()); 

它尝试使用内置查询TemporalQueries.zoneId()提取区域。根据{{​​3}},如果时态对象是null,则此查询返回OffsetDateTime

  

因此ZonedDateTime将返回getZone()的结果,但OffsetDateTime将返回null。

您可以通过致电odt.query(TemporalQueries.zoneId())来确认这一点 - 它确实会返回null

稍后,此查询的结果为javadoc

R result = temporal.query(query);
if (result == null && optional == 0) {
    throw new DateTimeException("Unable to extract value: " + temporal.getClass());
}

由于resultnull,它会抛出异常。

实际上,尝试从z获取区域名称(模式OffsetDateTime)会引发异常:

// java.time.DateTimeException: Unable to extract value: class java.time.OffsetDateTime
DateTimeFormatter.ofPattern("z").format(OffsetDateTime.now());

因为这种模式最终出现在上述问题中。

使用getLocalizedDateTimePattern

检查所有区域设置的日期样式
// did this for all locales
DateTimeFormatterBuilder.getLocalizedDateTimePattern(FormatStyle.LONG, FormatStyle.LONG,
    IsoChronology.INSTANCE, locale);

我没有全部检查,但大多数都使用小写z模式,这意味着它对大多数(如果不是全部)语言环境都会失败。

没有直接关联,但是当您使用atZoneSameInstant作为参数调用ZoneOffset时,您只需拨打odt.toZonedDateTime()即可。