Properly calculating time differences in JavaScript

Let me tell you a tale about a fat-client application that has nice some time-related logic written in JavaScript. We want to calculate the difference between two dates, measured in days. Easy, you say, just use the Date object and do some calculating.
As a JavaScript veteran you know that you have to use new Date() instead of Date() because the second one returns a string for some reason, you recall that the month of October is identified by the number 9 because we start counting the months starting at 0 and quickly figure out that subtracting two Date objects results in a number which is the amount of milliseconds passed between two moments.

var DAY_IN_MS = 24 * 60 * 60 * 1000;
var d1 = new Date(2012, 9, 27);
var d2 = new Date(2012, 9, 28);
console.log((d2 - d1) / DAY_IN_MS); // yields 1

Looks fine, doesn’t it? So just wrap it in a function, unit-test it and be done with it? Not so fast there. Let’s just change the dates ever so slightly

var DAY_IN_MS = 24 * 60 * 60 * 1000;
var d1 = new Date(2012, 9, 27);
var d2 = new Date(2012, 9, 28);
var d3 = new Date(2012, 9, 29);
console.log((d2 - d1) / DAY_IN_MS); // yields 1
console.log((d3 - d2) / DAY_IN_MS); // yields 1.0416666666666667

This is the point where most developers start cursing. Is this a new way in which JavaScript is broken? It isn’t, because the number is completely accurate.
The JavaScript object created by new Date(2012, 9, 28) represents midnight on the 28th of October, 2012 in your local time zone, new Date(2012, 9, 29) represents midnight the following day.
Subtracting the first from the seconds yields the number of milliseconds that have passed between those two moments, which, as you probably have guessed, includes the extra hour put in because of daylight savings time.

> new Date(2012, 9, 29);
Mon Oct 29 2012 00:00:00 GMT+0100 (CET)
> new Date(2012, 9, 28);
Sun Oct 28 2012 00:00:00 GMT+0200 (CEST)
> (new Date(2012, 9, 29) - new Date(2012, 9, 28)) / 60 / 60 / 100
25

So where is the error? The error is in our assumption that a day has 24 hours, because depending on how you define a day, it hasn’t – October 28th 2012 has 25 hours.
If you Google “JavaScript time difference”, most people just use Math.round (1) or simply use flat-out buggy code (1 2) and call it a day (pun intended), but that is not how we roll here.
What do we really mean when we ask “How many days have passed between two dates in the calendar”? We usually mean “How many midnights have happened between these two dates?”. Unfortunately, because of DST, you can’t just use the number of milliseconds between two dates at midnight to calculate how many midnights have happened, because some of them are more or less than 24 hours apart. If only there was a magical place that doesn’t have this madness going on…
Luckily, there is, and that place is UTC. UTC is a time measuring system that does not have daylight savings time.
Edit: as pointed out in the comments, the rabbit hole goes down even further – officially, even in UTC, a day might have more than 24 hours because of leap seconds. Fortunately for us, the ECMA-262 specification explicitly ignores leap seconds and we can go about our business. If JavaScript would implement UTC correctly, we would have to account for leap seconds or use UT1.
The JavaScript Date API is just as beautiful as most other JavaScript APIs: While the only useful use of the Date object is by using it as a constructor (with new), the way to use UTC is by using the function Date.UTC which returns a unix timestamp. This is the JavaScript time API in a nutshell:

> new Date(2012, 9, 29);
Mon Oct 29 2012 00:00:00 GMT+0100 (CET) // (a somewhat useful object)
> Date(2012, 9, 29);
'Mon Nov 05 2012 16:18:12 GMT+0100 (CET)' // (a string - no relation to the parameters)
> Date.UTC(2012, 9, 29);
1351468800000 // (unix time in milliseconds)
> new Date.UTC(2012, 9, 29); // failure
TypeError: function UTC() { [native code] } is not a constructor
    at repl:1:9
    [....]

