75

I input new Date("2017-01-01") in chrome console, the output shows its hour is 8, but new Date("2017-01-1") and new Date("2017-1-01") shows their hour are both 0, so how does new Date(dateString) parse?

new Date("2017-01-01")
// Sun Jan 01 2017 08:00:00 GMT+0800 (中国标准时间)*
new Date("2017-01-1")
// Sun Jan 01 2017 00:00:00 GMT+0800 (中国标准时间)*
new Date("2017-1-1")
// Sun Jan 01 2017 00:00:00 GMT+0800 (中国标准时间)*
new Date("2017-1-01")
// Sun Jan 01 2017 00:00:00 GMT+0800 (中国标准时间)*
Michał Urbaniak
  • 1,249
  • 13
  • 28
hiway
  • 3,906
  • 10
  • 34
  • 57
  • You might want to check out this link http://stackoverflow.com/questions/7556591/javascript-date-object-always-one-day-off/31732581#31732581 – SoEzPz Mar 28 '17 at 15:23
  • 1
    Reason #42123 why I don't want to deal with JS... – Antzi Mar 29 '17 at 06:12

4 Answers4

78

"2017-01-01" follows the ISO standard ES5 Date Time String format (simplification of ISO 8601 Extended Format), and therefore it is in UTC time, which is 8am in China. All other strings are parsed as local time in Chrome1.


1 Relevant source code in Chromium: https://cs.chromium.org/chromium/src/v8/src/dateparser-inl.h?type=cs&l=16

Date parsing in Chromium follows the standard ES5 rules as well as these extra rules:

  • Any unrecognized word before the first number is ignored.
  • Parenthesized text is ignored.
  • An unsigned number followed by : is a time value, and is added to the TimeComposer. A number followed by :: adds a second zero as well. A number followed by . is also a time and must be followed by milliseconds. Any other number is a date component and is added to DayComposer.
  • A month name (or really: any word having the same first three letters as a month name) is recorded as a named month in the Day composer.
  • A word recognizable as a time-zone is recorded as such, as is (+|-)(hhmm|hh:).
  • Legacy dates don't allow extra signs (+ or -) or unmatched ) after a number has been read (before the first number, any garbage is allowed).
  • Any strings that satisfy the ES5 rules and the rules above will be parsed using ES5 rules. This means "1970-01-01" will be in UTC time-zone not in local time-zone.

What does that mean?

First note that "2017-01-01" is parsed in UTC time because it is a "date" string instead of a "date-time" string, and it matches the ES5 definition of a "date" string. If time is attached, then it will follow the ISO standard and parse it in local time.

Examples:

  • 2017-01-01 - Jan 1, 2017 in UTC time
  • 2017-01-01T00:00 - Jan 1, 2017 in local time
  • 2017-1-1 - Jan 1, 2017 in local time
  • 2017-(hello)01-01 - Jan 1, 2017 in local time
  • may 2017-01-01 - May 1, 2017 in local time
  • mayoooo 2017-01-01 - May 1, 2017 in local time
  • "jan2017feb-mar01apr-may01jun" - Jun 1, 2017 in local time
Community
  • 1
  • 1
