首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在开始时创建一个带有可选部分的DateTimeFormater

在开始时创建一个带有可选部分的DateTimeFormater
EN

Stack Overflow用户
提问于 2021-06-11 10:32:15
回答 2查看 228关注 0票数 3

我有一个具有这种结构的hh:mm:ss.SSS的timecodes,我有一个自己的类,实现了时态接口。它具有自定义字段TimecodeHour字段,允许值大于23小时。我想用DateTimeFormatter进行解析。小时值是可选的(可以省略,小时数可以大于24小时);如RegEx (\d*\d\d:)?\d\d:\d\d.\d\d\d

为了解决这个问题,可以将我的自定义字段替换为普通的HOUR_OF_DAY字段。

我现在的格式化程序

代码语言:javascript
复制
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按预期解析,但带有小时省略的值将抛出异常。

代码语言:javascript
复制
java.time.format.DateTimeParseException: Text '20:33.123' could not be parsed at index 5

我假设,由于小时和分钟有相同的模式,解析器从前面开始,并捕获可选部分的分钟值。这是对的,怎样才能解决呢?

EN

回答 2

Stack Overflow用户

发布于 2021-06-15 19:29:55

我开始怀疑20:33.123并不意味着一天中的某个时间是在午夜20到21分钟之间。也许是一段时间,略长于20分钟。如果这是正确的,为它使用一个Duration

不幸的是,java.time不包括用于解析和格式化Duration的方法,而不是ISO8601格式。这使我们至少有三种选择:

  1. 使用第三方图书馆。Time4J提供了一个优雅的解决方案,如下所示。Joda-Time有它的PeriodFormatter类.Apache还可以提供用于分析和格式化持续时间的工具。
  2. 在使用Duration.parse()进行解析之前,将字符串转换为ISO8601格式。
  3. 编写自己的解析器。

我想我们太懒了,而且Joda-Time已经过时了,所以我想在这里继续选择1和2,在Time4J变体中的选项1。

适应ISO 8601的规范

一开始,ISO 8601格式的持续时间感觉很不寻常,但很简单。PT20M33.123S的意思是20分33.123秒。

代码语言:javascript
复制
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);
}

我们试试看:

代码语言:javascript
复制
    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库提供了非常优雅的解决方案,就像您的思路一样。我们真正需要的是这个格式化程序:

代码语言:javascript
复制
private static final Formatter<ClockUnit> TIME_CODE_PARSER 
        = Duration.formatter(ClockUnit.class, "[###hh:mm:ss.fff][mm:ss.fff]");

只需这样使用:

代码语言:javascript
复制
    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是接近尾声。这不是你想要的,所以是的,你使用了错误的方法。虽然您可能会让它开始工作,但是任何在您之后维护代码的人都会发现它令人困惑,并且可能很难根据您的意图进行修改。

链接

票数 3
EN

Stack Overflow用户

发布于 2021-06-11 10:59:03

我认为从根本上讲,问题在于它被困在了错误的道路上。它看到一个长度为2的字段,我们知道它是分钟,但它相信是小时。一旦它相信了可选部分的存在,当我们知道它不是,整个事情注定要失败。

这可以通过将最小小时长度更改为3来证明。

代码语言:javascript
复制
.appendValue(TimecodeHour.HOUR, 3, 4, SignStyle.NEVER)

它现在知道"20“不能是小时,因为小时至少需要3位数。使用这个小的更改,它现在正确地解析,无论可选部分是否存在。

因此,假设“小时”字段确实需要介于2到4位之间,我认为您必须实现一个解决方案。例如,计算字符串中冒号的数量,并根据遇到的哪个格式化程序使用不同的格式化程序。除了冒号外,使用不同的分隔符对小时也是有效的。

自引入以来,解析器逻辑在各种Java版本上经历了相当多的bug修复--正如您可以想象的那样,有很多潜在的边缘情况--所以我希望使用最新版本的Java将使这个问题消失。不幸的是,即使在Java 16中,这种行为似乎也是一样的。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/67935444

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档