Wednesday, March 14, 2012

JavaScript and Dates, What a Mess!

We recently had a dygraphs regression where a date specified as "2012-03-13" would be displayed as "2012-03-12 20:00" when you hovered over it (or something else depending on your time zone).

The culprit wound up being surprising behavior from JavaScript's Date parser. Here's the gist of the problem, via the Chrome JavaScript Console:

> new Date("2012/03/13")
Tue Mar 13 2012 00:00:00 GMT-0400 (EDT)
> new Date("2012-03-13")
Mon Mar 12 2012 20:00:00 GMT-0400 (EDT)

So, why the discrepancy of four hours between these seemingly-identical dates? Here's another puzzler:

> new Date("2012-03-13")
Mon Mar 12 2012 20:00:00 GMT-0400 (EDT)
> new Date("2012-3-13")
Tue Mar 13 2012 00:00:00 GMT-0400 (EDT)

Why does that extra zero in the first form subtract four hours?

I've run into many issues with new Date and Date.parse while working on dygraphs over the years. This issue inspired me to put together…

The Comprehensive Cross-Browser
JavaScript Date Parsing Table

On that page, you'll see how a variety of browsers do (or don't) parse particular date strings.

Here are some rules of thumb:

  • Stick to "YYYY/MM/DD" for your date strings whenever possible. It's universally supported and unambiguous. With this format, all times are local.
  • Avoid using hyphenated dates ("YYYY-MM-DD") unless you know what you're doing. Only newer browsers support them.
  • So long as you specify four digits years, date strings at least have a unique parse across all browsers. Some browsers may return an error, but you won't get inconsistent behavior.
  • Chrome tends to be more accepting than other browsers. If a date format parses in Chrome, you shouldn't assume that it parses anywhere else.
  • If a recent browser can interpret as date string as ISO-8601, it will. With this format, your date/time string is interpreted as UTC.

With the table and these rules of thumb in hand, let's take another look at those mysterious Date parses from the beginning of the post:

> new Date("2012/03/13")
Tue Mar 13 2012 00:00:00 GMT-0400 (EDT)

This parses to midnight in the local time zone. This behavior is universal across browsers.

> new Date("2012-03-13")
Mon Mar 12 2012 20:00:00 GMT-0400 (EDT)

This can be interpreted as an ISO-8601 date, and so it is (following our last rule of thumb). This means that it is parsed as UTC. But it is displayed using local time. I'm in the EDT time zone, hence the difference of four hours. This format is only supported by newer browsers (Chrome, FF4+, IE9).

> new Date("2012-3-13")
Tue Mar 13 2012 00:00:00 GMT-0400 (EDT)

This cannot be interpreted as an ISO-8601 date, since it's missing a "0" in front of the month ("3" vs. "03"). So Chrome falls back to interpreting this as a local time. At the moment, Chrome is the only browser which supports this particular gem of a format.

A final cautionary note: if you are writing a library and wish to convert date strings to millis since epoch, the built-in Date.parse method looks like just the ticket. But you'll eventually run into an issue. For reasons known only to its authors, MooTools overrides this function with an incompatible version (theirs returns a Date, not a number). What you really want is new Date(dateString).getTime().

So if you're writing a library and you get a date string from your user, what should you do with it? In dygraphs, the answer keeps getting more and more complicated.

12 comments:

  1. The main problem I've faced with Dygraphs is setting Dates using the native format.
    Wikipedia says: "The Unix epoch is the time 00:00:00 UTC on 1 January 1970 (or 1970-01-01T00:00:00Z ISO 8601)."

    And in Js:
    new Date(0) returns
    Thu Jan 01 1970 01:00:00 GMT+0100 (CET)

    The only solution is to add/substract you local time offset, which is totally unacceptable but that's the way it is.

    I implemented an extension to strftime.js to handle GMT+0, just like php's gmstrftime does.

    https://github.com/jllodra/dygraphs/blob/UTC/strftime/strftime.js

    Usage is:

    * var d = new Date();
    *
    * var ymd = d.strftime('%Y/%m/%d', false); // your local timezone
    * var iso = d.strftime('%Y-%m-%dT%H:%M:%S%z', true); // GMT+0 or UTC

    ReplyDelete
  2. Josep,

    It sounds like you'd be happier if dygraphs always displayed dates using UTC. That would at least be more consistent, but would be a pretty large change. It might make sense as an option though, one that would make dygraphs use date.getUTCHours() and co instead of date.getHours(), which uses local time.

    ReplyDelete
  3. Thanks for sharing your (surely painful) research.
    The "slashes instead of dashes" approach solved all my issues, with reasonable certainty (as much as one can have with browsers behaviour).
    The UTC thing was correctly identified in some stackoverflow threads, but the slashes solution didn't. But there is a link to here in http://stackoverflow.com/questions/2587345/javascript-date-parse

    ReplyDelete
  4. 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
    Grep Command Examples in Linux/Unix

    ReplyDelete
  5. Kiran, that is a generic Javascript question. You might get better answers on Stack Overflow.

    ReplyDelete
  6. @danvk, before everything else, accept my most sincere gratitude and best compliments for this most awesome effort, of yours.

    I am not too sharp with javascripts myself, but I suppose you could somehow integrate the use of jQuery as suggested here:
    http://stackoverflow.com/questions/1576753/parse-datetime-string-in-javascript
    It could allow use of almost any date format that the data files may contain.
    We are using Dygraphs for an important project where we need to analyse logfiles where data is in csv format, and date-time is printed using YYYYMMDDHHMMSS format. Note there are no spaces, slashes or colons. Why the logs are such are a different matter. And I think an awesome programmer like you can figure it out.

    Anyways my colleague and I are going to give that a shot, and if successful, will be more than happy to share.

    Thanks again for a superlative and so immensely useful work.

    ReplyDelete
  7. Great blog, saved me lots of time! Thanks!

    ReplyDelete
  8. Thanks for the great article.
    What I found more mess is that
    only chrome can parse some date like: "2014-12-31 12:00:01"
    FF and IE both will result in Invalid Date

    ReplyDelete
  9. I's amazing how simple thing can get so complicated. I tried to get date and print date, but instead of that I get also a time and time zone and it seems I can't get rid of it, without some sort of hacking

    ReplyDelete
  10. Hate the fact that date is 1 based but month is 0 based :| What kind of messed up design is that?

    ReplyDelete