Parsing local date and time strings with Javascript's Date()

JavaScript's Date constructor is changing in ES6. Where before it parsed all date, time and datetime strings as UTC, ES6 says it should default to local time where a time zone is not given.

Stephen Band

Last night while working on Touring Machine, a platform for organising events for artists and managers who frequently move between time zones, I found out something new about the date constructor. It’s going to change.

In our app dates are represented as standard date strings, such as "2015-04-24". These are parsed by the Date constructor (and by Date.parse, which does the same thing) as Universal Time.

Say your computer is in Switzerland, which is 2 hours ahead of UTC in summer, parsing "2015-04-24" nonetheless gives us a date object representing 00:00 on the 24.04.2015 UTC:

new Date("2015-04-24").toJSON()
// "2015-04-24T00:00:00.000Z"

But there is a catch: soon, it won’t. ES6 says that where a date time string does not contain time zone information the date object should assume it represents local time. I haven’t found much information on this 1, but as far as I can see that means the Date constructor will soon do this:

new Date("2015-04-24").toJSON()
// "2015-04-23T22:00:00.000Z"

That’s ten o’clock the night before!

In reality this is actually very sensible behaviour. Other languages already assume local time where a time zone is not specified, and pre-ES6 JS is the black sheep. But it does leave us in a situation where our logic may change depending on where the code is run. It would be nice to know what our Date constructor is doing before using it, so here is a little test.

var local =
    new Date().getTimezoneOffset() === 0 ||
    new Date('1970').toJSON() !== '1970-01-01T00:00:00.000Z';

If the timezone offset is zero it doesn’t matter whether the Date constructor is parsing to local time because local time and UTC are the same thing, so local is true. Otherwise it compares a parsed and JSONified date with the UTC time string and assumes that if they’re not equal we’re using local time. There may be holes in this argument, and if there are I would like to hear about them @stephband.

So to guarantee ‘correct’, ES6-like behaviour, here’s a createDate function that normalises the creation of dates from strings:

var rtimezone = /(?:Z|[+-]\d{2}:\d{2})$/;
var local =
    new Date().getTimezoneOffset() === 0 ||
    new Date('1970').toJSON() !== '1970-01-01T00:00:00.000Z';

function createLocalDate(value) {
    var date = new Date(value);

    // Offset the date by adding the date's offset in milliseconds.
    // Careful, getTimezoneOffset returns the offset in minutes.
    return new Date(+date + date.getTimezoneOffset() * 60000);
}

function createDate(value) {
    return typeof value !== 'string' ? new Date(value) :

        // If the Date constructor is parsing to local time...
        local ? new Date(value) :

        // ...or if the value contains a time zone...
        (value.length > 16 && rtimezone.test(value)) ? new Date(value) :

        // ...otherwise force the date to be a local date.
        createLocalDate(value) ;
}

createDate("2015-04-24")
// "2015-04-23T22:00:00.000Z"   Aaaaaah.

This potentially critical change seems barely hinted at in the spec:
people.mozilla.org/~jorendorff/es6-draft.html#sec-date.parse

Here’s the spec for time strings:
people.mozilla.org/~jorendorff/es6-draft.html#sec-date-time-string-format


Touring Machine is currently in private beta. If you are interested in using it get in touch with anna@touringmachine.co.uk. Or read more about how we made it.

Event page of the Touring Machine tour planner

Need an event organiser app?

Email hello@cruncher.ch Telephone +41 21 546 68 00