java.time无法解析小数部分吗?

Bas*_*que 35 java java-time

随着Mac OS X(Mavericks)上的第一个Java 8(b132)发布,使用新的java.time包的代码可以工作:

String input = "20111203123456"; 
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmss");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );
Run Code Online (Sandbox Code Playgroud)

渲染:

2011-12-03T12:34:56
Run Code Online (Sandbox Code Playgroud)

但是当我按照DateTimeFormatter类doc中的指定添加"SS"作为秒的小数(和"55"作为输入)时,会抛出异常:

java.time.format.DateTimeParseException: Text '2011120312345655' could not be parsed at index 0
Run Code Online (Sandbox Code Playgroud)

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

String input = "2011120312345655"; 
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmssSS");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );
Run Code Online (Sandbox Code Playgroud)

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

String input = "20111203123456978"; 
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmssSSS");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );
Run Code Online (Sandbox Code Playgroud)

这个例子工作,添加一个小数点(但我发现在doc中没有这样的要求):

String input = "20111203123456.978"; 
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmss.SSS");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );
Run Code Online (Sandbox Code Playgroud)

呈现:

localDateTime: 2011-12-03T12:34:56.978
Run Code Online (Sandbox Code Playgroud)

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

失败:

String input = "20111203123456.978"; 
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmssSSS");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );
Run Code Online (Sandbox Code Playgroud)

失败:

String input = "20111203123456978"; 
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmss.SSS");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );
Run Code Online (Sandbox Code Playgroud)

Men*_*ild 33

错误 - 在Java 9中修复

JDK-bug-log中已经报告了此问题.Stephen Colebourne提到了解决方案:

DateTimeFormatter dtf = 
  new DateTimeFormatterBuilder()
  .appendPattern("yyyyMMddHHmmss")
  .appendValue(ChronoField.MILLI_OF_SECOND, 3)
  .toFormatter();
Run Code Online (Sandbox Code Playgroud)

注意:此解决方法不包括仅使用两个模式符号SS的用例.调整可能只是使用其他字段,如MICRO_OF_SECOND(6次SSSSSS)或NANO_OF_SECOND(9次SSSSSSSSS).有关两位数的数字,请参阅下面的更新.

@PeterLawrey关于模式符号"S"的含义,请参阅此文档:

分数:以秒为单位输出纳秒级字段.纳秒值具有九位数,因此模式字母的数量从1到9.如果小于9,则截断纳秒值,仅输出最高有效数字.在严格模式下解析时,已解析数字的数量必须与模式字母的数量匹配.在宽松模式下解析时,解析数字的数量必须至少为模式字母的数量,最多为9位数.

所以我们看到S代表秒的任何一小部分(包括纳秒),而不仅仅是几毫秒.此外,遗憾的是,小数部分目前在相邻值解析中表现不佳.

编辑:

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

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

不幸的是,JSR-310在使用小数部分(appendFraction(...))时也没有很好地做到这一点.如果在类的javadoc中DateTimeFormatterBuilder查找关键字"adjacent",则会发现此功能仅由appendValue(...)-methods 实现.请注意,模式字母S的规范略有不同,但内部委托给appendFraction()-method.我假设我们至少要在Java 9之前(如JDK-bug-log中报告的那样,或者稍后???)直到分数部分也可以管理相邻的值解析.


2015-11-25更新:

以下使用两个小数位的代码不起作用并抛出一个DateTimeParseException.

DateTimeFormatter dtf = 
  new DateTimeFormatterBuilder()
  .appendPattern("yyyyMMddHHmmssSS")
  .appendValue(ChronoField.MILLI_OF_SECOND, 2)
  .toFormatter();
String input = "2011120312345655"; 
LocalDateTime.parse(input, dtf); // abort
Run Code Online (Sandbox Code Playgroud)

解决方法

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

不起作用,因为SimpleDateFormat以错误的方式解释分数(见输出,55毫秒而不是550毫秒).

剩下的解决方案是要么长时间等待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
Run Code Online (Sandbox Code Playgroud)

使用Joda-Time的解决方案:

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

使用我的库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
Run Code Online (Sandbox Code Playgroud)

2016-04-29更新:

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