42

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

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

渲染:

2011-12-03T12:34:56

但是,当我按照DateTimeFormatter 类文档中的指定添加“SS”作为秒的分数(和“55”作为输入)时,会引发异常:

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 );
4

3 回答 3

42

错误 – 在 Java 9 中已修复

此问题已在JDK-bug-log中报告。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 次 SSSSSSSSS)。对于两位小数,请参阅下面的更新。

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

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

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

编辑:

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

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

不幸的是,JSR-310 也没有很好地处理小数部分 ( appendFraction(...))。如果您在类的 javadoc 中查找关键字“adjacent”,DateTimeFormatterBuilder您会发现该功能仅通过appendValue(...)-methods 实现。请注意,模式字母 S 的规范略有不同,但在内部委托给appendFraction()-method。我假设我们至少要等到 Java 9(如 JDK-bug-log 中所报告的,或更高版本???),直到分数部分也可以管理相邻的值解析。


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

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

剩下的解决方案要么在 Java 9(或更高版本?)之前等待很长时间,要么编写自己的 hack 或使用 3rd-party 库作为解决方案。

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

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 年 4 月 29 日更新:

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

于 2014-03-24T10:28:22.723 回答
1
DateTimeFormatterBuilder#appendFraction(ChronoField.MILLI_OF_SECOND, 0, 3, true)

这样的事情帮助了我

于 2019-05-16T09:48:30.447 回答
0

这是一种算法,它调整通常从格式化的 date 返回的尾随零的顺序String

/**
 * Takes a Date and provides the format whilst compensating for the mistaken representation of sub-second values.
 * i.e. 2017-04-03-22:46:19.000991 -> 2017-04-03-22:46:19.991000
 * @param pDate Defines the Date object to format.
 * @param pPrecision Defines number of valid subsecond characters contained in the system's response.
 * */
private static final String subFormat(final Date pDate, final SimpleDateFormat pSimpleDateFormat, final int pPrecision) throws ParseException {
    // Format as usual.
    final String lString        = pSimpleDateFormat.format(pDate);
    // Count the number of characters.
    final String lPattern       = pSimpleDateFormat.toLocalizedPattern();
    // Find where the SubSeconds are.
    final int    lStart         = lPattern.indexOf('S');
    final int    lEnd           = lPattern.lastIndexOf('S');
    // Ensure they're in the expected format.
    for(int i = lStart; i <= lEnd; i++) { if(lPattern.charAt(i) != 'S') {
        // Throw an Exception; the date has been provided in the wrong format.
       throw new ParseException("Unable to process subseconds in the provided form. (" + lPattern + ").", i);
    } }
    // Calculate the number of Subseconds. (Account for zero indexing.)
    final int lNumSubSeconds = (lEnd - lStart) + 1;
    // Fetch the original quantity.
    String lReplaceString = lString.substring(lStart + (lNumSubSeconds - pPrecision), lStart + lNumSubSeconds);
    // Append trailing zeros.
    for(int i = 0; i < lNumSubSeconds - pPrecision; i++) { lReplaceString += "0"; }
    // Return the String.
    return lString.substring(0, lStart) + lReplaceString;
}
于 2017-04-04T10:45:28.783 回答