Extract multiple TimeUnits from a String

229 Views Asked by At

I am trying to strip every Time Unit of a String, e.g.

The String "4w10d50m39s" would return a TimeUnit of 4 weeks, a TimeUnit of 10 days, a TimeUnit of 50 minutes, and a TimeUnit of 39 seconds.

How can I achieve this?

Context: I need them to sum all the Time unit converted to millis and use it as a time stamp, it will be used inside a Minecraft server, in a command made to add a rank to a user for an specific amount of time, example: /addrank iLalox Vip 4w5d, that would set the expiration date to: System.currentMillis() + timeInMillis.

2

There are 2 best solutions below

3
On BEST ANSWER

To extract unit you can use regex

\\d+[wdms]

Demo

Then you can use matcher to extract matches from string and create TimeUnits. There is no Week constant in TimeUnit, so i would present week as amount of weeks * 7 in days.

public static void main(String[] args) {
    String test = new String("4w10d50m39s");
    Pattern pattern = Pattern.compile("\\d+[wdms]");
    Matcher matcher = pattern.matcher(test);

    while(matcher.find()) {
        String unit = matcher.group();
        char timeType = unit.charAt(unit.length() - 1);
        int timeAmount = Integer.parseInt(unit.substring(0, unit.length() - 1));
        if(timeType == 'd') {
            System.out.println(TimeUnit.DAYS.toDays(timeAmount) + " DAYS");
        }

        if(timeType == 'm') {
            System.out.println(TimeUnit.MINUTES.toMinutes(timeAmount) + " MINUTES");
        }

        if(timeType == 's') {
            System.out.println(TimeUnit.SECONDS.toSeconds(timeAmount) + " SECONDS");
        }

        if(timeType == 'w') {
            System.out.println(TimeUnit.DAYS.toDays(timeAmount * 7L) + " DAYS IN WEEK");
        }
    }
}

Output:

28 DAYS IN WEEK
10 DAYS
50 MINUTES
39 SECONDS
0
On

Period, Duration and Instant from java.time

I recommend that you use java.time, the modern Java date and time API, for your date and time work. Assuming your expiration is in real time (not Minecraft time):

    String string = "4w10d50m39s";
     
    String dayBasedString
            = string.replaceFirst("(^(?:\\d+w)?(?:\\d+d)?).*$", "$1");
    String timeBasedString = string.substring(dayBasedString.length());

    Period period = dayBasedString.isEmpty()
            ? Period.ZERO
            : Period.parse("P" + dayBasedString);
    assert period.getYears() == 0 : period;
    assert period.getMonths() == 0 : period;

    Duration dur = timeBasedString.isEmpty()
            ? Duration.ZERO
            : Duration.parse("PT" + timeBasedString); 
    dur = Duration.ofDays(period.getDays()).plus(dur);
    
    Instant now = Instant.now();
    Instant expiration = now.plus(dur);
    
    System.out.format("Now: %s; expiration: %s%n", now, expiration);

Output when I ran the snippet just now:

Now: 2021-08-20T18:24:35.701136Z; expiration: 2021-09-27T19:15:14.701136Z

Java has classes Period for day-based periods and Duration for time-based ones. Since your string is both, I am using both classes. And the Instant class for a point in time like the expiration time of your rank.

The Period class can natively parse a String like P4w10d, and the Duration class one like PT50m39s. The challenge is to split your string after the weeks and days and before the hours, minutes and seconds. My regular expression matches any (possibly empty) part of the string containing weeks and/or days and copies this part to a separate variable. Then copying the rest to another variable is not so complicated. A Period may contain years, months and days (weeks are automatically converted to days). For my following calculation to work I need to assume that there were no years or months since I don’t know how many days those are. I am also assuming that a day is 24 hours even though this is not always the case in real life.

As a variation you may use a ZonedDateTime for the current time. The advantage will be that you can directly add first a Period and then a Duration to it without the need to convert the weeks and days to a Duration first. And as a further variation you may use the PeriodDuration class from the ThreeTen Extra project. I haven’t checked, but I would expect that it would be possible to add a PeriodDuration to a ZonedDateTime since it would make good sense.

I need them to sum all the Time unit converted to millis and use it as a time stamp, …

For that you neither need to sum the units yourself nor to convert to milliseconds. Period and Duration parse larger chunks of your string, and Instant.plus() directly accepts the Duration object that we end up with.

Links