Search code examples
javadatecalendarmillisecondstimeunit

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.


Solution

  • 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.