3

IIUC java.time should end multiple date libraries and related issues, and offer something good and standardized. So it was kinda shock to me to learn, that java.time.Duration violates iso8601 standard. While it should be possible to parse: P2Y1DT1S, as that is valid by standard, java.time.Duration will parse only up to days (so you have to workaround via P731DT1S and java.time.Period can't do that at all, since it does not allow temporal part.

Questions:

  • Why? Is there a valid reason not to have interval parsing according to specification? What am I overlooking?

  • Or is there some readily available class, which will parse ISO8601?

Martin Mucha
  • 2,385
  • 1
  • 29
  • 49
  • The documentation for class `java.time.Duration` does **not** state that the class fully implements ISO 8601 so why do you think that it should? Have you seen this? [ISO 8601 Time Interval Parsing in Java](https://stackoverflow.com/questions/15977637/iso-8601-time-interval-parsing-in-java) – Abra Aug 11 '22 at 13:56
  • 1
    Note that `java.time` has `Duration` _and_ `Period`. – Slaw Aug 11 '22 at 14:03
  • @Abra we have iso for time,date duration etc. I'd expect someone not being creative but implement specification *first*, and bring his own ideas only after that. If I have customer, who wants to enter duration by specification, the current java implementation is fruitless defying the purpose of having specification. If we want best use out of standard library functions, it would be best if we have common specifications related methods always ready first, and speculative-ideas-based ones later or not at all(in sdk) – Martin Mucha Aug 11 '22 at 17:29
  • @Slaw sure, sure, but say I have this iso8601 valid duration string: "P2MT17H30M" and this one cannot be parsed by neither. Not great. – Martin Mucha Aug 11 '22 at 17:33

2 Answers2

3

Well, parsing such a string means it yields an object which supports both concepts to be present at the same time – a sort of PeriodDuration. But there is none in Java, and its rationale is explained here by JodaStephen:

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.

(Emphasis mine.)

So in short, the two concepts handle DST differently, and combining the two complicates things.


Regarding your second question, this answer suggests the org.threeten.extra.PeriodDuration class offered by the ThreeTen-Extra library.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
MC Emperor
  • 22,334
  • 15
  • 80
  • 130
  • Thanks for explanation and suggestion. Regarding validity of JodaStephen rationale — I'd really like to know, how his Period takes into account DST. I mean if I say 3 years, 2 months and a day, there is NO way to determine, if there should be dst adjustment or leap day for year, and this is NO concern of Duration class. We know exactly how is this duration long(3+2+1). I'd say, this is poor OO design. "The customer occupied the hotel room for 2 days and seventeen and a half hours" occupied the room for exactly P2DT17H30M period. Adding this duration to localdatetime might be complicated, sure. – Martin Mucha Aug 11 '22 at 17:25
  • @MartinMucha Regarding Daylight Saving Time (DST) adjustment with `Period`, you misunderstand both that class and the nature of DST. The `Period` class represents a span of time *unattached* from the timeline. So DST is irrelevant. DST is a political decision made in a local jurisdiction. So DST applies only to certain date-times in a particular time zone. A `Period` has no date, no time-of-day, and no time zone. So your statement "The customer occupied the hotel room for 2 days and seventeen and a half hours" has no meaning without specific dates, starting and ending times, & a time zone. – Basil Bourque Aug 27 '22 at 04:29
  • @MartinMucha Your hotel room occupation example in [your Comment](https://stackoverflow.com/questions/73321462/java-time-duration-implementation-vs-iso8601-standard/73508526#comment129491676_73322228) inspired me to add [my own Answer](https://stackoverflow.com/a/73508526/642706) with that as an example. – Basil Bourque Aug 27 '22 at 05:12
  • @BasilBourque can you elaborate what I understood wrong about DST? I deeply believe that neither Duration nor Period is related to DST, while both has possibility to be complicated by dst cutovers, but in the end, 'customer occupying room 2d17h30m means exactly that every time. But in this answer JodaStephen is quoted explaining why it's valid, that period is affected by dst, duration not, and class supporting ISO is not available in JDK. I tried to say, that both (dst-related impl, iso-compliant class absent) is bad and I cannot see valid justification for that. – Martin Mucha Aug 28 '22 at 11:28
1

tl;dr

Use the org.threeten.extra.PeriodDuration class.

PeriodDuration
        .between(
                LocalDate
                        .of( 2023 , Month.JANUARY , 23 )
                        .atTime( LocalTime.NOON )
                        .atZone( ZoneId.of( "America/New_York" ) ) ,
                LocalDate
                        .of( 2023 , Month.JANUARY , 23 )
                        .atTime( LocalTime.NOON )
                        .atZone( ZoneId.of( "America/New_York" ) )
                        .plusDays( 2 )
                        .plusHours( 17 )
                        .plusMinutes( 30 )
        )
        .normalizedStandardDays()
        .toString()

P2DT17H30M

Details

The Answer by MC Emperor is correct and wise. Always think twice before making use of a time span combining years-months-days with hours-minutes-seconds.

PeriodDuration

But if you insist on that combination, there is a class for that. You will find it in the ThreeTen-Extra library. This library is led by the same man, Stephen Colebourne, who leads the java.time (JSR 310) framework and its predecessor, Joda-Time.

The org.threeten.extra.PeriodDuration class represents an amount of time in the ISO 8601 calendar system that combines a period and a duration.

Here is some example code.

ZoneId z = ZoneId.of( "America/New_York" );
LocalDate ld = LocalDate.of( 2023 , Month.JANUARY , 23 );
LocalTime lt = LocalTime.NOON;
ZonedDateTime start = ZonedDateTime.of( ld , lt , z );
ZonedDateTime end = start.plusDays( 2 ).plusHours( 17 ).plusMinutes( 30 );
PeriodDuration pd = PeriodDuration.between( start , end );

Dump to console.

System.out.println( start + "/" + end + " = " + pd );

When run:

2023-01-23T12:00-05:00[America/New_York]/2023-01-26T05:30-05:00[America/New_York] = P3DT-6H-30M

Notice in that result we have negative amounts for hours and minutes. This is because the two-phase logic:

  • First, examine dates, LocalDate objects. Use Period.between to count days where the beginning is inclusive while the ending is exclusive. From the 23rd to the 26th is 3 days.
  • Second, examine time-of-day values, LocalTime objects. Use Duration.between to calculate elapsed time. We are calculating a duration between noon and 5:30 AM, so we must go back in time six and a half hours.

Therefore our result is 3 days minus 6.5 hours.

You can see this logic in the open source code.

If you want to smooth out this result to flip the negative hours-minutes, add call to PeriodDuration#normalizedStandardDays().

PeriodDuration pd = PeriodDuration.between( start , end ).normalizedStandardDays();

2023-01-23T12:00-05:00[America/New_York]/2023-01-26T05:30-05:00[America/New_York] = P2DT17H30M

P2DT17H30M = P3DT-6H-30M

We now get 2 days and 17.5 hours, P2DT17H30M. This is the same amount of time as 3 days minus 6.5 hours, P3DT-6H-30M.

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