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”类型的值[重复]
activity_main、menu、action_setting 无法解析或不是字段
使用 Spring Boot 2 和 Kotlin 进行 Jackson 反序列化,无法构造 `java.time.LocalDate` 的实例