Derek 朕會功夫
  • 92,235
  • 44
  • 185
  • 247
  • 9
    *"All other strings are parsed as local time."* Not necessarily. Once you're not parsing the only date/time format in the spec, you're into *"implementation-specific heuristics or implementation-specific date formats"* and all bets are off. – T.J. Crowder Mar 28 '17 at 04:00
  • 2
    @T.J.Crowder Added "in Chrome" for accuracy. – Derek 朕會功夫 Mar 28 '17 at 04:05
  • " "2017-01-01" follows the ISO " So... what about `2016-12-31`? That would be still in iso and in the same format like the rest of the dates in question. Is non-ISO input considered inappropriate for this function, or is there something smelly going on? – Peter Badida Mar 28 '17 at 06:08
  • 4
    @KeyWeeUsr Are you saying you get a different result for "2016-12-31"? For me that one parses as ISO also. – Mr Lister Mar 28 '17 at 06:49
  • 1
    @MrLister No, I'm just wondering if the function strictly expects ISO or why isn't it capable to add "0"s automatically to the string. – Peter Badida Mar 28 '17 at 07:30
  • 2
    @KeyWeeUsr: The format calls for four digits for year and two digits for month and day. Of course it handles 2016-12-31. What it wouldn't handle is 2016-2-28. – T.J. Crowder Mar 28 '17 at 11:39
  • @KeyWeeUsr It doesn't _strictly expect_ ISO, and it's not that it "_isn't capable to add "0"s_", but that _if_ the input _does_ conform to ISO (i.e. month/day are two digits etc.) _then_ it treats it as a UTC date/time. Since you haven't specified a time, it defaults to 00:00. When treated as UTC, this becomes 8am China local time (first result). For the other three, because the input _doesn't_ conform to ISO format, the input is treated as a local date/time, so 00:00 is taken as a local time. In all cases, the string produced shows local time. – TripeHound Mar 28 '17 at 13:15
  • 4
    "standard ES5 rules", while true, is possibly the weirdest proposition I have read today. – njzk2 Mar 28 '17 at 13:43
  • @TripeHound oh, so there's the "catch"! If it's not in the ISO shape, I get the Greenwich time and if it is, I get it from my own timezone. Still I think for a date that's just silly _not_ to implement. Over a single zero in date string you can ruin the whole code. Nice. – Peter Badida Mar 28 '17 at 13:47
  • @T.J.Crowder well, I know how to count digits, hehe :D the point is pretty much that I was confused wtf is going on as I just see it as a bug (or better said silently hidden error) probably like OP. If it's not in ISO, it should **throw an error**, not parse nonsense anyway. – Peter Badida Mar 28 '17 at 13:50
  • 1
    @KeyWeeUsr Not quite: The output is always localtime (see `.toISOString()` for an alternative). However, if the input **is** in ISO format, the whole thing (including time default of 00:00) gets _treated_ as Greenwich/GMT/UTC (and so shows 08:00 localtime). If the input **isn't** in ISO, then the whole thing (including the default 00:00) is treated as local time (or, strictly as other answers say, it's implementation specific)... – TripeHound Mar 28 '17 at 14:02
  • 1
    ... and the almost certain reason it doesn't throw an error for non-ISO input is that historically this function probably just "tried to interpret a date" long before anyone thought to standardise either the input or it's behaviour, and changing that now would break too much. – TripeHound Mar 28 '17 at 14:04
  • @TripeHound hm, so it's kind of "lesser evil", I see. – Peter Badida Mar 28 '17 at 14:13
  • @KeyWeeUsr It's a compromise, for sure. If someone needs to be pedantically correct (ISO format, UTC timezone), they can, but they have to be on-the-dot; if they don't care, they get pretty much what they expect (local time). – jpaugh Mar 28 '17 at 20:51
  • 1
    @KeyWeeUsr It throws on valid ISO, tho. "2017-088" is a valid ISO 8601 date, it's the 88th day of 2017 year, Chrome shows "Invalid Date". – Atomosk Mar 29 '17 at 02:01
  • 1
    @Atomosk According to [Section 20.3.1.16 of the ECMAScript specification](http://www.ecma-international.org/ecma-262/6.0/#sec-date-time-string-format), the date format used is a _simplification_ of ISO 8601 (presumably more in line with [RFC 3339 Date and Time on the Internet](https://tools.ietf.org/html/rfc3339)) and doesn't include Ordinal dates such as 2017-088. – TripeHound Mar 29 '17 at 06:58
18

difference between new Date("2017-01-01") and new Date("2017-1-1")

new Date("2017-01-01") is in-spec (more below). new Date("2017-1-1") is not, and so falls back on any "...implementation-specific heuristics or implementation-specific date formats" the JavaScript engine wants to apply. E.g., you have no guarantee how (or whether) it will successfully parse and if so, whether it will be parsed as UTC or local time.

Although new Date("2017-01-01") is in-spec, sadly what browsers are supposed to do with it has been a moving target, because it doesn't have a timezone indicator on it:

  • In ES5 (December 2009), strings without a timezone indicator were supposed to be parsed as UTC. But that's at variance with the ISO-8601 standard the date/time format is based on, which says strings without a timezone indicator are meant to be local time, not UTC. So in ES5, new Date("2017-01-01") is parsed in UTC.
  • In ES2015 (aka ES6, June 2015), strings without a timezone indicator were supposed to be local time, not UTC, like ISO-8601. So in ES2015, new Date("2017-01-01") is parsed as local time.
  • But, that was changed again in ES2016 (June 2016) because browsers had been parsing date-only forms with - in them as UTC for years. So as of ES2016, date-only forms (like "2017-01-01") are parsed in UTC, but date/time forms (like "2017-01-01T00:00:00") are parsed in local time.

Sadly, not all JavaScript engines currently implement the spec. Chrome (as of this writing, v56) parses date/time forms in UTC even though they should be local time (so does IE9). But Chrome, Firefox, and IE11 (I don't have IE10 or Edge handy) all handle date-only forms correctly (as UTC). IE8 doesn't implement the ISO-8601 form at all (having been released before the ES5 spec was released).

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
3

When parsing dates, JavaScript interprets ISO dates as UTC time and other formats as local time.

As MDN article suggests,

Where the string is ISO 8601 date only, the UTC time zone is used to interpret arguments.

Given a date string of "March 7, 2014", parse() assumes a local time zone, but given an ISO format such as "2014-03-07" it will assume a time zone of UTC (ES5 and ECMAScript 2015). Therefore Date objects produced using those strings may represent different moments in time depending on the version of ECMAScript supported unless the system is set with a local time zone of UTC. This means that two date strings that appear equivalent may result in two different values depending on the format of the string that is being converted.

// 2017-03-28 is interpreted as UTC time,
// shown as 2017-03-28 00:00:00 in UTC timezone,
// shown as 2017-03-28 06:00:00 in my timezone:
console.log("ISO dates:");
var isoDates = [new Date("2017-03-28")];
for (var dt of isoDates)
{
  console.log(dt.toUTCString() + " / " + dt.toLocaleString());
}

// Other formats are interpreted as local time, 
// shown as 2017-03-27 18:00:00 in my timezone,
// shown 2017-03-28 00:00:00 in my timezone:
console.log("Other formats:");
var otherDates = [new Date("2017-3-28"), new Date("March 28, 2017"), new Date("2017/03/28")];
for (var dt of otherDates)
{
  console.log(dt.toUTCString() + " / " + dt.toLocaleString());
}
Community
  • 1
  • 1
Yeldar Kurmangaliyev
  • 33,467
  • 12
  • 59
  • 101
  • 2
    *"... and other formats as local time"* Not necessarily. Once you're not parsing the only date/time format in the spec, you're into *"implementation-specific heuristics or implementation-specific date formats"* and all bets are off. – T.J. Crowder Mar 28 '17 at 03:48
2

This format is International Standard (ISO format)

new Date("2017-01-01")

This guarantees same output across all browsers.

However, the other formats may change according to browser as they are not so well defined.

As you can see that this format

new Date("2017-1-1")

is parsed successfully in chrome, but gives error in IE 11

Raman Sahasi
  • 30,180
  • 9
  • 58
  • 71