I have two LocalDate
s declared as following:
val startDate = LocalDate.of(2019, 10, 31) // 2019-10-31
val endDate = LocalDate.of(2019, 9, 30) // 2019-09-30
Then I calculate the period between them using Period.between
function:
val period = Period.between(startDate, endDate) // P-1M-1D
Here the period has the negative amount of months and days, which is expected given that endDate
is earlier than startDate
.
However when I add that period
back to the startDate
, the result I'm getting is not the endDate
, but the date one day earlier:
val endDate1 = startDate.plus(period) // 2019-09-29
So the question is, why doesn't the invariant
startDate.plus(Period.between(startDate, endDate)) == endDate
hold for these two dates?
Is it Period.between
who returns an incorrect period, or LocalDate.plus
who adds it incorrectly?
If you look how plus
is implemented for LocalDate
@Override
public LocalDate plus(TemporalAmount amountToAdd) {
if (amountToAdd instanceof Period) {
Period periodToAdd = (Period) amountToAdd;
return plusMonths(periodToAdd.toTotalMonths()).plusDays(periodToAdd.getDays());
}
...
}
you'll see plusMonths(...)
and plusDays(...)
there.
plusMonths
handles cases when one month has 31 days, and the other has 30. So the following code will print 2019-09-30
instead of non-existent 2019-09-31
println(startDate.plusMonths(period.months.toLong()))
After that, subtracting one day results in 2019-09-29
. This is the correct result, since 2019-09-29
and 2019-10-31
are 1 month 1 day apart
The Period.between
calculation is weird and in this case boils down to
LocalDate end = LocalDate.from(endDateExclusive);
long totalMonths = end.getProlepticMonth() - this.getProlepticMonth();
int days = end.day - this.day;
long years = totalMonths / 12;
int months = (int) (totalMonths % 12); // safe
return Period.of(Math.toIntExact(years), months, days);
where getProlepticMonth
is total number of months from 00-00-00. In this case, it's 1 month and 1 day.
From my understanding, it's a bug in a Period.between
and LocalDate#plus
for negative periods interaction, since the following code has the same meaning
val startDate = LocalDate.of(2019, 10, 31)
val endDate = LocalDate.of(2019, 9, 30)
val period = Period.between(endDate, startDate)
println(endDate.plus(period))
but it prints the correct 2019-10-31
.
The problem is that LocalDate#plusMonths
normalises date to be always "correct". In the following code, you can see that after subtracting 1 month from 2019-10-31
the result is 2019-09-31
that is then normalised to 2019-10-30
public LocalDate plusMonths(long monthsToAdd) {
...
return resolvePreviousValid(newYear, newMonth, day);
}
private static LocalDate resolvePreviousValid(int year, int month, int day) {
switch (month) {
...
case 9:
case 11:
day = Math.min(day, 30);
break;
}
return new LocalDate(year, month, day);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With