Florian
Florian

Reputation: 31

Unexpected behavior when adding and substracting Periods from Dates

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?

Upvotes: 3

Views: 117

Answers (1)

Pistolnikus
Pistolnikus

Reputation: 366

TL;DR

The plus/minus(period) methods of LocalDate first add/subtract months, then days. This can lead to initially surprising behavior, especially when crossing a leap day.

Details

Implementation of LocalDate's method plus is as follows:

if (amountToAdd instanceof Period periodToAdd) {
    return plusMonths(periodToAdd.toTotalMonths()).plusDays(periodToAdd.getDays());
}
...

A similar approach is taken for the minus method.

For a period specified in the question:

Period period = Period.ofDays(365).plus(Period.ofYears(1));

it'll first subtract(add) 12 months and then subtract(add) 365 days.

This is why dateA.minus(period).plus(period) results in 2020-04-23 (and not in original date - 2020-04-24). When subtracting, we cross over 2020-02-29 via the minusMonths path (thus skipping 29 days as one month). However, on the return journey, we cross it via the plusDays path (resulting in one day "missing").

Example

Illustrated in four steps:

LocalDate dateA = LocalDate.of(2020, 4, 24);
// 2020-04-24

LocalDate dateAMinusMonths = dateA.minusMonths(12);
// 2019-04-24

LocalDate dateAMinusMonthsMinusDays = dateAMinusMonths.minusDays(365);
// 2018-04-24

LocalDate dateAMinusMonthsMinusDaysPlusMonths = dateAMinusMonthsMinusDays.plusMonths(12);
// 2019-04-24

LocalDate dateAMinusMonthsMinusDaysPlusMonthsPlusDays = dateAMinusMonthsMinusDaysPlusMonths.plusDays(365);
// 2020-04-23

Similarly, adding 365 days to any date may not always result in the same date and day in the next year.

Bug? It depends...

On one hand, it seems odd that adding and subtracting the same period does not lead to the same date; there is a lack of symmetry. This symmetry could be achieved if we subtracted days first when subtracting, and then subtracted months.

On the other hand, there is a symmetry in that it does not matter whether we add or subtract periods; we always add/subtract months first (if applicable), then days.

Upvotes: 0

Related Questions