I'm creating 3 GregorianCalendar objects:
The day difference between the first and the second is 96 days. The day difference between the first and the third is... 96 days. Say hunh?
Apologies for the Scala code, but you Java-heads should be able to get what's going on:
def test(): Unit = {
val start = new GregorianCalendar(2018, 11, 4)
val laterA = new GregorianCalendar(2018, 11, 4)
laterA.add(Calendar.DATE, 96)
val laterB = new GregorianCalendar(2018, 11, 4)
laterB.add(Calendar.DATE, 97)
println(ChronoUnit.DAYS.between(start.toInstant, laterA.toInstant))
println(ChronoUnit.DAYS.between(start.toInstant, laterB.toInstant))
}
The above prints the following:
96
96
What's the deal?
As others have said already, avoid the GregorianCalendar
class if you can. It’s got some design problems and is now long outdated.
However, your particular problem was not with the GregorianCalendar
class. It adds 97 days nicely in your code, taking summer time (DST) into account and everything. As others have correctly pointed out, adding 97 days to December 4 this year gives you March 11, the day when summer time has just begun in those North American time zones that use summer time (month 11 as constructor argument meaning December, another confusing thing about GregorianCalendar
). Therefore the last day added was only 23 hours long.
Your real issue is with the combination of ChronoUnit.DAYS
and Instant
. An Instant
is a point in time with no time zone and doesn’t have a concept of days. Instead the result you get is the same as you would get from counting hours, dividing by 24 and throwing away the remainder. That’s seldom useful. And since the last day added was only 23 hours, it is not being counted. Instead count days between ZonedDateTime
or LocalDate
instances, or some other java.time class that has “date” in its name. Example 1 (Java code, can you translate yourself?):
LocalDate start = LocalDate.of(2018, Month.DECEMBER, 4);
LocalDate laterB = start.plusDays(97);
System.out.println(ChronoUnit.DAYS.between(start, laterB));
Output:
97
If you cannot avoid getting a GregorianCalendar
from a legacy API that you cannot afford to upgrade just now, the first thing you should do is to convert it using toZonedDateTime
to get a ZonedDateTime
. This will give you all the relevant information from the GregorianCalendar
, including its time zone and its calendar date. In other words, you should not use its toInstant
method as you did in the code in the question. Example 2:
GregorianCalendar start = new GregorianCalendar(2018, Calendar.DECEMBER, 4);
GregorianCalendar laterB = new GregorianCalendar(2018, Calendar.DECEMBER, 4);
laterB.add(Calendar.DATE, 97);
System.out.println(ChronoUnit.DAYS.between(start.toZonedDateTime(),
laterB.toZonedDateTime()));
The output is now the one you had expected:
97
I have run both snippets with America/New_York as my default time zone.