First of all, I think timezone probably has something to do with this. I'm in EST/EDT. Also, I'm testing this on chromium 17 / linux.
Now, let's say I create two dates like this:
// December 5 dateFromNumbers = new Date(2020, 11, 5); dateFromString = new Date("2020-12-5");
It seems these dates should have identical timestamps, and they do:
+dateFromNumbers == +dateFromString; // true
...at least in this case. But in some cases, they don't:
// December 15 dateFromNumbers = new Date(2020, 11, 15); dateFromString = new Date("2020-12-15"); +dateFromNumbers == +dateFromString; // false
What's going on here?
dateFromNumbers; // Tue Dec 15 2020 00:00:00 GMT-0500 (EST) dateFromString; // Mon Dec 14 2020 19:00:00 GMT-0500 (EST)
dateFromString is 5 hours earlier than
dateFromNumbers in this case (EST is GMT - 5, I'm sure it's related somehow).
It seems to affect the ends of months October through December. Here's a fiddle that makes it easy to see which days differ (unless you are red-green colorblind, in that case it may be difficult to see, my apologies).
- You can set your system timezone to EST/EDT to see the jsfiddle example as I'm seeing it.
- Date's month numbers are zero-based; the
11is not a typo.
- This issue appears in every year that I checked.
After looking in V8's source code:
// Specification: // Accept ES5 ISO 8601 date-time-strings or legacy dates compatible // with Safari. <...> // A string that matches both formats (e.g. 1970-01-01) will be // parsed as an ES5 date-time string - which means it will default // to UTC time-zone. That's unavoidable if following the ES5 // specification.
Reading the surrounding code, it seems that a date-time string with both month and day 2 symbols long is considered a valid ES5 date-time string. Further down in the ES5 parser, after it parses the dates and times, there's a comment:
// Successfully parsed ES5 Date Time String. Default to UTC if no TZ given.
In case of "YYYY-MM-DD", once the code gets that far, the ES5 parser has successfully parsed the entire string, so it returns before the legacy parser gets a chance to localize the timezone. Otherwise (month/day are one symbol long) it's treated as a "legacy" datetime and the legacy parser gets to deal with it and localize it.