Parsing the ISO-8601 duration values of the AMAZON.DURATION slot type

4.9k views Asked by At

Does the java.time library provide a consolidated way to parse the entire ISO-8601 Duration Specification?

The Alexa Slot Type reference for duration lists some example strings to expect when using the AMAZON.DURATION slot type. All strings are in ISO-8601 Duration, but P2YT3H10 cannot be parsed by either java.time.Period or java.time.Duration.

Seq(
 "PT10M",
 "PT5H",
 "P3D",
 "PT45S",
 "P8W",
 "P7Y",
 "PT5H10M",
 "P2YT3H10"
).map { s =>
 s -> Try {
   try {
     Period.parse(s)
   } catch {
     case ex: Throwable => Duration.parse(s)
   }
 }.map(x => x.toString -> x.getClass.getSimpleName)
}
.foreach(println)

Results:

(PT10M,Success((PT10M,Duration)))
(PT5H,Success((PT5H,Duration)))
(P3D,Success((P3D,Period)))
(PT45S,Success((PT45S,Duration)))
(P8W,Success((P56D,Period)))
(P7Y,Success((P7Y,Period)))
(PT5H10M,Success((PT5H10M,Duration)))
(P2YT3H10,Failure(java.time.format.DateTimeParseException: Text cannot be parsed to a Duration))
3

There are 3 answers

0
Meno Hochschild On BEST ANSWER

Hugos answer is only related to one detail aspect of the ISO-8601-standard, namely the combination of date and time part with a "T"-separator. This detail is indeed not supported by java.time but supported by the external library Threeten-Extra (using the class PeriodDuration in the version v1.2). However:

The ISO-8601-standard describes even more variations.

One variation is the usage of weeks in the form "P8W". Both java.time and Threeten-Extra automatically change it to "P56D" but don't leave it in week-related form when parsing and formatting. A similar view can be obtained when looking at the time components. java.time.Duration cannot directly store hours or minutes but automatically convert it to seconds. When formatting an automatical normalization is performed, too. Example:

System.out.println(Duration.parse("PT4200S")); // PT1H10M

So in both cases: What you put inside during construction is not always what you get when formatting.

Other points:

  • Duration representations in ISO-8601 (as specified in section 4.4.4.2) are always called "Duration" (even if date-related) while java.time uses two different terms, namely Period and Duration (either date-related or time-related).
  • Week-related durations are handled separately since two standard ways in ISO-8601-notation are described: "PnnYnnMnnDTnnHnnMnnS" or "PnnW".
  • Signs don't exist in ISO-8601-specification (see section 3.4.2: symbol "n" defined as positive integer or zero) while java.time allows signs even inside the representations and interprete signs as component-related. Note that XML-Schema only handles signs preceding the whole duration expression (before "P"), that is not component-related but duration-related.
  • Alternative representations of durations are also specified, either in basic or in extended format: "PYYYYMMDDThhmmss" resp. "PYYYY-MM-DDThh:mm:ss".
  • Handling of fractional seconds: ISO-8601 speaks about usage of comma or dot (and even explicitly prefers the comma) while java.time.Duration only supports the dot.

Finally we can state:

The ISO-8601-standard is only partially supported by java.time (and Threeten-Extra which only supports the combination of the classes Period and java.time.Duration as additional detail).

Alternative solution for true ISO-8601-support:

If you want or even need to overcome the limitations baked into java.time then you can use my library Time4J which supports all the additional details of ISO-8601. See the API of class net.time4j.Duration. Following examples also show the compatibility with java.time:

Duration<CalendarUnit> d1 = Duration.parseCalendarPeriod("P8W");
System.out.println(d1); // P8W
System.out.println(d1.getPartialAmount(CalendarUnit.WEEKS)); // 8
System.out.println(Duration.Formatter.ofPattern(CalendarUnit.class, "W' weeks'").format(d1)); // 8 weeks
System.out.println(PrettyTime.of(Locale.GERMAN).print(d1)); // 8 Wochen
LocalDate ld = LocalDate.of(2017, 9, 17);
System.out.println(PlainDate.from(ld).plus(d1)); // 2017-11-12
System.out.println(PlainDate.of(2017, 9, 17).plus(d1)); // 2017-11-12

Duration<IsoUnit> d2 = Duration.parsePeriod("P2DT5H10M");
LocalDateTime ldt = LocalDateTime.of(2017, 9, 17, 19, 15);
System.out.println(PlainTimestamp.from(ldt).plus(d2)); // 2017-09-20T00:25
System.out.println(PlainTimestamp.of(2017, 9, 17, 19, 15).plus(d2)); // 2017-09-20T00:25
System.out.println(PrettyTime.of(Locale.GERMAN).print(d2)); // 2 Tage, 5 Stunden und 10 Minuten

Duration<IsoUnit> d3 = Duration.parsePeriod("P0001-01-02T05:10:04");
System.out.println(d3); // P1Y1M2DT5H10M4S
LocalDateTime ldt = LocalDateTime.of(2017, 9, 17, 19, 15);
System.out.println(PlainTimestamp.from(ldt).plus(d3)); // 2018-10-20T00:25:04

Side note: Formatting of durations is possible in actually 86 languages.

0
AudioBubble On

PS: your input is missing the designator for the last field (P2YT3H10- 2 years, 3 hours and 10 what?). So, in the code below, I just assumed that it was M (minutes) - it won't work without a designator after 10.


The java.time API has separated the ISO8601 duration in 2 classes:

  • java.time.Period, that can handle date-based fields (years, months and days)
  • java.time.Duration, that can handle time-based fields* (actually, according to javadoc: "This class models a quantity or amount of time in terms of seconds and nanoseconds. It can be accessed using other duration-based units, such as minutes and hours")

Unfortunately, those classes can't handle an ISO8601 duration with both date and time based fields.

One alternative is to use the ThreeTen Extra project, that contains some extensions for java.time API. With this lib, you can use the org.threeten.extra.PeriodDuration class, which can parse full ISO8601 durations:

PeriodDuration pd = PeriodDuration.parse("P2YT3H10M");

Then you can get the respective Period and Duration from it:

System.out.println(pd.getPeriod()); // P2Y
System.out.println(pd.getDuration()); // PT3H10M

Another alternative is to split the String, and parse both Period and Duration separately:

// split in 2 parts
String input = "P2YT3H10M";
String[] v = input.split("T");
Period p;
Duration d;
if (v.length == 1) { // has only date-based fields
    p = Period.parse(input);
    d = Duration.ZERO;
} else {
    if ("P".equals(v[0])) { // has only time-based fields
        p = Period.ZERO;
    } else {
        p = Period.parse(v[0]);
    }
    d = Duration.parse("PT" + v[1]);
}

*A Duration also accepts days, and those are always considered to have 24 hours (in a Period, a day doesn't necessarily have 24 hours due to Daylight Saving Time effects).

0
Pablo Pazos On

Java 8 Duration only allows days, hours, minutes and seconds:

The formats accepted are based on the ISO-8601 duration format PnDTnHnMn.nS

https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html#parse-java.lang.CharSequence-