JavaScript Date Parsing Changes in ES6

Do you know the difference between these two lines?

var d1 = new Date("2015/06/17");
var d2 = new Date("2015-06-17");

How about these?

var d1 = new Date("2015/06/17 00:00:00");
var d2 = new Date("2015-06-17 00:00:00");

Or these?

var d1 = new Date("2015/06/17 00:00:00");
var d2 = new Date("2015-06-17T00:00:00");

Still unsure?  How about now?

var d1 = new Date("2015/06/17 00:00:00");
var d2 = new Date("2015-06-17T00:00:00Z");

Ah, that's it!  In all of the above examples, d2 is parsed as UTC, while d1 is parsed as local time.  This occurs because all of the d2 strings are valid under ISO 8601.

There's a problem though.  ISO 8601 specifies that time without a Z, and without an offset (such as -08:00) should be treated as local time. In the above examples, all but the last one were without offset - so why were they parsed as UTC?

Well, in the EcmaScript specification (up to and including the current ES5.1), there is part of the spec that states (in section 15.9.1.15)

... The value of an absent time zone offset is "Z".

That explains the current deviation from ISO 8601, and why folks have trouble understanding why they get the "wrong" date when they do something like this (screenshot from Chrome):

Parsing a date in Google Chrome

So, to make this align with the ISO 8601 spec, this line was changed in the ES6 specification (in section 20.3.1.16).  It now says:

... If the time zone offset is absent, the date-time is interpreted as a local time.

That's great for compliance and spec alignment.  But it's a serious issue for backward compatibility.  Any code that expects the Date constructor to parse a date as UTC will need to be updated.

To maintain compatibility as ES6 is adopted, it's important to not rely on this bit of functionality.  Instead, if you to parse as UTC then you should explicitly add the Z.

var d = new Date("2015-06-17Z");

This will parse as UTC both today with ES5, and tomorrow with ES6.

If you need to parse as local time, you could consider using the slashes, like I showed as d1 - but this isn't guaranteed to work in all browsers.  (I've been told that it doesn't work in some versions of Safari.)

A better approach, for both local and UTC, is to use Moment.js.

var m1 = moment("2015-06-17");      // local time
var m2 = moment.utc("2015-06-17");  // UTC

Both m1 and m2 are moment objects, so if you need a Date, then call .toDate().  Or if you need a string, then use .format() or .format("MM/DD/YYYY"), or any of the other functions offered by the library.

Regardless of whether you use a library or manually add a Z, you should prepare for this change today so that you won't be bit by this as ES6 starts making its way into browsers.

Update

This matter was also discussed in ECMAScript tc39 issue #87, and then the spec was augmented in PR #138.  The net effect is that date-time forms such as "2015-11-04T00:00:00" are indeed interpreted as local time, per the ES6 spec and ISO8601, but the date-only forms such as "2015-11-04" are still interpreted as UTC.  The first part is great, but the second part is really confusing and doesn't match ISO8601.  In my opinion, all browsers should treat both forms as local.  They should only be treated as UTC if they are followed with Z or +00:00.

Currently, all modern browsers except Chrome will treat the date-time form as local, and all modern browsers treat the date-only form as UTC.

This material is also covered in my Pluralsight course, Date and Time Fundamentals. If you enjoy this topic, please consider watching the video course!