我有一个具有这种结构的hh:mm:ss.SSS的timecodes,我有一个自己的类,实现了时态接口。它具有自定义字段TimecodeHour字段,允许值大于23小时。我想用DateTimeFormatter进行解析。小时值是可选的(可以省略,小时数可以大于24小时);如RegEx (\d*\d\d:)?\d\d:\d\d.\d\d\d
为了解决这个问题,可以将我的自定义字段替换为普通的HOUR_OF_DAY字段。
我现在的格式化程序
DateTimeFormatter UNLIMITED_HOURS = new DateTimeFormatterBuilder()
.appendValue(ChronoField.HOUR_OF_DAY, 2, 2,SignStyle.NEVER)
.appendLiteral(':')
.parseDefaulting(TimecodeHour.HOUR, 0)
.toFormatter(Locale.ENGLISH);
DateTimeFormatter TIMECODE = new DateTimeFormatterBuilder()
.appendOptional(UNLIMITED_HOURS)
.appendValue(MINUTE_OF_HOUR, 2)
.appendLiteral(':')
.appendValue(SECOND_OF_MINUTE, 2)
.appendFraction(MILLI_OF_SECOND, 3, 3, true)
.toFormatter(Locale.ENGLISH);带有小时值的Timecodes按预期解析,但带有小时省略的值将抛出异常。
java.time.format.DateTimeParseException: Text '20:33.123' could not be parsed at index 5我假设,由于小时和分钟有相同的模式,解析器从前面开始,并捕获可选部分的分钟值。这是对的,怎样才能解决呢?
发布于 2021-06-15 19:29:55
我开始怀疑20:33.123并不意味着一天中的某个时间是在午夜20到21分钟之间。也许是一段时间,略长于20分钟。如果这是正确的,为它使用一个Duration。
不幸的是,java.time不包括用于解析和格式化Duration的方法,而不是ISO8601格式。这使我们至少有三种选择:
PeriodFormatter类.Apache还可以提供用于分析和格式化持续时间的工具。Duration.parse()进行解析之前,将字符串转换为ISO8601格式。我想我们太懒了,而且Joda-Time已经过时了,所以我想在这里继续选择1和2,在Time4J变体中的选项1。
适应ISO 8601的规范
一开始,ISO 8601格式的持续时间感觉很不寻常,但很简单。PT20M33.123S的意思是20分33.123秒。
public static Duration parse(String timeCodeString) {
String iso8601 = timeCodeString
.replaceFirst("^(\\d{2,}):(\\d{2}):(\\d{2}\\.\\d{3})$", "PT$1H$2M$3S")
.replaceFirst("^(\\d{2}):(\\d{2}\\.\\d{3})$", "PT$1M$2S");
return Duration.parse(iso8601);
}我们试试看:
System.out.println(parse("20:33.123"));
System.out.println(parse("123:20:33.123"));产出如下:
PT20M33.123S PT123H20M33.123S
我对replaceFirst的两个调用首先用小时处理这个案件,然后是不需要几个小时的情况。因此,要么将匹配regex的字符串转换为ISO 8601格式。然后由Duration类解析。正如您所看到的,Duration还打印了ISO8601格式。但是,以不同的方式格式化它并不坏。
Time4J
Time4J库提供了非常优雅的解决方案,就像您的思路一样。我们真正需要的是这个格式化程序:
private static final Formatter<ClockUnit> TIME_CODE_PARSER
= Duration.formatter(ClockUnit.class, "[###hh:mm:ss.fff][mm:ss.fff]");只需这样使用:
System.out.println(TIME_CODE_PARSER.parse("20:33.123"));
System.out.println(TIME_CODE_PARSER.parse("123:20:33.123"));PT20M33,123000000S PT123H20M33,123000000S
Time4J Duration类也打印ISO8601格式。它似乎使用逗号作为十进制分隔符,这在ISO 8601中是首选的,并且当其中一些小数为0时,它也会在秒上打印9个小数。
在格式模式中,字符串###hh表示2至5位数小时,而fff表示秒的小数分数的三位数。
你的方法有什么问题吗?
你的方法有什么问题吗?ChronoField.HOUR_OF_DAY的意思是:一天中的一个小时。0是午夜,12是中午,23是接近尾声。这不是你想要的,所以是的,你使用了错误的方法。虽然您可能会让它开始工作,但是任何在您之后维护代码的人都会发现它令人困惑,并且可能很难根据您的意图进行修改。
链接
PeriodFormatterTimeSpanFormatter发布于 2021-06-11 10:59:03
我认为从根本上讲,问题在于它被困在了错误的道路上。它看到一个长度为2的字段,我们知道它是分钟,但它相信是小时。一旦它相信了可选部分的存在,当我们知道它不是,整个事情注定要失败。
这可以通过将最小小时长度更改为3来证明。
.appendValue(TimecodeHour.HOUR, 3, 4, SignStyle.NEVER)它现在知道"20“不能是小时,因为小时至少需要3位数。使用这个小的更改,它现在正确地解析,无论可选部分是否存在。
因此,假设“小时”字段确实需要介于2到4位之间,我认为您必须实现一个解决方案。例如,计算字符串中冒号的数量,并根据遇到的哪个格式化程序使用不同的格式化程序。除了冒号外,使用不同的分隔符对小时也是有效的。
自引入以来,解析器逻辑在各种Java版本上经历了相当多的bug修复--正如您可以想象的那样,有很多潜在的边缘情况--所以我希望使用最新版本的Java将使这个问题消失。不幸的是,即使在Java 16中,这种行为似乎也是一样的。
https://stackoverflow.com/questions/67935444
复制相似问题