Parsing time strings like "1h 30min"

Viewed 30474

Anyone know of a Java library that can parse time strings such as "30min" or "2h 15min" or "2d 15h 30min" as milliseconds (or some kind of Duration object). Can Joda-Time do something like this?

(I have an ugly long method to maintain that does such parsing and would like to get rid of it / replace it with something that does a better job.)

8 Answers

FYI, Just wrote this for hour+ periods, only uses java.time.*, pretty simple to understand and customize for any need;

This version works with strings like; 3d12h, 2y, 9m10d, etc.

import java.time.Duration;
import java.time.Instant;
import java.time.Period;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Locale;
private static final Pattern periodPattern = Pattern.compile("([0-9]+)([hdwmy])");

public static Long parsePeriod(String period){
    if(period == null) return null;
    period = period.toLowerCase(Locale.ENGLISH);
    Matcher matcher = periodPattern.matcher(period);
    Instant instant=Instant.EPOCH;
    while(matcher.find()){
        int num = Integer.parseInt(matcher.group(1));
        String typ = matcher.group(2);
        switch (typ) {
            case "h":
                instant=instant.plus(Duration.ofHours(num));
                break;
            case "d":
                instant=instant.plus(Duration.ofDays(num));
                break;
            case "w":
                instant=instant.plus(Period.ofWeeks(num));
                break;
            case "m":
                instant=instant.plus(Period.ofMonths(num));
                break;
            case "y":
                instant=instant.plus(Period.ofYears(num));
                break;
        }
    }
    return instant.toEpochMilli();
}

java.time

Quoted below is a notice from the home page of Joda-Time:

Note that from Java SE 8 onwards, users are asked to migrate to java.time (JSR-310) - a core part of the JDK which replaces this project.

Solution using java.time, the modern Date-Time API:

You can convert the input string into ISO_8601#Duration format and then parse the same into java.time.Duration which was introduced with Java-8 as part of JSR-310 implementation.

Demo:

import java.time.Duration;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        // Test
        Stream.of(
                "30min", 
                "2h 15min", 
                "2d 15h 30min"
        ).forEach(s -> System.out.println(s + " => " + toMillis(s) + "ms"));
    }

    static long toMillis(String strDuration) {
        strDuration = strDuration.replaceAll("\\s+", "").replaceFirst("(\\d+d)", "P$1T");
        strDuration = strDuration.charAt(0) != 'P' ? "PT" + strDuration.replace("min", "m")
                : strDuration.replace("min", "m");
        Duration duration = Duration.parse(strDuration);
        return duration.toMillis();
    }
}

Output:

30min => 1800000ms
2h 15min => 8100000ms
2d 15h 30min => 228600000ms

ONLINE DEMO

Learn more about the modern Date-Time API* from Trail: Date Time.


* If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring. Note that Android 8.0 Oreo already provides support for java.time.

I realise this thread is a few years old but it keeps popping up at the top of a Google search so I thought we'd share this.

We needed this capability as well on a couple of projects, so we created this small library for it: https://github.com/blueanvil/kotlin-duration-string

It's written in/for Kotlin, but you can use it from Java code as well (see JavaTest.java):

Duration duration = toDuration("24d 3h 45m");
...
toHumanReadableString(duration);

Not exactly Java - rather Kotlin, and not using Joda but JDK's java.time:

val RELAXED_FORMATTER = DateTimeFormatter.ofPattern("yyyy[-MM[-dd[' 'HH:mm[:ss[.SSS]]]]]")

fun parseTemporalInput(input: String): LocalDateTime? {
    var result = LocalDateTime.MAX.withNano(0)

    if (input.lowercase() == "now")
        return LocalDateTime.now()

    try {
        val parsed = RELAXED_FORMATTER.parse(input)
        for (field in listOf(YEAR, MONTH_OF_YEAR, DAY_OF_MONTH, HOUR_OF_DAY, MINUTE_OF_HOUR, SECOND_OF_MINUTE)) {
            try {
                result = result.with(field, parsed.getLong(field))
            } catch (ex: UnsupportedTemporalTypeException) {
                result = result.with(field, if (field.isDateBased) 1 else 0)
            }
        }
        return result
    } 
    catch (parseEx: DateTimeParseException) {
        try {
            val inputToIso8601 = "P" + input.uppercase().replace("-","").replace(" ", "").replace("D", "DT").removeSuffix("T")
            // Expected format:  "PnDTnHnMn.nS"
            val duration = Duration.parse(inputToIso8601)

            val base = LocalDateTime.now().let {
                if (!inputToIso8601.contains("D")) it
                else it.truncatedTo(ChronoUnit.DAYS)
            }

            return base.minus(duration)
        }
        catch (ex: DateTimeParseException) {
            return null
        }
    }
    return null
}
  • Only supports days (since the underlying ISO 8601 does not standardize weeks, months etc.)
  • Does not handle relative in the future, although easy to add.
  • Is not meant as a full-blown implementation of duration parsing, so take it as that.

Taken from this Gist: https://gist.github.com/OndraZizka/5fd56479ed2f6175703eb8a2e1bb1088

Related