解析为 LocalTime 模式 mm:ss.S

Parse to LocalTime pattern mm:ss.S

如何从字符串中解析 LocalTime,例如mm:ss.S 格式的“10:38.0”?我很难改变格式。

    public static LocalTime parseTime(String time) {
        return localTime = LocalTime.parse(time, DateTimeFormatter.ofPattern("mm:ss.S"));
    }

获取错误

ISO 类型 java.time.format.Parsed java.time.format.DateTimeParseException:无法解析文本“10:38.2”:无法从 TemporalAccessor 获取本地时间:{MinuteOfHour=10,MicroOfSecond=200000,MilliOfSecond=200,NanoOfSecond=200000000,SecondOfMinute=38},

问题是,mm:ss 并不是真正的当地时间。这更像是短跑比赛时间。发生该错误是因为翻译要求为过去的小时数提供一个值,并且 none 可用('no hours' 在这里被解释为:他们不在模式中,而不是:“他们失踪了,因此让我们假设有 0 小时").

解决这个问题的一种 hacky 方法是将模式更改为 [HH:]mm:ss - [] 表示可选输入。现在含义发生了变化:10:20 被解释为(通过可选的输入方面)00:10:20 的 shorthand 并且 可解析为 LocalTime .

但是,也许 LocalTime 并不是您在这里寻找的东西;如果这确实描述了测量事件所经过的时间,那么您正在寻找 Duration,而不是 LocalTime。不幸的是,将 10:20 解析为 10 分 20 秒的持续时间并非易事,API 只是不支持它(从 DTFormatter 对象到达那里的唯一方法是 通过 LocalTime,够疯狂了)。

我相信你想要:.ofPattern("H:mm.s")

        public static LocalTime parseTime(String time) {
        return LocalTime.parse(time, DateTimeFormatter.ofPattern("H:mm.s"));
    }

https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html

DateTimeFormatterBuilder#parseDefaulting

您可以使用 DateTimeFormatterBuilder#parseDefaulting 将一天中的小时默认为零。

但是,在常识中,10:38.0代表一个时长。您可以通过找到解析的 LocalTimeLocalTime.MIN.

之间的持续时间来获得 Duration 对象

演示:

import java.time.Duration;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.util.Locale;

public class Main {
    public static void main(String[] args) {
        String str = "10:38.0";
        DateTimeFormatter dtf = new DateTimeFormatterBuilder()
                                .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
                                .appendPattern("mm:ss.S")
                                .toFormatter(Locale.ENGLISH);

        LocalTime time = LocalTime.parse(str, dtf);
        System.out.println(time);

        Duration duration = Duration.between(LocalTime.MIN, time);
        System.out.println(duration);
    }
}

输出:

00:10:38
PT10M38S

ONLINE DEMO

了解有关 modern Date-Time API* from Trail: Date Time 的更多信息。


* 如果您正在为 Android 项目工作,并且您的 Android API 级别仍然不符合 Java-8,请检查Java 8+ APIs available through desugaring. Note that Android 8.0 Oreo already provides support for java.time

java.time.Duration.parse()

正如其他几个人正确而明智地指出的那样,您的示例字符串 10:38.0 看起来更像是一段时间,一段持续时间。不像一天中午夜后 10 分钟多一点的时间。所以 LocalTime 不是在这里使用的正确 class。使用 Duration。并将字符串解析为 Duration 对象。

不过Durationclass只支持解析ISO 8601格式。 ISO 8601 格式类似于 PT10M38.0S,持续 10 分 38.0 秒(或者例如 PT10M38SPT10M38.00000S,它们也有效)。有更多方法可以克服此限制。 Arvind Kumar Avinash 已经在他的回答中展示了一个。我的方法是在解析之前转换字符串:

public static Duration parseTime(String time) {
    String iso = time.replaceFirst("^(\d+):(\d+(?:\.\d*)?)$", "PTMS");
    return Duration.parse(iso);
}

让我们试试看:

    Duration dur = parseTime("10:38.0");
    System.out.println(dur);

输出为:

PT10M38S

您看到 Duration 也以 ISO 8601 格式打印回来。

根据您希望进行的进一步处理,您可能会在 class 的文档中找到许多有用的方法; link 下面。

time.replaceFirst("^(\d+):(\d+(?:\.\d*)?)$", "PTMS") 的工作原理: 我正在使用正则表达式来匹配您的字符串:

  • ^:匹配字符串的开头。
  • (\d+)捕获组匹配一位或多位数字。圆括号表示捕获组。我将在下面的替换中需要此功能。
  • ::一个冒号(确实如此)。
  • (\d+(?:\.\d*)?):一组捕获的数字,可选地后跟一个点和零个或多个其他数字。 (?: 表示我使用的非捕获组的开头,因为我不需要在替换中单独使用它。 ? 在非捕获组之后表示它是可选的(因此 38 没有分数也适用于秒数)。
  • $: 匹配你的字符串结尾

在我的替换字符串中PTMS</code>和<code>表示第一和第二捕获组行进的任何内容,是将 1038.0 插入结果字符串以获得 PT10M38.0S.

带有外部库的更好的解决方案:Time4J

使用上面的非平凡正则表达式来使您的字符串和 Duration.parse() 相遇并不是完美的解决方案。 Time4J 库支持基于模式的持续时间解析。因此,如果您可以容忍外部依赖,请考虑使用它。详见Time4J作者Meno Hochshield的回答。

链接

a) class DateTimeFormatter 无法解析像 mm:ss.S 这样没有小时的表达式,因为这样的解析器试图将其解释为时间点,而不是持续时间。缺少小时是将结果解析为 LocalTime.

实例的固定要求

b) 您可能需要持续时间,而不是 LocalTime。好吧,java.time 确实有一个名为 java.time.Duration 的 class 但它只能格式化和解析类 ISO-8601 表达式的子集,例如: PT10M38.2S 你想要的模式不支持。对不起。

c) 有些人建议妥协:将 LocalTime 解释为一种持续时间(不是真的!)然后使用默认小时值解析表达式,最后评估分钟和秒等等。但是,只有当您永远不会获得大于 59 分钟或 59 秒的时间分量值时,这种 hacky 解决方法才会起作用。

d) 我的外部图书馆 Time4J supports pattern-based printing and parsing of durations. Example using the class net.time4j.Duration.Formatter:

@Test
public void example() throws ParseException {
    TemporalAmount ta =
        Duration.formatter(ClockUnit.class, "mm:ss.f")
            .parse("10:38.2")
            .toTemporalAmount();
    System.out.println(LocalTime.of(5, 0).plus(ta)); // 05:10:38.200
}

该示例还演示了通过转换方法 toTemporalAmount() 到 Java-8-class 的桥梁,例如 LocalTime。如果您使用 net.time4j.PlainTime 代替,那么桥接当然不是必需的。

此外,time4j-duration-class 的众多特性之一是在表达式包含不符合标准时钟方案(如 10 分 68 秒(= 11 分钟)的时间分量时控制归一化+ 8 秒)。

@Test
public void example2() throws ParseException {
    net.time4j.Duration dur =
        Duration.formatter(ClockUnit.class, "mm:ss.f")
            .parse("10:68.2")
            .with(Duration.STD_CLOCK_PERIOD); // normalizing
    System.out.println(PlainTime.of(5, 0).plus(dur)); // 05:11:08.200
}