359

Case One:

new Date(Date.parse("Jul 8, 2005"));

Output:

Fri Jul 08 2005 00:00:00 GMT-0700 (PST)

Case Two:

new Date(Date.parse("2005-07-08"));

Output:

Thu Jul 07 2005 17:00:00 GMT-0700 (PST)


Why is the second parse incorrect?

RobG
  • 124,520
  • 28
  • 153
  • 188
user121196
  • 27,234
  • 56
  • 136
  • 195
  • 31
    The second parse isn't incorrect per se, it's just that the first is parsed in local time, and the second in UTC. Note that "Thu Jul 07 2005 17:00:00 GMT-0700 (PST)" is the same as "2005-07-08 00:00". – jches Jul 23 '12 at 20:58
  • 1
    jsPerf: http://jsperf.com/value-of-date-input-to-date-object – Web_Designer Jun 11 '13 at 23:37
  • 20
    [ISO 8601](https://xkcd.com/1179/) xkcd. – ulidtko Apr 15 '14 at 12:02
  • 1
    In case anyone came here to figure out why a date is returning `NaN` in Firefox, I discovered that most other browsers (and Node.js) will parse a date without a day, such as "April 2014" as April 1, 2014, but Firefox returns NaN. You must pass a proper date. – Jazzy May 22 '14 at 18:11
  • To add to Jason's comment above: If you're receiving a NaN in Firefox, another issue could be that Firefox and Safari don't like hyphenated dates. Only Chrome does. Use a slash instead. – Bangkokian Dec 12 '15 at 06:31
  • They would be the same by changing **Case 2** to `new Date(Date.parse("2005-07-08T00:00-07:00"))` or changing **Case 1** to `new Date(Date.parse("Jul 8, 2005 UTC"))` – DJDaveMark Jan 24 '18 at 12:37

11 Answers11

459

Until the 5th edition spec came out, the Date.parse method was completely implementation dependent (new Date(string) is equivalent to Date.parse(string) except the latter returns a number rather than a Date). In the 5th edition spec the requirement was added to support a simplified (and slightly incorrect) ISO-8601 (also see What are valid Date Time Strings in JavaScript?). But other than that, there was no requirement for what Date.parse / new Date(string) should accept other than that they had to accept whatever Date#toString output (without saying what that was).

As of ECMAScript 2017 (edition 8), implementations were required to parse their output for Date#toString and Date#toUTCString, but the format of those strings was not specified.

As of ECMAScript 2019 (edition 9) the format for Date#toString and Date#toUTCString, have been specified as (respectively):

  1. ddd MMM DD YYYY HH:mm:ss ZZ [(timezone name)]
    e.g. Tue Jul 10 2018 18:39:58 GMT+0530 (IST)
  2. ddd, DD MMM YYYY HH:mm:ss Z
    e.g. Tue 10 Jul 2018 13:09:58 GMT

providing 2 more formats that Date.parse should parse reliably in new implementations (noting that support is not ubiquitous and non–compliant implementations will remain in use for some time).

I would recommend that date strings are parsed manually and the Date constructor used with year, month and day arguments to avoid ambiguity:

// parse a date in yyyy-mm-dd format
function parseDate(input) {

  let parts = input.split('-');

  // new Date(year, month [, day [, hours[, minutes[, seconds[, ms]]]]])
  return new Date(parts[0], parts[1]-1, parts[2]); // Note: months are 0-based
}
Mike B.
  • 10,955
  • 19
  • 76
  • 118
Christian C. Salvadó
  • 723,813
  • 173
  • 899
  • 828
  • Excellent, I had to use this as `Date.parse` was not behaving with UK date formats for some reason I couldn't work out – Ben Jul 23 '12 at 10:59
  • 1
    Time parts are documented in @CMS code. I used this code with a date format of "2012-01-31 12:00:00" `return new Date(parts[0], parts[1] - 1, parts[2], parts[3], parts[4], parts[5]);` Works perfectly, thanks! – Richard Rout Feb 13 '13 at 20:57
  • 1
    @CMS what do you mean by _implementation dependent_ ? – Royi Namir Mar 21 '13 at 09:10
  • 3
    @RoyiNamir, it means that the results depend on what web browser (or other JavaScript implementation) is running your code. – Samuel Edwin Ward Mar 29 '13 at 14:58
  • 1
    I have also had problem with new Date(string) in different browsers behaving differently. It's not even a question of it being broken on old versions of IE, the different browsers are just not consistent. Do not use Date.parse or new Date(string) ever. – Hoffmann Jun 25 '13 at 16:20
  • Yes, the provided answers is not porpler ywokring in all cases. – aggsol Nov 10 '14 at 13:38
204

During recent experience writing a JS interpreter I wrestled plenty with the inner workings of ECMA/JS dates. So, I figure I'll throw in my 2 cents here. Hopefully sharing this stuff will help others with any questions about the differences among browsers in how they handle dates.

The Input Side

All implementations store their date values internally as 64-bit numbers that represent the number of milliseconds (ms) since 1970-01-01 UTC (GMT is the same thing as UTC). This date is the ECMAScript epoch that is also used by other languages such as Java and POSIX systems such as UNIX. Dates occurring after the epoch are positive numbers and dates prior are negative.

The following code is interpreted as the same date in all current browsers, but with the local timezone offset:

Date.parse('1/1/1970'); // 1 January, 1970

In my timezone (EST, which is -05:00), the result is 18000000 because that's how many ms are in 5 hours (it's only 4 hours during daylight savings months). The value will be different in different time zones. This behaviour is specified in ECMA-262 so all browsers do it the same way.

