java.time 是不是无法解析秒的分数?

Posted

技术标签:

【中文标题】java.time 是不是无法解析秒的分数?【英文标题】:Is java.time failing to parse fraction-of-second?java.time 是否无法解析秒的分数? 【发布时间】:2014-04-30 13:11:08 【问题描述】:

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

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

渲染:

2011-12-03T12:34:56

但是,当我按照DateTimeFormatter class doc 中的指定为小数秒添加“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 );

【问题讨论】:

那一小部分是纳秒。你可以试试9位数吗?尽管从阅读文档来看,只要模式与位数匹配,它就应该使用少于 9 位的数字。 重新阅读文档后,您可以尝试在秒字段后加上小数点吗?喜欢20111203123456.978? 添加小数点使其工作。请参阅问题底部的补充。但是你能指出我在文档中的这种要求吗?我正在尝试确定是否应该提交错误报告。 首先,它对你有用。其次,我没有看到它是专门写的,但更像是我对几分之一秒的解释。秒的小数部分前面应该有一个小数。没有小数是没有意义的。为了与时间 HH:mm:ss 进行比较,冒号通常用作分隔符,但它不是字段的一部分。因此,不需要冒号,因为它不是字段的一部分。但是秒的分数是秒字段的一部分。所以应该用小数分隔。我就是这么解释的。但是,如果您仍然认为存在错误,请随时记录错误报告。 @BasilBourque,最大的问题之一是the SE tag system doesn't consider . in the same way it considers other punctuation。寻找java-time 的人不会看到java.time,而且在不同的地方有一个可怕的标签组合,使用一个而不是另一个。如果您认为带有句点的标签会更有帮助,那么让我们完全消除其他标签以消除歧义。 【参考方案1】:

错误 - 在 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”的含义见this documentation:

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

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

编辑:

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

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

不幸的是,JSR-310 并没有很好地处理小数部分 (appendFraction(...))。如果您在DateTimeFormatterBuilder 类的javadoc 中查找关键字“adjacent”,您会发现此功能仅由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 方库作为解决方案。

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

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

【讨论】:

在 2015 年 11 月 25 日的更新中,您说此格式化程序引发 DateTimeParseException:new DateTimeFormatterBuilder().appendPattern("yyyyMMddHHmmssSS").appendValue(ChronoField.MILLI_OF_SECOND, 2)。但是,如果您删除模式末尾的“SS”(它不应该存在,因为您在毫秒内使用appendValue()),它不会引发异常。唯一的问题是它错误地解释了分数,返回“55”而不是“550”,比如SimpleDateFormat @EtienneNeveu 非常感谢。根据您的评论,我现在已经更正了我 2015 年的声明。【参考方案2】:
DateTimeFormatterBuilder#appendFraction(ChronoField.MILLI_OF_SECOND, 0, 3, true)

这样的事情帮助了我

【讨论】:

【参考方案3】:

这是一种算法,它调整通常从格式化日期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;

【讨论】:

Stack Overflow 上的答案最好包含一些讨论或解释。 @BasilBourque 你说得对。我希望 cmets 会为自己说话,但你说得对,它至少值得一些背景。

以上是关于java.time 是不是无法解析秒的分数?的主要内容,如果未能解决你的问题,请参考以下文章

无法解决 android studio 中的符号“java.time.LocalDate”错误

无法从字符串中反序列化“java.time.LocalDate”类型的值[重复]

C#分数背包o(n logn)解

activity_main、menu、action_setting 无法解析或不是字段

apache下php无法解析直接显示源代码解

使用 Spring Boot 2 和 Kotlin 进行 Jackson 反序列化,无法构造 `java.time.LocalDate` 的实例