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]");
    
    Time4J
    Duration
    类也打印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