progressive_overload progressive_overload - 5 months ago 15
Java Question

Is the TimeUnit class broken?

I noticed a strange behaviour of the TimeUnit class, so I created this minimal example to reproduce it.

long differenceInDays;

Calendar c1 = Calendar.getInstance();
Calendar c2 = Calendar.getInstance();

c1.setTimeInMillis(1466062306000l); // Thu Jun 16 2016 09:31:46 GMT+0200
c2.setTimeInMillis(1466028000000l); // Thu Jun 16 2016 00:00:00 GMT+0200

differenceInDays = TimeUnit.DAYS.convert(c2.getTimeInMillis() - c1.getTimeInMillis(), TimeUnit.MILLISECONDS);
System.out.println(differenceInDays); // obviously zero

c2.add(Calendar.DATE, +1);
differenceInDays = TimeUnit.DAYS.convert(c2.getTimeInMillis() - c1.getTimeInMillis(), TimeUnit.MILLISECONDS);
System.out.println(differenceInDays); // why zero and not one?

c2.add(Calendar.DATE, +1);
differenceInDays = TimeUnit.DAYS.convert(c2.getTimeInMillis() - c1.getTimeInMillis(), TimeUnit.MILLISECONDS);
System.out.println(differenceInDays); // suddenly a 1, but not a 2 like expected


It is obvious that the first time the difference is calculated it is 0, because not a whole day lies between the dates.

But the second time a whole day is added, so how can the difference be still 0?

Output:


0
0
1


I don't think this problem is daylight saving time or leap year related because I only do calculations within the same year, even month.

Here is a date to milliseconds calculator for you to check.

Answer

You can see better what's going on here with simple math:

c1 = 1466062306000
c2 = 1466028000000

d = 86400000                // one day

c2 - c1 = -34306000         // negative, but less than one day in magnitude
c2 - c1 + d = 52094000      // less than one day
c2 - c1 + d + d = 138494000 // more than one day, less than two days

The correct way to handle this, assuming you're using Java 8, is as follows:

// Decide what time zone you want to work in
ZoneId tz = ZoneId.of("Europe/Berlin");

// If you wanted the local time zone of the system,
// Use this instead:
// ZoneId tz = ZoneId.systemDefault();

// Get instants from the timestamps
Instant i1 = Instant.ofEpochMilli(1466062306000l);
Instant i2 = Instant.ofEpochMilli(1466028000000l);

// Get the calendar date in the specified time zone for each value
LocalDate d1 = i1.atZone(tz).toLocalDate();
LocalDate d2 = i2.atZone(tz).toLocalDate();

// Get the difference in days
long daysBetween = ChronoUnit.DAYS.between(d2, d1);

If your inputs are truly Calendar objects instead of timestamps, I'd suggest Calendar.toInstant() as described in the Legacy Date-Time Code guidance.

If you're using Java 7 or earlier, you will find similar capabilities from the Joda Time library.

if you really don't want to use any of these, and still do things the old (hard) way, then see this example.

Comments