While there is some variance in the input string formats that the major browsers will parse as dates, they essentially interpret them the same as far as time zones and daylight saving is concerned even though parsing is largely implementation dependent.

However, the ISO 8601 format is different. It's one of only two formats outlined in ECMAScript 2015 (ed 6) specifically that must be parsed the same way by all implementations (the other is the format specified for Date.prototype.toString).

But, even for ISO 8601 format strings, some implementations get it wrong. Here is a comparison output of Chrome and Firefox when this answer was originally written for 1/1/1970 (the epoch) on my machine using ISO 8601 format strings that should be parsed to exactly the same value in all implementations:

Date.parse('1970-01-01T00:00:00Z');       // Chrome: 0         FF: 0
Date.parse('1970-01-01T00:00:00-0500');   // Chrome: 18000000  FF: 18000000
Date.parse('1970-01-01T00:00:00');        // Chrome: 0         FF: 18000000
  • In the first case, the "Z" specifier indicates that the input is in UTC time so is not offset from the epoch and the result is 0
  • In the second case, the "-0500" specifier indicates that the input is in GMT-05:00 and both browsers interpret the input as being in the -05:00 timezone. That means that the UTC value is offset from the epoch, which means adding 18000000ms to the date's internal time value.
  • The third case, where there is no specifier, should be treated as local for the host system. FF correctly treats the input as local time while Chrome treats it as UTC, so producing different time values. For me this creates a 5 hour difference in the stored value, which is problematic. Other systems with different offsets will get different results.

This difference has been fixed as of 2020, but other quirks exist between browsers when parsing ISO 8601 format strings.

But it gets worse. A quirk of ECMA-262 is that the ISO 8601 date–only format (YYYY-MM-DD) is required to be parsed as UTC, whereas ISO 8601 requires it to be parsed as local. Here is the output from FF with the long and short ISO date formats with no time zone specifier.

Date.parse('1970-01-01T00:00:00');       // 18000000
Date.parse('1970-01-01');                // 0

So the first is parsed as local because it's ISO 8601 date and time with no timezone, and the second is parsed as UTC because it's ISO 8601 date only.

So, to answer the original question directly, "YYYY-MM-DD" is required by ECMA-262 to be interpreted as UTC, while the other is interpreted as local. That's why:

This doesn't produce equivalent results:

console.log(new Date(Date.parse("Jul 8, 2005")).toString()); // Local
console.log(new Date(Date.parse("2005-07-08")).toString());  // UTC

