【问题标题】:OffsetDateTime fails with localized formatter using FormatStyle of LONG or FULLOffsetDateTime 使用 LONG 或 FULL 的 FormatStyle 本地化格式化程序失败
【发布时间】:2017-09-16 00:53:11
【问题描述】:

tl;博士

这失败了。

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

ZonedDateTime 中具有相同偏移量的同一时刻有效。

为什么?

详情

当让java.time 通过DateTimeFormatter.ofLocalizedDateTime 自动本地化OffsetDateTime 的字符串表示形式时,如果格式化程序带有FormatStyleSHORTMEDIUM,则调用format 有效。但是当格式化程序带有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 具有相同的时刻和相同的偏移量成功?为什么我需要从OffsetDateTimeZonedDateTime 进行这种转换?

OffsetDateTime 格式化失败的这种行为是错误还是功能?

我会提交一份错误报告,但我想确定我误解了什么。

【问题讨论】:

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


    【解决方案1】:

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

    使用FormatStyle.LONGFormatStyle.FULL seems 需要ZoneIdOffsetDateTime 没有

    请查看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}.
    

    您可以使用OffsetDateTimeZoneOffset 创建DateTimeFormatter

    DateTimeFormatter f = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG)
                                      .withZone(odt.getOffset());
    

    在这种情况下,OffsetDateTime 将在格式化之前转换为 ZonedDateTime

    【讨论】:

      【解决方案2】:

      调试代码,我发现格式化程序最终以this line结尾(grepcode的行与我的JDK安装的数字不完全相同,但代码是):

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

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

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

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

      后来,这个查询的结果是checked by a DateTimePrintContext

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

      因为resultnull,所以会抛出异常。


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

      // 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 模式,这意味着它对于大多数(如果不是全部)语言环境都会失败。


      没有直接关系,但是当您使用ZoneOffset 作为参数调用atZoneSameInstant 时,您可以直接调用odt.toZonedDateTime()

      【讨论】:

        猜你喜欢
        • 2021-03-10
        • 2022-01-15
        • 1970-01-01
        • 1970-01-01
        • 2020-10-30
        • 2016-12-10
        • 1970-01-01
        • 2010-09-05
        相关资源
        最近更新 更多