Java 创建一个DateTimeFormatter,开头有一个可选部分
我有一个具有这种结构的时间码Java 创建一个DateTimeFormatter,开头有一个可选部分,java,datetime-format,java-time,Java,Datetime Format,Java Time,我有一个具有这种结构的时间码hh:mm:ss.SSS,我有一个自己的类,实现了时态接口。 它具有自定义字段TimecodeHour字段,允许小时值大于23。 我想用DateTimeFormatter进行解析。小时值是可选的(可以省略,小时可以大于24);作为正则表达式(\d*\d\d:)?\d\d:\d\d。\d\d\d 就这个问题而言,我的自定义字段可以替换为正常的小时/天字段 我当前的格式化程序 DateTimeFormatter UNLIMITED_HOURS = new DateTime
hh:mm:ss.SSS
,我有一个自己的类,实现了时态接口。
它具有自定义字段TimecodeHour字段,允许小时值大于23。
我想用DateTimeFormatter进行解析。小时值是可选的(可以省略,小时可以大于24);作为正则表达式(\d*\d\d:)?\d\d:\d\d。\d\d\d
就这个问题而言,我的自定义字段可以替换为正常的小时/天字段
我当前的格式化程序
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);
按预期解析具有小时值的时间码,但忽略小时值的时间码会引发异常
java.time.format.DateTimeParseException: Text '20:33.123' could not be parsed at index 5
我假设,由于小时和分钟具有相同的模式,解析器从前面开始,并捕获可选部分的分钟值。
这是正确的吗?怎样才能解决这个问题呢?我认为从根本上说,问题在于它会被困在错误的道路上。它看到一个长度为2的区域,我们知道是分钟,但它相信是小时。一旦它相信可选部分存在,当我们知道它不存在时,整个事情注定要失败 这可以通过将最小小时长度更改为3来证明
.appendValue(TimecodeHour.HOUR, 3, 4, SignStyle.NEVER)
现在它知道“20”不能是小时,因为小时至少需要3位数字。有了这个小改动,它现在可以正确地解析,不管可选部分是否存在
因此,假设小时字段确实需要在2到4位之间,我认为您必须实现一个变通方法。例如,计算字符串中的冒号数,并根据遇到的格式设置程序使用不同的格式设置程序。在小时数中使用冒号以外的其他分隔符也可以
自从引入解析器逻辑以来,它在不同的Java版本上都经历了很多错误修复——正如您所想象的,有很多潜在的边缘情况——因此我希望使用最新版本的Java可以消除这个问题。不幸的是,即使在Java 16中,行为似乎仍然是一样的。尝试使用两个可选部分(一个有小时,另一个没有小时),如:
我不知道
TimecodeHour
,所以我用HOUR\u OF u DAY
来测试(也懒得包含分数)我开始怀疑
20:33.123
并不是指一天中午夜后20到21分钟之间的时间。可能需要一段时间,比20分钟多一点。如果这是正确的,请使用持续时间
不幸的是,java.time不包括解析和格式化ISO 8601格式以外的持续时间的方法。这让我们至少有三个选择:
使用第三方库。Time4J提供了一个优雅的解决方案,见下文。Joda Time有它的PeriodFormatter
类。Apache还可以提供解析和格式化持续时间的工具
使用Duration.parse()
解析之前,将字符串转换为ISO 8601格式
编写自己的解析器
我在想我们太懒了,不适合三个人。而乔达的时代已经过时了,所以我想追求选项1。二,。在这里,选择1。在Time4J变体中
适应ISO8601的正则表达式
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"));
输出为:
我对replaceFirst
的两个调用首先处理有小时的案例,然后处理没有小时的案例。因此,两者都会将与正则表达式匹配的字符串转换为ISO 8601格式。然后,Duration
类对其进行解析。如您所见,Duration
也会将ISO 8601格式打印回来。不过,不同的格式也不错,搜索一下如何设置
时间4j
Time4J库提供了真正优雅的解决方案,其思路与您的非常一致。我们真正需要的是这个格式化程序:
private static final Formatter<ClockUnit> TIME_CODE_PARSER
= Duration.formatter(ClockUnit.class, "[###hh:mm:ss.fff][mm:ss.fff]");
Time4JDuration
类也打印ISO 8601格式。它似乎使用逗号作为十进制分隔符,这在ISO 8601中是首选的,并且当某些小数为0时,它也会在秒上打印9个小数
在格式模式中,字符串###hh
表示2到5位数的小时,fff
表示小数点后三位数的秒
你的方法有什么问题吗?
你的方法有什么问题吗ChronoField.HOUR OF_OF_DAY
的意思是:HOUR OF DAY。0是午夜,12是中午,23是接近一天的结束。这不是你想要的,所以是的,你使用了错误的方法。虽然您可能可以让它工作,但在您之后维护代码的任何人都会发现它令人困惑,并且可能很难根据您的意图进行修改
链接
- 什么是
TimecodeHour.HOUR
?似乎不属于JDK@MichaelTimecodeHour是我自己的班级;允许小时值大于23的自定义字段。请将其包含在问题中,以便人们可以将您的代码复制到IDE中并运行它。TimecodeHour
是否表示时间量,例如持续时间,而不是一天中的某个时间?如果是这样,不要对其使用任何时态。如果您想创建自己的类,它可以实现TemporalAmount
。您可能还喜欢使用Duration
类。@user15793316很好。测试过了,你是对的,确实如此。不要认为这会起作用。如果两个部分都是可选的,这将接受一个空字符串,对吗?或包含两部分的字符串,如11:22:33.4411:22.33。你需要一些方法来做一种逻辑OR,这是存在的none@user15793316很好,你的方法似乎有效,我稍后会看看它是否能通过所有测试tests@Michael我测试了他的aproach:空字符串抛出一个异常,一个包含连接在一起的两个变体的字符串返回
PT20M33.123S
PT123H20M33.123S
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