Losing Time on the Garden Path: Calculating the Difference Between Two Datetime Stamps

Paul Hill, August 2004

On and off for many years, I have seen people post to comp.lang.java.programmer questions about calculating differences between two dates. Many people think that calculating the difference in days is a simple exercise. They even post code that they believe demonstrates the trivial solution. The problem is that the obvious solution -- subtracting the millisecond timestamps of two moments and dividing by the number of milliseconds per day, contains a bug. The bug in this trivial calculation is that in many areas of the world where there is daylight savings time, one day is 23 hours long and another is 25 hours long1. This bug is not a limitation of either java.util.Date or java.util.Calendar, but is a characteristic of the definition of the calendar day in many locations, thus it is common to any day calculation involving millisecond values.

There are two concepts we can use to define the difference in days between two date time stamps, both are very useful in certain situations. The first idea I call delta calendar days. It assumes any time today subtracted from any time tomorrow results in a 1 day difference, thus 0 = today, 1 = tomorrow, 2 = the day after tomorrow, -1 = yesterday, -2 = the day before yesterday. The other idea I shall call delta day periods. It assumes that the difference between some time today and the same clock time tomorrow is exactly 1 day. If the difference between the times is greater than the period of a day, the value is larger. These differences are shown in Table 1

Table 1: Two different day difference definitions

Datetime 1
Datetime 2 Delta
Calendar
Days
Delta
Day
Periods
2004-01-01 8:00
2004-01-02 09:00 1 1 day 1 hour
2004-01-01 8:00
2004-01-03 11:00 2 2 days 3 hours
2004-01-01 8:00
2004-01-04 13:00 3 3 days 5 hours
2004-01-01 8:00
2003-12-31 09:00 -1 0 days 23 hours
2004-01-01 8:00
2003-12-30 11:00 -2 1 day 21 hours
2004-01-01 8:00
2003-12-29 13:00 -3 2 days 19 hours

The first approximation for delta day periods -- the calculation which is "trivial and obvious" to many -- would seem to be

java.util.Date lateDate, earlyDate;
deltaDays = ( lastDate.getTime() - earlyDate.getTime() )/ MILLSECS_PER_DAY;

But this is not correct. If we choose the dates in the spring around the "spring forward" date and the date in the autumn of the "fall back" date we can see the problem.

Table 2: Day differences compared to 24 hour periods, including at the DLS change dates.

Datetime 1 Datetime 2 Delta
Calendar
Days
Delta
Day
Periods
Delta
24-hour
Periods
2004-04-01 T 06:00:00.0 PST 2004-04-02 T 06:00:00.0 PST 1 1 1
2004-04-01 T 06:00:00.0 PST 2004-04-03 T 06:00:00.0 PST 2 2 2
2004-04-01 T 06:00:00.0 PST 2004-04-04 T 06:00:00.0 PDT 3 3 2
2004-10-31 T 00:00:01.0 PDT 2004-10-31 T 23:59:59.0 PST 0 0 1

The last two dates in the above chart calculate through the daylight savings change date in the North American "Pacific" timezone which includes Vancouver CA; Seattle, San Francisco and Los Angeles USA.

Using 24-hour periods is an inappropriate solution because:

  • It returns the same value for two different date-times a day apart.
  • 24 hours is not the definition of a day as used by java.util.GregorianCalendar.
  • This is not the definition of a day as defined by ISO-8601, the international standard which defines date-time formats and date calculations.
  • Listing 1 is FAQCalendar which subclasses java.util.Gregoriancalendar and provides methods to calculate:

  • Day numbers -- getJulianDay(), getUnixDay()-- which you can use to calculate the difference in calendar days.
  • The difference in day periods and in the temptingly "trivial to calculate" 24-hour periods --
    diffDayPeriods( Calendar), get24HourPeriods(Calendar)
    .
  • The diffDayPeriods calculation includes using the timezone offset for the current time and hence the correct DLS offset in the difference calculation. The key statements are:

    long endL = end.getTimeInMillis() + end.getTimeZone().getOffset( end.getTimeInMillis() );
    long startL = this.getTimeInMillis() + this.getTimeZone().getOffset( this.getTimeInMillis() );
    return (endL - startL) / MILLISECS_PER_DAY;

    The method get24HourPeriods(Calendar) has been marked as deprecated.

    Listing 2 is DeltaDays which is a Junit Test which will test all the methods discussed above to calculate a difference in days.

    Summary

    I would not recommend using the "trivial and obvious" method when calculating the number of days between datetime values because of the problems with daylight savings. The correct calculation depends on your needs, but may include knowing the correct DLS offset for the times in the calculation. Luckily, this timezone and DLS offset value can be obtained from within a java.util.Calendar.

    Further general information about bad date calculations can be found at The Best of Dates, The Worst Of Dates, see particularly section 8.4 Length Of Day Bugs. The current article corrects the common error "advancing to the next Civil Day by adding 24*60*60 (86,400) seconds to the current time"2 and avoids "Using your own logic to manipulate a time rather than calling a trustworthy library function."2


    The author can be reached at goodhill (AT) xmission.com.


    1 For this article I have ignored any issues of leap seconds which occasionally effect the length of other days. Most binary java.util.Dates do not include leap seconds because the underlying hardware and OS does not provide leap seconds.

    2 See the discussion in The Best of Dates, The Worst Of Dates.

    Last updated 2005-05-22