ChronoUnit.MONTHS: inconsistency between addTo() and between()

99 views Asked by At

In some cases, ChronoUnit.MONTHS perform math on dates inconsistently depending if we add or subtract. Corner case is when we e.g. add 1 month to a date 2023-01-31. I understand there is no single, correct answer if the result should be 2023-02-28 or 2023-03-01. However, I would expect the same API to at least use the same assumptions, which is not the case here:

var d1 = OffsetDateTime.parse("2023-01-31T10:00:00Z");

// add 1 month
var d2 = ChronoUnit.MONTHS.addTo(d1, 1);
// add 23 hours
d2 = ChronoUnit.HOURS.addTo(d2, 23);

// subtract
System.out.println(ChronoUnit.MONTHS.between(d1, d2)); // 0

Apparently, addTo() assumes 1 month from 2023-01-31 10:00 is 2023-02-28 10:00, however, between() assumes one full month ends at 2023-03-01 10:00.

Is this a bug? Do I miss or misunderstand something?

BTW, d1.plusMonths(1) works in the same way as addTo() and this behavior is even clearly documented. between() seems to be inconsistent with other operations.

EDIT:

I understand the date math is tricky, it isn't exactly "math", calendar dates don't flow linearly and we can't treat them as adding/subtracting just numbers. But still, I find some examples extremely counter-intuitive:

  • Between 2023-01-31 11:00 and 2023-03-01 10:00 there are 0 full months, while there is literally a full calendar month between these dates.
  • There are 5 months between 2023-05-31 11:00 and 2023-12-01 10:00, but if we add 6 months to the first date, we get... 2023-11-30 11:00, so older date than the one that is 5 months away.
1

There are 1 answers

1
Arvind Kumar Avinash On

It's not a bug. It is documented as follows:

The calculation returns a whole number, representing the number of complete units between the two temporals. For example, the amount in hours between the times 11:30 and 13:29 will only be one hour as it is one minute short of two hours.

In the following code, d2 becomes 2023-02-28T10:00Z but when it calculates the difference as per the above documentation, it would have counted the difference as 1 if a date such as 2023-02-31T10:00:00Z existed. If not, it counts the difference as 1 for the next day which is 2023-03-01T10:00:00Z.

var d1 = OffsetDateTime.parse("2023-01-31T10:00:00Z");
var d2 = ChronoUnit.MONTHS.addTo(d1, 1);
System.out.println(ChronoUnit.MONTHS.between(d1, d2)); // 0

When you add 23 hours in the next statement, it becomes 2023-03-01T09:00Z which is one hour less than 2023-03-01T10:00:00Z; therefore it does not reach the point to count the difference as 1 month.

d2 = ChronoUnit.HOURS.addTo(d2, 23); // 2023-03-01T09:00Z

Had you added 24 hours, it would have become 2023-03-01T10:00:00Z and given you the difference as 1.

This behaviour is consistent across all the time units.