JavaScript Date type is horribly broken
JavaScript dates have always been a bit funny. As a .Net developer, there was a bit of a mind-shift for me, because they are inherently different than types like DateTime
or DateTimeOffset
. But I won't spend too much time on what they are not, lets just talk about what they are.
In JavaScript, dates are internally represented as an integer number of milliseconds since the Unix epoch date (Jan 1, 1970, midnight UTC). But when displayed, they take on the attributes of the local time zone of the environment that they are running in. This has some interesting effects:
- Two different computers could be given the same integer date value, and they will output different strings.
- If you just capture a date value from the user, for example you might use a date-picker control that returns you a
Date
object, that object will inherently pick up the user's time zone. - If there is any ambiguity in that value, how it is resolved is left up to the host environment (usually a browser). Implementations can vary.
This last point is a tricky one. Let's say a user in the US Eastern time zone enters a local date corresponding to November 3rd, 2013 at 1:00 AM. Well, there are two of these, since that is when daylight saving time ends and the clocks roll back an hour. So what will happen in JavaScript?
- If the user is running Internet Explorer or Safari, they get a value corresponding to 05:00 UTC.
- But if they're running Chrome, Firefox, Opera, or if the code is running in a Node.js application, they get a value corresponding to 06:00 UTC.
This alone wouldn't be so bad, but there's an even bigger problem. The rules in which a conversion of a local date to a UTC date are WRONG. Well, they are if you are working with anything other than dates from right now.
Let me be more specific. JavaScript as we currently know it is based on the ECMAScript 5.1 specification. All implementations of modern JavaScript are supposed to have followed this spec, so that code is portable between different implementations. And buried in this spec, are the following two paragraphs:
15.9.1.8 Daylight Saving Time Adjustment...The implementation of ECMAScript should not try to determine whether the exact time was subject to daylight saving time, but just whether daylight saving time would have been in effect if the current daylight saving time algorithm had been used at the time. This avoids complications such as taking into account the years that the locale observed daylight saving time year round.If the host environment provides functionality for determining daylight saving time, the implementation of ECMAScript is free to map the year in question to an equivalent year (same leap-year-ness and same starting week day for the year) for which the host environment provides daylight saving time information. The only restriction is that all equivalent years should produce the same result.
Read that a few times, carefully. Basically it is saying "Whatever the rules are now, you must assume that these rules have always been in effect". What? Really? YES - someone thought this would be ok. That someone, would not be me!
As we know just by the sheer number of updates to the various time zone databases, time zones can change at the whim of politicians, or for religious reasons, or for any other number of reasons. Even here in the United States, congress passed a law in 2005 that went into effect in 2007, changing the dates that daylight savings time starts and stops in the USA. Does modern JavaScript know about this change? No sir, it does not! All it knows is how things currently work. It has no idea that they used to be different.
Pass a date like November 7, 2004 (at mignight). At this date in history, daylight savings time was over - by a week (it ended on Oct 31 that year). But ask today, and it will tell you that it is still in daylight time! It's not until a few hours later that it think the transition has happened and applies the standard time offset. To illustrate, this is what happens in Google Chrome:
new Date(2004,10,7,0,0)
//=> Sun Nov 07 2004 00:00:00 GMT-0400 (Eastern Daylight Time)
new Date(2004,10,7,1,0)
//=> Sun Nov 07 2004 01:00:00 GMT-0500 (Eastern Standard Time)
The problem is not just limited to web browsers either. If you write server applications using Node.js - you also have this problem.
To be fair, not everyone has this problem. It seems someone at Microsoft had the good sense (or the accidental fortune) of not following the spec. If you are running Internet Explorer - you are fine. It ignores this part of the ECMAScript spec and properly determines the correct timezone offset, using the Windows time zone data stored in the system registry. Good for you, IE. Sometimes breaking with the spec has an upside.
On the other hand, if you are running Firefox, not only do they calculate the zone wrong, but they also mess up the names! They can't seem to get "daylight" and "standard" straight - they have them inverted. You can read about this here. This is a bug, and I'm sure they will fix it in an upcoming version.
So what to do about the bad spec? Well it seems someone out there has already realized this flaw and corrected it for the upcoming ECMAScript 6. The current draft is here, and states:
An implementation of ECMAScript is expected to make its best effort to determine the local daylight saving time adjustment. An implementation dependent algorithm using best available information on time zones to determine the local daylight saving time adjustment DaylightSavingTA(t), measured in milliseconds.
This is much better. But who knows how long it will be before this gets ratified and makes its way into the browsers? And what about all of the current and old browsers out there?
It's going to take some hefty JavaScript to work around this problem. Specifically, instead of relying on the operating system to make the time zone offset determination, you're going to have to do it yourself. There are currently 5 different libraries that implement the TZDB data in JavaScript, and they are in various states of stability and usefulness. I listed them here, but I think I should do more. I will be analyzing these libraries in the next few weeks. Expect to see another blog post here comparing them, and illustrating how they can be used to overcome this issue.
I would like to thank Jeff Walden, and "bpa" for helping to uncover this issue recently. You can read more here.
I would also like to give a big smack upside the head to the folks at ECMA that let this slide. A spec should never be written as "don't follow the normal rules, follow our rules instead because we think they are easier". Did they not stop to consider that the host environment would probably already have the capability to do this right? It should have been a very simple matter to say that timezone decisions should be left to the host environment.
UPDATE
Apparently Internet Explorer is indeed affected by this issue, at least through version 9. IE10 appears to be following the ECMAScript6 recommendation. Not sure if it was by accident or by design, but it's a good thing.
UPDATE 2
The latest draft of ES6 has an additional note:
NOTE It is recommended that implementations use the time zone information of the IANA Time Zone Database.
This is excellent. Yay!
UPDATE 3
The issue mentioned with regards to FireFox flipping the names for standard and daylight time appears to be resolved in FireFox 25. The other issues still remain.
UPDATE 4
Apparently it was not fixed in FireFox 25. I still see the issue in FireFox 30, and even uninstalling and re-installing FireFox 25 does not fix the problem. Hmmmm...
UPDATE 5
This is also been fixed in the ECMA Internationalization API (ECMA-402). In version 1.0:
12.3.2 Intl.DateTimeFormat.prototype.format
...
The calculations should use best available information about the specified calendar and time zone. If the calendar is "gregory", then the calculations must match the algorithms specified in ES5, 15.9.1, except that calculations are not bound by the restrictions on the use of best available information on time zones for local time zone adjustment and daylight saving time adjustment imposed by ES5, 15.9.1.7 and 15.9.1.8.
A similar wording also appears in version 2.0 of the spec, in section 12.3.4.
UPDATE 6
Checking this out again as of Firefox 42.0, the name flipping is resolved. It may have been resolved earlier - I haven't checked in awhile.
However, both the current Firefox 42.0 and Chrome 46.0.2490.80 still choose daylight time for the 2004-11-07 date I mentioned above, even though the standard time was actually in effect.