This does:

console.log(new Date(Date.parse("Jul 8, 2005")).toString());
console.log(new Date(Date.parse("2005-07-08T00:00:00")).toString());

The bottom line is this for parsing date strings. The ONLY ISO 8601 string that you can safely parse across browsers is the long form with an offset (either ±HH:mm or "Z"). If you do that you can safely go back and forth between local and UTC time.

This works across browsers (after IE9):

console.log(new Date(Date.parse("2005-07-08T00:00:00Z")).toString());

Most current browsers do treat the other input formats equally, including the frequently used '1/1/1970' (M/D/YYYY) and '1/1/1970 00:00:00 AM' (M/D/YYYY hh:mm:ss ap) formats. All of the following formats (except the last) are treated as local time input in all browsers. The output of this code is the same in all browsers in my timezone. The last one is treated as -05:00 regardless of the host timezone because the offset is set in the timestamp:

console.log(Date.parse("1/1/1970"));
console.log(Date.parse("1/1/1970 12:00:00 AM"));
console.log(Date.parse("Thu Jan 01 1970"));
console.log(Date.parse("Thu Jan 01 1970 00:00:00"));
console.log(Date.parse("Thu Jan 01 1970 00:00:00 GMT-0500"));

However, since parsing of even the formats specified in ECMA-262 is not consistent, it is recommended to never rely on the built–in parser and to always manually parse strings, say using a library and provide the format to the parser.

E.g. in moment.js you might write:

let m = moment('1/1/1970', 'M/D/YYYY'); 

The Output Side

On the output side, all browsers translate time zones the same way but they handle the string formats differently. Here are the toString functions and what they output. Notice the toUTCString and toISOString functions output 5:00 AM on my machine. Also, the timezone name may be an abbreviation and may be different in different implementations.

Converts from UTC to Local time before printing

 - toString
 - toDateString
 - toTimeString
 - toLocaleString
 - toLocaleDateString
 - toLocaleTimeString

Prints the stored UTC time directly

 - toUTCString
 - toISOString 

In Chrome
toString            Thu Jan 01 1970 00:00:00 GMT-05:00 (Eastern Standard Time)
toDateString        Thu Jan 01 1970
toTimeString        00:00:00 GMT-05:00 (Eastern Standard Time)
toLocaleString      1/1/1970 12:00:00 AM
toLocaleDateString  1/1/1970
toLocaleTimeString  00:00:00 AM

toUTCString         Thu, 01 Jan 1970 05:00:00 GMT
toISOString         1970-01-01T05:00:00.000Z

In Firefox
toString            Thu Jan 01 1970 00:00:00 GMT-05:00 (Eastern Standard Time)
toDateString        Thu Jan 01 1970
toTimeString        00:00:00 GMT-0500 (Eastern Standard Time)
toLocaleString      Thursday, January 01, 1970 12:00:00 AM
toLocaleDateString  Thursday, January 01, 1970
toLocaleTimeString  12:00:00 AM

toUTCString         Thu, 01 Jan 1970 05:00:00 GMT
toISOString         1970-01-01T05:00:00.000Z

I normally don't use the ISO format for string input. The only time that using that format is beneficial to me is when dates need to be sorted as strings. The ISO format is sortable as-is while the others are not. If you have to have cross-browser compatibility, either specify the timezone or use a compatible string format.

The code new Date('12/4/2013').toString() goes through the following internal pseudo-transformation:

  "12/4/2013" -> toUCT -> [storage] -> toLocal -> print "12/4/2013"

I hope this answer was helpful.

RobG
  • 124,520
  • 28
  • 153
  • 188
