18

The Period class in java.time handles only the date-oriented potion: years, months, days.

What about the time portion: hours, minutes, seconds?

How can we parse and generate string representations of full periods as defined in ISO 8601, PnYnMnDTnHnMnS? For example, a day and a half: P1DT12H. The academic year is nine months, P9M. Every year I get two weeks and 3 days of vacation, P17D. The customer occupied the hotel room for 2 days and seventeen and a half hours, P2DT17H30M.

The Period class in Joda-Time handles full period. Why not in java.time? Is there some other mechanism?

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • 1
    @Codebender Duration is a different concept in java.time. And Duration is not limited to less than a day. And Duration cannot be combined with Period to form a full day, at least not that I know. – Basil Bourque Sep 03 '15 at 04:35
  • I meant to say Duration and Period cannot be combined to create a full period ( not "day"). – Basil Bourque Sep 03 '15 at 05:15
  • Got it... So you want to represent something like `2 and a half days`. Or `2 days and 1 hour`. Not `49 hours`. – Codebender Sep 03 '15 at 05:23
  • 1
    @Codebender Yes, exactly. See Joda-Time, which has three different ways to define spans of time: Period, Duration, and Interval. Duration is the simplest, a number of milliseconds, not tied to the timeline. Period is a count of years, and a count of months, and a count of days, and a count of hours, and so on, not tied to the timeline. As in, "the academic year is nine months", or "every year I get two weeks and three days of vacation". Interval is a pair of points on the timeline, specific date-time values. – Basil Bourque Sep 03 '15 at 05:53
  • May I ask you what is your use-case? I think it would be easier to give a concrete answer related to a use-case so answerers could describe a workaround for example, otherwise we can only say "no not possible" and have to guess why JSR-310-team did not want to realize that feature. – Meno Hochschild Sep 03 '15 at 08:26
  • @MenoHochschild One use case is parsing and generating those ISO 8601 `P` strings. – Basil Bourque Sep 04 '15 at 15:14
  • Well, this was and is possible even without java.time but with standard JDK, see [xml-duration](http://docs.oracle.com/javase/8/docs/api/javax/xml/datatype/DatatypeFactory.html#newDuration-java.lang.String-) – Meno Hochschild Sep 04 '15 at 21:34
  • @MenoHochschild Wow, you really pulled a bunny out of hat on that one! Do you know how well that class works? Any issues or limitations? Please make your comment an Answer. – Basil Bourque Sep 04 '15 at 22:29
  • Dear Down-Voter, please leave a criticism along with your vote. – Basil Bourque Jan 29 '18 at 08:08

3 Answers3

16

In Java SE 8, it is the responsibility of the application to create a class linking Period and Duration if that is needed.

Note that a Duration contains an amount of seconds, not separate amounts of seconds, minutes and hours. The amount of seconds can exceed 24 hours, thus a Duration can represent a "day". But it is a fixed 24 hours day. By contrast, the representation of a "day in Period is descriptive and takes into account DST. The state of a Period is formed from three separate fields - days, months and years.

Bear in mind that "The customer occupied the hotel room for 2 days and seventeen and a half hours, P2DT17H30M" has the possibility to be complicated by DST cutovers. Using Period and Duration separately things are clear - Period is affected by DST cutovers and Duration is not.

In design terms, the original java.time Period did include hours, minutes and seconds. However, this resulted in the need for many methods and complicated Javadoc to describe all the possibilities around normalization and DST. By separating the concepts, the interaction of each with the timeline is a lot clearer. Note that the two classes also relate to the SQL design ("year to month" and "day to second" concepts).

There are no current plans to add a new class for Java SE 9in this area, however it cannot be completely ruled out because XML/ISO-8601 allows a single combined representation.

JodaStephen
  • 60,927
  • 15
  • 95
  • 117
  • 1
    Thanks for explaining the rationale. But... what's then the correct way to handle the full duration? E.g. if I need _start time plus a period plus a duration_, would I have to do something like: `String[] periodAndDuration = isoStartTime.split("T"); Period period = Period.parse(periodAndDuration[0]); Duration duration = Duration.parse("PT" + periodAndDuration[1]);` Seems awfully unwieldy... – kaqqao Aug 11 '17 at 12:19
  • See the answer by Basil Borque below where the necessary class has been added to the ThreeTen-Extra library. – JodaStephen Aug 13 '17 at 15:29
  • tl;dr day may consist of 25 hours during a daylight saving shift, creating ambiguity for the hour that occurs twice during same day – Klesun Aug 01 '20 at 13:43
7

org.threeten.extra.PeriodDuration

The ThreeTen-Extra project offers a class combining a Period and a Duration. Simply called PeriodDuration.

An amount of time in the ISO-8601 calendar system that combines a period and a duration.

This class models a quantity or amount of time in terms of a Period and Duration. A period is a date-based amount of time, consisting of years, months and days. A duration is a time-based amount of time, consisting of seconds and nanoseconds. See the Period and Duration classes for more details.

The days in a period take account of daylight saving changes (23 or 25 hour days). When performing calculations, the period is added first, then the duration.

Caveat: Be sure to read the Answer by JodaStephen to understand the issues involved in trying to combine Period and Duration. It rarely makes sense to do so in practice, though that is counter to our intuition.

FYI, ThreeTen-Extra, java.time in JSR 310, and Joda-Time are all led by the same man, Stephen Colebourne a.k.a. JodaStephen.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
5

Short answer related to java.time (JSR-310):

No, that package does not offer a solution.

Alternatively, you can use the class Duration in the package javax.xml.datatype for parsing strings like PnYnMnDTnHnMnS. This is also available in older JDK-versions since Java-5. Example:

// parsing
String iso = "P2Y4M30DT17H5M57.123S";
javax.xml.datatype.Duration xmlDuration =
    DatatypeFactory.newInstance().newDuration(iso);
int years = xmlDuration.getYears();
int months = xmlDuration.getMonths();
int days = xmlDuration.getDays();
int hours = xmlDuration.getHours();
int minutes = xmlDuration.getMinutes();
int fullSeconds = xmlDuration.getSeconds();
BigDecimal seconds = (BigDecimal) xmlDuration.getField(DatatypeConstants.SECONDS);

// generating ISO-string
String xml = xmlDuration.toString();
System.out.println(xml); // P2Y4M30DT17H5M57.123S

If you ask for limitations/issues, well, here you get a list:

  • Some (alternative) ISO-formats like P0001-04-20T4H cannot be parsed.

  • Some methods defined in javax.xml.datatype.Duration rely on an internal Calendar-instance (documented) so that those methods might not work if an instance of Duration holds very large values.

  • Working with fractional seconds might be awkward and sometimes limited in precision if operating on a Calendar-instance.

  • There is only one single normalization method using a Calendar-instance. At least this method takes into account DST-effects in a standard way.

  • Formatting (not even to mention localized printing) is not offered.

If you want to overcome those issues then you can consider an external library (and yes, I don't only think of Joda-Time whose precision is constrained to millisecs and whose internationalization is limited, too). Otherwise the package javax.xml.datatype has the advantage to save the effort to embed an external library into your classpath.

Update:

About the question in comment related to external libraries, I know Joda-Time and my library Time4J.

First one (Joda-Time) offers a special class called ISOPeriodFormat. This class is also able to parse alternative ISO-formats (although PyyyyWwwddThhmmss is not mentioned in original ISO-8601-paper while support for PYYYY-DDD is missing). Joda-Time defines a builder-driven approach for period formatters which can also be used for printing durations (periods). Furthermore, there is a limited support for localized printing (with version 2.9.3 of Joda-Time in 13 languages). Finally the class Period offers various normalization methods (see javadoc).

Second one (Time4J) offers the classes net.time4j.Duration and two formatting tools (Duration.Formatter for pattern-based printing/parsing and net.time4j.PrettyTime for localized printing in actually 78 languages). The class Duration offers for parsing ISO-strings the static method parsePeriod(String) and also various normalizing methods. Example for the interoperability with java.time (JSR-310) proving that this library can be considered and used as powerful extension of new java-8-date-time-api:

// input: using java.time-package
LocalDateTime start = LocalDateTime.of(2016, 3, 7, 10, 15, 8);
LocalDateTime stop = LocalDateTime.of(2016, 6, 1, 22, 15);

// define how you measure the duration (zone correction would also be possible)
Duration<?> duration =
    TimestampInterval.between(start, stop).getDuration(
        CalendarUnit.YEARS,
        CalendarUnit.MONTHS,
        CalendarUnit.DAYS,
        ClockUnit.HOURS,
        ClockUnit.MINUTES,
        ClockUnit.SECONDS
    );

// generate standard ISO-representation
String s = duration.toStringISO();
System.out.println(s); // P2M25DT11H59M52S

// parse ISO-String and prove equality with original
System.out.println(Duration.parsePeriod(s).equals(duration)); // true

// adding duration to <start> yields <stop>
System.out.println(start.plus(duration.toTemporalAmount())); // 2016-06-01T22:15

// format in human time
System.out.println(PrettyTime.of(Locale.US).print(duration));
// output: 2 months, 25 days, 11 hours, 59 minutes, and 52 seconds

For completeness I should also mention ocpsoft.PrettyTime but I am not sure if that library is able to process ISO-strings. It is rather designed for relative times.

Meno Hochschild
  • 42,708
  • 7
  • 104
  • 126