The correct calculation, without using Math.round or other hacks therefore is

var DAY_IN_MS = 24 * 60 * 60 * 1000;
var d1 = Date.UTC(2012, 9, 27);
var d2 = Date.UTC(2012, 9, 28);
var d3 = Date.UTC(2012, 9, 29);
console.log((d2 - d1) / DAY_IN_MS); // yields 1
console.log((d3 - d2) / DAY_IN_MS); // yields 1

These kinds of bugs are sneaky because they only show up for certain input values (I wonder if I would have noticed it if I hadn’t tested the code last week around the DST change) and usually don’t show up in unit tests unless you happen to know what you are looking for. The results are often nearly correct, and we are not used to thinking about time zones and we often hold invalid assumptions about time. Always using UTC isn’t a solution either, because sometimes we want the local time zone to be considered.
Libraries like Moment.js help, but the real protection against these kinds of bugs is to know about time zones, time measurement system and thinking about what you are actually calculating instead of simply throwing a Math.round in there to make it all work.
Just as anybody that has had the pleasure of seeing Rent will tell you: while a year has five hundred twenty-five thousand six hundred minutes, it still is difficult to measure the time of the year.

Kommentare

  1. Awesome post. Appreciate your hardwork
    I have an issue with date in my project. From Back end java code, i am sending date to front end,
    In front end i am comparing current client browser time to back end date,
    For this i am having lot of issues.
    so i tried to do both back end and front end dates as UTC date
    My Back end date is 'Tue May 21 2013 07:01:41'
    and Front end date in js is 'Tue, 21 May 2013 06:57:39 GMT' using now.toUTCString() function.
    How can i remove GMT in front end using java script?
    Thanks
    <a href="http://www.cloudhadoop.com/2013/05/Grep-Simple-Example-Linux-Unix.html" rel="nofollow">Grep Command Examples in Linux/Unix </a>

  2. <blockquote>The JavaScript Date API is just as beautiful as most other JavaScript APIs: While the only useful use of the Date object is by using it as a constructor (with new), the way to use UTC is by using the function <code>Date.UTC</code> which returns a unix timestamp. </blockquote>
    Note that you can pass the result of <code>Date.UTC</code> to <code>new Date</code> (one of its invocations takes a unix timestamp) to get what <code>new Date.UTC</code> <em>should</em> do:
    <blockquote><code>var DAY_IN_MS = 24 * 60 * 60 * 1000;
    var d1 = new Date(Date.UTC(2012, 9, 27));
    var d2 = new Date(Date.UTC(2012, 9, 28));
    var d3 = new Date(Date.UTC(2012, 9, 29));
    console.log((d2 - d1) / DAY_IN_MS); // yields 1
    console.log((d3 - d2) / DAY_IN_MS); // yields 1
    </code></blockquote>
    This way, you can still get Date objects.

  3. No.
    You should use UTC <b>with</b> leap seconds for what you want. ECMA-262 would slowly drift apart from the real sun days.
    The <code>Math.round</code> is actually needed with a correct UTC implementation because the duration of a day is not supposed to be 24 hours. These pedantic complains about details should have been in the opposite direction.

  4. @adg - basically JavaScript's Date.UTC is UTC minus the leap seconds, therefore the date calculation as I describe it works. You are right though, when working with a correct UTC implementation, you have to take leap seconds into account.

  5. Ignoring leap seconds will not make them disappear :-) They belong to UTC, not to ECMA-262.
    Therefore, whenever we compute the difference between two UTC times, we should really account for any leap second.

  6. @Jon @infogulch - you're both right, but JavaScript ignores leap seconds (see post)

  7. Actually, even your second example is not 100% accurate, since there was a leap second this last June 30th, making it off by 1000ms.

  8. Even in UTC, midnights are not always separated by 24 hours thanks to leap seconds :) http://en.wikipedia.org/wiki/Leap_second