Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to parse time based tokens in java ?(1m 1M 1d 1Y 1W 1S)

I gets following strings from FE:

1m
5M
3D
30m
2h
1Y
3W

It corresponds to 1 minute, 5 months,3 days, 30 minutes, 2 hours, 1 year, 3 weeks.

Is there mean in java to parse it?

I want to manipulate(add/minus) with Instant(or LocalDatetTime). Is there way to do it in java?

like image 743
gstackoverflow Avatar asked Dec 24 '22 00:12

gstackoverflow


2 Answers

Period & Duration

I consider the following solution simple and pretty general (not fully general).

public static TemporalAmount parse(String feString) {
    if (Character.isUpperCase(feString.charAt(feString.length() - 1))) {
        return Period.parse("P" + feString);
    } else {
        return Duration.parse("PT" + feString);
    }
}

It seems that your date-based units (year, month, week, day) are denoted with uppercase abbreviations (Y, M, W and D) while the time-based ones (hour and minute) are lowercase (h and m). So I test the case of the last character of the string to decide whether to parse into a Period or a Duration. I exploit the fact that both of Period.parse and Duration.parse accept the letters in either case.

You wanted to add or subtract the durations to and from Instant or LocalDateTime. This works in most cases. Let’s see:

    String[] timeAmountStrings = { "1m", "5M", "3D", "30m", "2h", "1Y", "3W" };
    LocalDateTime base = LocalDateTime.of(2019, Month.MARCH, 1, 0, 0);
    for (String tas : timeAmountStrings) {
        TemporalAmount amount = parse(tas);
        System.out.println("String: " + tas + " parsed: " + amount + " added: " + base.plus(amount));

        try {
            System.out.println("Added to Instant: " + Instant.EPOCH.plus(amount));
        } catch (DateTimeException dte) {
            System.out.println("Adding to Instant didn’t work: " + tas + ' ' + dte);
        }

        System.out.println();
    }

Output:

String: 1m parsed: PT1M added: 2019-03-01T00:01
Added to Instant: 1970-01-01T00:01:00Z

String: 5M parsed: P5M added: 2019-08-01T00:00
Adding to Instant didn’t work: 5M java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Months

String: 3D parsed: P3D added: 2019-03-04T00:00
Added to Instant: 1970-01-04T00:00:00Z

String: 30m parsed: PT30M added: 2019-03-01T00:30
Added to Instant: 1970-01-01T00:30:00Z

String: 2h parsed: PT2H added: 2019-03-01T02:00
Added to Instant: 1970-01-01T02:00:00Z

String: 1Y parsed: P1Y added: 2020-03-01T00:00
Adding to Instant didn’t work: 1Y java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Years

String: 3W parsed: P21D added: 2019-03-22T00:00
Added to Instant: 1970-01-22T00:00:00Z

We see that adding to LocalDateTime works in all cases. Adding to Instant works in most cases, only we cannot add a period of months or years to it.

like image 71
Ole V.V. Avatar answered Jan 10 '23 18:01

Ole V.V.


The Answer by Ole V.V. is correct, clever, and well-done. I would take it one step further.

PeriodDuration

The ThreeTen-Extra project adds functionality to the java.time classes built into Java 8 and later. Among its offerings is the PeriodDuration class, combining a Period (years-month-days) with a Duration (hours-minutes-seconds-nanos).

By the way, let me caution you about combining the two concepts. While that may seem fine intuitively, if you ponder a bit you may see it as problematic depending on your business logic.

Anyways, let’s adapt the code seen in the other Answer.

  • If the input is uppercase, we create a string in standard ISO 8601 duration format, and parse it as a Period, adding that result to our PeriodDuration object.
  • If the input is lowercase, we create a string in standard ISO 8601 duration format, parse it as a Duration, and add the result to our PeriodDuration object.

Code:

List < String > inputs = List.of( "1m" , "5M" , "3D" , "30m" , "2h" , "1Y" , "3W" );
PeriodDuration pd = PeriodDuration.ZERO;
for ( String input : inputs )
{
    String s = input.trim();
    String lastLetter = s.substring( s.length() - 1 );
    int codepoint = Character.codePointAt( lastLetter , 0 );
    if ( Character.isUpperCase( codepoint ) )
    {
        String x = "P" + s;
        Period p = Period.parse( x );
        pd = pd.plus( p );
    } else
    { // Else, lowercase.
        String x = "PT".concat( s ).toUpperCase();
        Duration d = Duration.parse(x );
        pd = pd.plus( d );
    }
}
System.out.println( "pd.toString(): " + pd );

P1Y5M24DT2H31M

like image 36
Basil Bourque Avatar answered Jan 10 '23 19:01

Basil Bourque