drankin2112
  • 4,362
  • 1
  • 12
  • 20
  • 3
    First off, this as a fantastic write-up. I wanted to point out a dependency, however. With regard to timezone specifiers, you stated: "The absence of a specifier should presume local time input." Thankfully, the ECMA-262 standard removes any need to presume. [It states](http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.15): "The value of an absent time zone offset is “Z”." So, a date/time string without a timezone specified is assumed to be an UTC rather than local time. Of course, as with so many things JavaScript, there appears to be little agreement between implementations. – Daniel Apr 04 '14 at 13:50
  • 2
    *...including the most frequently used '1/1/1970' and '1/1/1970 00:00:00 AM' formats.* — most frequently used **where**? That's not in my country, for sure. – ulidtko Apr 15 '14 at 11:59
  • 3
    @ulidtko - Sorry, I'm in US. Wow... you're right there in Kiev. I hope that you and your family stay safe and that things stabilize over there soon. Take care of yourself and good luck with everything. – drankin2112 Apr 15 '14 at 16:14
  • Just a note here. It seems that this does not work for Safari browsers (i.e. iOS or OSX). That or I've some other issue going on. – keyneom Apr 24 '14 at 22:59
  • I Believe this should be the correct answer, with so much detailed information. And for me this is working in safari. Some how I was getting away with new Date ('2014-06-19 17:47:00') on Chrome and Firefox ---- new Date (Date.parse('2014-06-19T17:47:00Z')) --- thanks – Aukhan Jun 19 '14 at 12:47
  • "*The ONLY ISO 8601 string that you can safely parse across browsers*" nope, fails in IE8. *Always* manually parse strings. – RobG Jan 03 '15 at 21:25
  • @Calne - Thanks for your kind words. In all fairness, this post came about 3 years too late so the OP probably didn't find it terribly helpful. But, I agree. There are a lot of questions on SO where the best answer comes in late, and thus, far down the screen. Maybe they'll work on that in the future. – drankin2112 Oct 14 '15 at 21:23
  • @drankin2112 I hope so, for 60% of the questions here on SO that's the case, the good answers come later :/ – Kyle Oct 15 '15 at 11:01
  • 1
    @Daniel—fortunately the ECMAScript authors fixed their error with respect to missing time zone for date and time representations. Now date and time strings without a timezone use the host timezone offset (i.e. "local"). Confusingly, ISO 8601 date only forms are [*treated as UTC*](https://tools.ietf.org/html/rfc3339) (even though it's not particularly clear from the spec), whereas ISO 8601 treats them as local, so they didn't fix everything. – RobG Jan 28 '17 at 23:37
  • "*Therefore, the following code…*" is a *non sequitur*. The format used to store the time value is unrelated to parsing. "*Ironically, [ISO 8601] is the format where browsers can differ.*" Not at all, they differ for many non-ISO formats too. Some good information in this answer, but generally parsing with the built-in parser can't be recommended with confidence for any format. – RobG Apr 10 '17 at 06:36
  • Could you please rephrase "This doesn't Jive" ? It's extremely obscure for non-english speakers. – tggm Aug 13 '19 at 12:36
72

There is some method to the madness. As a general rule, if a browser can interpret a date as an ISO-8601, it will. "2005-07-08" falls into this camp, and so it is parsed as UTC. "Jul 8, 2005" cannot, and so it is parsed in the local time.

See JavaScript and Dates, What a Mess! for more.

Brad Koch
  • 16,415
  • 18
  • 102
  • 128
danvk
  • 13,227
  • 3
  • 51
  • 86
  • 3
    "*As a general rule, if a browser can interpret a date as an ISO-8601, it will.*" is not supportable. "2020-03-20 13:30:30" is treated as ISO 8601 and local by many browsers, but Invalid Date by Safari. There are many ISO 8601 formats that are not supported by most browsers, e.g. 2004-W53-7 and 2020-092. – RobG Apr 01 '20 at 22:30
7

Another solution is to build an associative array with date format and then reformat data.

This method is useful for date formatted in an unussual way.

An example:

    mydate='01.02.12 10:20:43':
    myformat='dd/mm/yy HH:MM:ss';


    dtsplit=mydate.split(/[\/ .:]/);
    dfsplit=myformat.split(/[\/ .:]/);

    // creates assoc array for date
    df = new Array();
    for(dc=0;dc<6;dc++) {
            df[dfsplit[dc]]=dtsplit[dc];
            }

    // uses assc array for standard mysql format
    dstring[r] = '20'+df['yy']+'-'+df['mm']+'-'+df['dd'];
    dstring[r] += ' '+df['HH']+':'+df['MM']+':'+df['ss'];
Pascal Belloncle
  • 10,538
  • 3
  • 53
  • 56
guido rizzi
  • 71
  • 1
  • 1
6

Use moment.js to parse dates:

var caseOne = moment("Jul 8, 2005", "MMM D, YYYY", true).toDate();
var caseTwo = moment("2005-07-08", "YYYY-MM-DD", true).toDate();

The 3rd argument determines strict parsing (available as of 2.3.0). Without it moment.js may also give incorrect results.

Lukasz Wiktor
  • 17,016
  • 4
  • 63
  • 80
4

According to http://blog.dygraphs.com/2012/03/javascript-and-dates-what-mess.html the format "yyyy/mm/dd" solves the usual problems. He says: "Stick to "YYYY/MM/DD" for your date strings whenever possible. It's universally supported and unambiguous. With this format, all times are local." I've set tests: http://jsfiddle.net/jlanus/ND2Qg/432/ This format: + avoids the day and month order ambiguity by using y m d ordering and a 4-digit year + avoids the UTC vs. local issue not complying with ISO format by using slashes + danvk, the dygraphs guy, says that this format is good in all browsers.

Juan Lanus
  • 2,037
  • 22
  • 15
  • You may want to see [the author's answer](http://stackoverflow.com/a/9709060/425313). – Brad Koch Oct 24 '12 at 18:01
  • I would say the solution with the example in jsFiddle works enough good if you use jQuery as it uses the datepicker parser. In my case the problem is with jqGrid, but found it has its parseDate method. But anyway the example helped me by giving me an idea so +1 for it, thanks. – Vasil Popov Mar 14 '13 at 11:29
  • 2
    That article on dygraphs is wrong and the first example on the page clearly illustrates why using slashes instead of hyphens is really bad advice. At the time the article was written, using "2012/03/13" resulted in the browser parsing it as a local date, instead of UTC. The ECMAScript specification defines explicit support for using "YYYY-MM-DD" (ISO8601), so always use hyphens. It should be noted that at the time I'm writing this comment, Chrome has been patched to treat slashes as UTC. – Lachlan Hunt Mar 10 '18 at 02:01
4

While CMS is correct that passing strings into the parse method is generally unsafe, the new ECMA-262 5th Edition (aka ES5) specification in section 15.9.4.2 suggests that Date.parse() actually should handle ISO-formatted dates. The old specification made no such claim. Of course, old browsers and some current browsers still do not provide this ES5 functionality.

Your second example isn't wrong. It is the specified date in UTC, as implied by Date.prototype.toISOString(), but is represented in your local timezone.

Community
  • 1
  • 1
peller
  • 4,435
  • 16
  • 20
  • 1
    And the parsing of date strings was changed again in ECMAScript 2015 so that "2005-07-08" is local, not UTC. BTW, ES5 wasn't the standard until June 2011 (and currently ECMAScript 2015 is). ;-) – RobG Sep 23 '15 at 07:23
  • 1
    Just to confound things, TC39 decided October (just one month after my previous post) that "2005-07-08" [*should be UTC*](https://github.com/tc39/ecma262/issues/87), however ""2005-07-08T00:00:00" should be local. Both are ISO 8601 compliant formats, both are without a timezone, but are treated differently. Go figure. – RobG Feb 22 '16 at 07:29
2

This light weight date parsing library should solve all similar problems. I like the library because it is quite easy to extend. It's also possible to i18n it (not very straight forward, but not that hard).

Parsing example:

var caseOne = Date.parseDate("Jul 8, 2005", "M d, Y");
var caseTwo = Date.parseDate("2005-07-08", "Y-m-d");

And formatting back to string (you will notice both cases give exactly the same result):

console.log( caseOne.dateFormat("M d, Y") );
console.log( caseTwo.dateFormat("M d, Y") );
console.log( caseOne.dateFormat("Y-m-d") );
console.log( caseTwo.dateFormat("Y-m-d") );
Nux
  • 7,204
  • 5
  • 47
  • 62
2

Here is a short, flexible snippet to convert a datetime-string in a cross-browser-safe fashion as nicel detailed by @drankin2112.

var inputTimestamp = "2014-04-29 13:00:15"; //example

var partsTimestamp = inputTimestamp.split(/[ \/:-]/g);
if(partsTimestamp.length < 6) {
    partsTimestamp = partsTimestamp.concat(['00', '00', '00'].slice(0, 6 - partsTimestamp.length));
}
//if your string-format is something like '7/02/2014'...
//use: var tstring = partsTimestamp.slice(0, 3).reverse().join('-');
var tstring = partsTimestamp.slice(0, 3).join('-');
tstring += 'T' + partsTimestamp.slice(3).join(':') + 'Z'; //configure as needed
var timestamp = Date.parse(tstring);

Your browser should provide the same timestamp result as Date.parse with:

(new Date(tstring)).getTime()
Lorenz Lo Sauer
  • 20,692
  • 12
  • 75
  • 85
  • I'd suggest to add T to the regex to catch already JS-formatted dates as well: inputTimestamp.split(/[T \/:-]/g) – andig Jul 15 '15 at 07:47
  • If you split the string into component parts, then the most reliable next step is to use those parts as arguments to the Date constructor. Creating another string to give to the parser just gets you back to step 1. "2014-04-29 13:00:15" should be parsed as local, your code re-formats it as UTC. :-( – RobG Apr 10 '17 at 06:40
2

Both are correct, but they are being interpreted as dates with two different timezones. So you compared apples and oranges:

// local dates
new Date("Jul 8, 2005").toISOString()            // "2005-07-08T07:00:00.000Z"
new Date("2005-07-08T00:00-07:00").toISOString() // "2005-07-08T07:00:00.000Z"
// UTC dates
new Date("Jul 8, 2005 UTC").toISOString()        // "2005-07-08T00:00:00.000Z"
new Date("2005-07-08").toISOString()             // "2005-07-08T00:00:00.000Z"

I removed the Date.parse() call since it's used automatically on a string argument. I also compared the dates using ISO8601 format so you could visually compare the dates between your local dates and the UTC dates. The times are 7 hours apart, which is the timezone difference and why your tests showed two different dates.

The other way of creating these same local/UTC dates would be:

new Date(2005, 7-1, 8)           // "2005-07-08T07:00:00.000Z"
new Date(Date.UTC(2005, 7-1, 8)) // "2005-07-08T00:00:00.000Z"

But I still strongly recommend Moment.js which is as simple yet powerful:

// parse string
moment("2005-07-08").format()       // "2005-07-08T00:00:00+02:00"
moment.utc("2005-07-08").format()   // "2005-07-08T00:00:00Z"
// year, month, day, etc.
moment([2005, 7-1, 8]).format()     // "2005-07-08T00:00:00+02:00"
moment.utc([2005, 7-1, 8]).format() // "2005-07-08T00:00:00Z"
DJDaveMark
  • 2,085
  • 16
  • 27
1

The accepted answer from CMS is correct, I have just added some features :

  • trim and clean input spaces
  • parse slashes, dashes, colons and spaces
  • has default day and time

// parse a date time that can contains spaces, dashes, slashes, colons
function parseDate(input) {
    // trimes and remove multiple spaces and split by expected characters
    var parts = input.trim().replace(/ +(?= )/g,'').split(/[\s-\/:]/)
    // new Date(year, month [, day [, hours[, minutes[, seconds[, ms]]]]])
    return new Date(parts[0], parts[1]-1, parts[2] || 1, parts[3] || 0, parts[4] || 0, parts[5] || 0); // Note: months are 0-based
}
Stephane L
  • 1,445
  • 1
  • 17
  • 34