Search code examples
javaparsingjava-timedurationlocaltime

Parse to LocalTime pattern mm:ss.S


How can parse LocalTime from String e.g. "10:38.0" in mm:ss.S format? I struggle to change the format.

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

Getting error

ISO of type java.time.format.Parsed java.time.format.DateTimeParseException: Text '10:38.2' could not be parsed: Unable to obtain LocalTime from TemporalAccessor: {MinuteOfHour=10, MicroOfSecond=200000, MilliOfSecond=200, NanoOfSecond=200000000, SecondOfMinute=38},


Solution

  • java.time.Duration.parse()

    As several others have correctly and wisely stated, your example string of 10:38.0 looks more like an amount of time, a duration. Not like a time of day a little more than 10 minutes after midnight. So LocalTime is not the correct class to use here. Use Duration. And parse the string into a Duration object.

    The Duration class only supports parsing of ISO 8601 format, though. ISO 8601 format goes like PT10M38.0S for a period of time of 10 minutes 38.0 seconds (or for example PT10M38S or PT10M38.00000S, they work too). There are more ways to overcome this limitation. Arvind Kumar Avinash already shows one in his answer. My way would be to convert the string before parsing it:

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

    Let’s try it out:

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

    Output is:

    PT10M38S

    You see that the Duration prints back in ISO 8601 format too.

    Depending on what further processing you want your duration for you are likely to find many useful methods in the documentation of that class; link below.

    How time.replaceFirst("^(\\d+):(\\d+(?:\\.\\d*)?)$", "PT$1M$2S") works: I am using a regular expression to match your string:

    • ^: Match the beginning of your string.
    • (\\d+): A capturing group matching one or more digits. Round brackets denote capturing groups. I will need this feature in the replacement below.
    • :: A colon (indeed).
    • (\\d+(?:\\.\\d*)?): A capturing group of digits optionally followed by a dot and zero or more further digits. (?: denotes the beginning of a non-capturing group that I use since I don’t need it separately in the replacement. ? after the non-capturing group denotes that it is optional (so 38 with no fraction would work for the seconds too).
    • $: match the end of your string

    In my replacement string, PT$1M$2S, $1 and $2 denotes whatever was marched by the first and second capturing groups, which is what inserts 10 and 38.0 into the resulting string to obtain PT10M38.0S.

    Nicer solution with an external library: Time4J

    Using the non-trivial regular expression above to make your string and Duration.parse() meet isn’t the perfectly beautiful solution. Pattern-based parsing of a duration is supported by the Time4J library. So if you can tolerate an external dependency, consider using it. See the details in the answer by Meno Hochshield, the author of Time4J.

    Links