2

I was experiencing some unexpected behaviors when doing date/time calculations. When it comes to daylight savings or leap years, the order of operations (months/days/hours) does matter, which is rather obvious. But it seems that the operation order is not deterministic when it comes to addition and substraction of java.time.Period?

To make more clear what I mean here an example, where I add and substract the same period from a date:

public static void main(String[] args) {
    Period period = Period.ofDays(365).plus(Period.ofYears(1));
    LocalDate dateA = LocalDate.of(2020, 4, 24);
    LocalDate dateA2 = dateA.minus(period).plus(period);
    LocalDate dateA3 = dateA.plus(period).minus(period);
    System.out.println(dateA);
    System.out.println(dateA2);
    System.out.println(dateA3);

    System.out.println("---");

    LocalDate dateB = LocalDate.of(2022, 4, 24);
    LocalDate dateB2 = dateB.minus(period).plus(period);
    LocalDate dateB3 = dateB.plus(period).minus(period);
    System.out.println(dateB);
    System.out.println(dateB2);
    System.out.println(dateB3);
}

the output is

2020-04-24
2020-04-23
2020-04-24
---
2022-04-24
2022-04-24
2022-04-23

I was expecting that addition and substraction negate each other, so adding and substracting the same Period would always result in the initial date, but it does not. (It would require to swap the execution order from years/months/days when adding to days/months/years when substracting i assume)

Is this a bug or expected?

Florian
  • 21
  • 2
  • Is your arithmetic on the dates crossing a daylight savings boundary? – user207421 Apr 24 '23 at 10:09
  • @user207421 Depending on interpretation either "no" or "that shouldn’t matter". The OP is using `LocalDate`, and `LocalDate` hasn’t got any time zone and thus is not able to be influenced by summer time (DST). – Ole V.V. Apr 24 '23 at 10:51
  • It’s a guess only: Maybe when subtracting the year is subtracted first, then the 265 days. And when adding the year is added first, then the 365 days. This means in the case of date A when subtracting you are subtracting the year across the leap day in 2020, that is 366 days, but adding only 365 days when going in the opposite direction. This causes you to end up 1 day short. For date B you have the same issue when crossing the leap day in 2024. – Ole V.V. Apr 24 '23 at 10:55
  • The documentation confirms my guess. The operations are implemented by calling [`Period.addTo()`](https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/time/Period.html#addTo(java.time.temporal.Temporal)) and `Period.subtractFrom()`. From the documentation of the former: *... if the months are zero, the years are added if non-zero, otherwise the combination of years and months is added if non-zero. Finally, any days are added.* Same for the latter, it subtracts the year first and finally the days. – Ole V.V. Apr 24 '23 at 11:02
  • Try it with `Duration` instead – g00se Apr 24 '23 at 11:03
  • @g00se That does not work. `LocalDate.now().plus(Duration.ofDays(365))` gives *java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Seconds*. A `Duration` is time-based, think hours, minutes, seconds and fraction of second, and hence incompatible with `LocalDate`, which knows only days, months and years. – Ole V.V. Apr 24 '23 at 11:06
  • Right, yes I see – g00se Apr 24 '23 at 11:11
  • *Is this a bug or expected?* It surprised me too. On one hand date math is very often not well-defined, and probably other date-time libraries would be more in line with your expectations and mine. For example subtracting the days first and the years and months next would yield consistent results in your example situation, but still not in all situations. I would consider it more correct. On the other hand as I said, your observations agree with the documentation. So if a bug, then a bug in the design/requirements. – Ole V.V. Apr 24 '23 at 11:38

0 Answers0