Reputation: 151
Using ZonedDateTime.until
returns a wrong amount of months for a given date until a given end date. Looks like something to do with February ;)
This is the test I have written
@Test
fun `three months from end of november till first of march`() {
val dateOfNow = LocalDate.of(2022, 11, 30)
val timeOfNow = LocalTime.of(0, 0, 0, 1)
val dateTimeOfNow = LocalDateTime.of(dateOfNow, timeOfNow)
val timeZoneOfTestland = ZoneOffset.of("+00:00")
val zonedDateTimeOfNow = dateTimeOfNow.atZone(timeZoneOfTestland)
val dateEnd = LocalDate.of(2023, 3, 1)
val timeEnd = LocalTime.of(0, 0, 0, 0)
val dateTimeEnd = LocalDateTime.of(dateEnd, timeEnd)
val zonedDateEnd = dateTimeEnd.atZone(timeZoneOfTestland)
val until = zonedDateTimeOfNow.until(zonedDateEnd, ChronoUnit.MONTHS)
assertEquals(3, until)
}
And it returns 2 instead of expected 3.
The test succeeds when I change nano seconds of dateTimeOfNow
val timeOfNow = LocalTime.of(0, 0, 0, 0)
But it fails again when I set the date to Nov 29th
val dateOfNow = LocalDate.of(2022, 11, 29)
val timeOfNow = LocalTime.of(0, 0, 0, 1)
In my point of view, it's clearly 3 months until March 1st 2023. In any case.
I tried it without year change using these dates
val dateOfNow = LocalDate.of(2022, 1, 31)
val timeOfNow = LocalTime.of(0, 0, 0, 1)
val dateTimeOfNow = LocalDateTime.of(dateOfNow, timeOfNow)
val timeZoneOfTestland = ZoneOffset.of("+00:00")
val zonedDateTimeOfNow = dateTimeOfNow.atZone(timeZoneOfTestland)
val dateEnd = LocalDate.of(2022, 5, 1)
val timeEnd = LocalTime.of(0, 0, 0, 0)
val dateTimeEnd = LocalDateTime.of(dateEnd, timeEnd)
val zonedDateEnd = dateTimeEnd.atZone(timeZoneOfTestland)
val until = zonedDateTimeOfNow.until(zonedDateEnd, ChronoUnit.MONTHS)
assertEquals(3, until)
But it fails as well with until == 2
instead of 3
.
Any explanation for that?
Upvotes: 2
Views: 461
Reputation: 151
Looks like using Date instead of DateTime is the best way to calculate amount of months between two dates. Since my function gets has two ZonedDateTime parameters I'm going to do something like this
LocalDate.from(zonedDateTimeOfNow).until(LocalDate.from(zonedDateTimeEnd), ChronoUnit.MONTHS)
Upvotes: 0
Reputation: 79395
Check the following documentation from OffsetDateTime#until
(emphasis mine):
The calculation returns a whole number, representing the number of complete units between the two date-times. For example, the amount in months between 2012-06-15T00:00Z and 2012-08-14T23:59Z will only be one month as it is one minute short of two months.
In your case, the difference is caused due to 1 nanosecond. Keep in mind that 2023-02-29 and 2023-02-30 do not exist.
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
public class Main {
public static void main(String[] args) {
// 2 months
// 2022-11-30T0:0:0:1 to 2022-12-30T0:0:0:1 - complete
// 2022-12-30T0:0:0:1 to 2023-01-30T0:0:0:1 - complete
// 2023-01-30T0:0:0:1 to 2023-03-01T0:0:0:1 would have been one month but
// the endtime is 2023-03-01T0:0:0:0, 1 nanosecond short
System.out.println(LocalDateTime.of(2022, 11, 30, 0, 0, 0, 1).until(LocalDateTime.of(2023, 3, 1, 0, 0, 0, 0),
ChronoUnit.MONTHS));
// 3 months as explained above
System.out.println(LocalDateTime.of(2022, 11, 30, 0, 0, 0, 1).until(LocalDateTime.of(2023, 3, 1, 0, 0, 0, 1),
ChronoUnit.MONTHS));
// 3 months
// 2022-11-30T0:0:0:0 to 2022-12-30T0:0:0:0 - complete
// 2022-12-30T0:0:0:0 to 2023-01-30T0:0:0:0 - complete
// 2023-01-30T0:0:0:0 to 2023-03-01T0:0:0:0 - complete
System.out.println(LocalDateTime.of(2022, 11, 30, 0, 0, 0, 0).until(LocalDateTime.of(2023, 3, 1, 0, 0, 0, 0),
ChronoUnit.MONTHS));
// 3 months
// 2022-11-28T0:0:0:1 to 2022-12-28T0:0:0:1 - complete
// 2022-12-28T0:0:0:1 to 2023-01-28T0:0:0:1 - complete
// 2023-01-28T0:0:0:1 to 2023-02-28T0:0:0:1 - complete
System.out.println(LocalDateTime.of(2022, 11, 28, 0, 0, 0, 1).until(LocalDateTime.of(2023, 3, 1, 0, 0, 0, 0),
ChronoUnit.MONTHS));
}
}
Output:
2
3
3
3
Upvotes: 2