java.time 是否无法解析秒的分数?错误 – 在 Java 9 中修复

2022-09-01 02:10:25

在 Mac OS X (Mavericks) 上发布了 Java 8 (b132) 的第一个版本,这段使用新的 java.time 包的代码可以正常工作:

String input = "20111203123456"; 
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmss");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );

渲染:

2011-12-03T12:34:56

但是,当我为秒的分数添加“SS”(以及“55”作为输入)时,如 DateTimeFormatter 类文档中所指定的那样,会引发异常:

java.time.format.DateTimeParseException: Text '2011120312345655' could not be parsed at index 0

该文档表示默认使用严格模式,并且需要与输入数字相同数量的格式字符。所以我很困惑为什么这个代码会失败:

String input = "2011120312345655"; 
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmssSS");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );

另一个使用文档示例的示例(“978”)(失败):

String input = "20111203123456978"; 
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmssSSS");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );

这个例子是有效的,添加了一个小数点(但我在文档中没有发现这样的要求):

String input = "20111203123456.978"; 
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmss.SSS");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );

呈现:

localDateTime: 2011-12-03T12:34:56.978

从输入字符串格式中省略句点字符会导致失败。

失败:

String input = "20111203123456.978"; 
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmssSSS");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );

失败:

String input = "20111203123456978"; 
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmss.SSS");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );

答案 1

错误 – 在 Java 9 中修复

此问题已在 JDK 错误日志中报告。Stephen Colebourne提到以下解决方案的解决方法:

DateTimeFormatter dtf = 
  new DateTimeFormatterBuilder()
  .appendPattern("yyyyMMddHHmmss")
  .appendValue(ChronoField.MILLI_OF_SECOND, 3)
  .toFormatter();

注意:此解决方法不涵盖仅两个模式符号 SS 的用例。调整可能仅使用其他字段,如MICRO_OF_SECOND(6 倍 SSSSSS)或NANO_OF_SECOND(9 倍 SSSSSSSS)。有关两个小数位数,请参阅下面的更新。

@PeterLawrey 关于图案符号“S”的含义,请参阅以下文档

分数:将秒的纳级字段输出为秒的小数部分。纳秒值有九位数字,因此图案字母的计数从1到9。如果它小于 9,则秒的纳秒值将被截断,仅输出最高有效位数。在严格模式下解析时,解析的位数必须与模式字母的计数匹配。在宽松模式下解析时,解析的位数必须至少为模式字母的计数,最多 9 位。

因此,我们看到S代表任何秒(包括纳秒)的分数,而不仅仅是毫秒。此外,不幸的是,分数部分目前在相邻值解析中效果不佳。

编辑:

作为背景,这里有一些关于相邻值解析的注释。只要字段由小数点或时间部分分隔符(冒号)等文本分隔,对要分析的文本中的字段的解释并不困难,因为解析器很容易知道何时停止,即字段部分何时结束以及下一个字段何时开始。因此,如果指定小数点,JSR-310 解析器可以处理文本序列。

但是,如果您有一系列跨越多个字段的相邻数字,那么就会出现一些实现困难。为了让解析器知道字段何时停止在文本中,有必要提前指示解析器给定字段由固定宽度的数字字符表示。这适用于所有假设数值表示的 -方法。appendValue(...)

不幸的是,JSR-310在分数部分()上也没有很好地做到这一点。如果您在类的javadoc中查找关键字“neighbor”,那么您会发现此功能只能通过-方法实现。请注意,模式字母 S 的规范略有不同,但在内部委托给 -method。我假设我们至少必须放弃Java 9(如JDK-bug-log中报告的那样,或更高版本???),直到分数部分也可以管理相邻值解析。appendFraction(...)DateTimeFormatterBuilderappendValue(...)appendFraction()


2015-11-25更新:

以下仅使用两个小数位数的代码不起作用,并且误解了毫秒部分:

    DateTimeFormatter dtf =
        new DateTimeFormatterBuilder()
            .appendPattern("yyyyMMddHHmmss")
            .appendValue(ChronoField.MILLI_OF_SECOND, 2)
            .toFormatter();
    String input = "2011120312345655";
    LocalDateTime ldt = LocalDateTime.parse(input, dtf);
    System.out.println(ldt); // 2011-12-03T12:34:56.055

解决方法

String input = "2011120312345655"; 
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSS");
Date d = sdf.parse(input);
System.out.println(d.toInstant()); // 2011-12-03T12:34:56.055Z

不起作用,因为以错误的方式解释分数,类似于现代示例(请参阅输出,55 ms而不是550 ms)。SimpleDateFormat

剩下的解决方案是等待很长时间才能等到Java 9(或更高版本?),或者编写自己的hack或使用第三方库作为解决方案。

基于肮脏黑客的解决方案:

String input = "2011120312345655"; 
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
int len = input.length();
LocalDateTime ldt = LocalDateTime.parse(input.substring(0, len - 2),  dtf);
int millis = Integer.parseInt(input.substring(len - 2)) * 10;
ldt = ldt.plus(millis, ChronoUnit.MILLIS);
System.out.println(ldt); // 2011-12-03T12:34:56.550

使用Joda-Time的解决方案

String input = "2011120312345655"; 
DateTimeFormatter dtf = DateTimeFormat.forPattern("yyyyMMddHHmmssSS");
System.out.println(dtf.parseLocalDateTime(input)); // 2011-12-03T12:34:56.550

使用我的库 Time4J 的解决方案

String input = "2011120312345655"; 
ChronoFormatter<PlainTimestamp> f = 
  ChronoFormatter.ofTimestampPattern("yyyyMMddHHmmssSS", PatternType.CLDR, Locale.ROOT);
System.out.println(f.parse(input)); // 2011-12-03T12:34:56.550

2016-04-29更新:

正如人们通过上面提到的JDK问题所看到的,它现在被标记为已解决 - 对于Java 9


答案 2
DateTimeFormatterBuilder#appendFraction(ChronoField.MILLI_OF_SECOND, 0, 3, true)

像这样的东西帮助了我


推荐