0

I have an issue whereby a JavaScript Date object is being built incorrectly. I'm not sure how best to resolve this issue. This is what I'm doing:

var date = new Date('2016-11-31');

Now. I understand that November does not have 31 days. This is an intended accident. The problem is rather than this date simply failing to construct, it actually builds as December 1st??

Now I think this maybe a locale issue, as when attempting the same in JSFiddle (or StackOverflow snippets as below), I get October 31st?

var date = new Date('2016-11-31');
console.log(date);

Does anyone know how I can get around this issue?

Niyoko
  • 7,512
  • 4
  • 32
  • 59
Welton122
  • 1,101
  • 2
  • 13
  • 28

2 Answers2

0

The problem is rather than this date simply failing to construct, it actually builds as December 1st??

Now I think this maybe a locale issue...

No, it's a design feature. Date is expressly designed to handle rollover between dates (in this case, I think it's probably down to the abstract DateFromTime operation), which is why things like myDate.setDate(myDate.getDate() + 1) can reliably be used to increment the day, even when going from one month to the next.

In a comment you've asked if there's a way that will fail if the date string is invalid in the way you identify in your question; the only way I know is to check the result afterward:

// Note: This example only handles YYYY-MM-DD, not times
function strictCreateDate(str) {
  var date = new Date(str);
  if (isNaN(date.getTime())) {
      throw new Error("Invalid date: '" + str + "'");
  }
  var parts = str.split("-");
  if (+parts[0] != date.getFullYear() ||
      +parts[1] != date.getMonth() + 1 ||
      +parts[2] != date.getDate()) {
      throw new Error("Invalid date: '" + str + "'");
  }
  return date;
}

(If you prefer to return an invalid Date instance rather than throwing an error, change the first throw above to return date; and the second one to return new Date(NaN);.)

Example:

// Note: This example only handles YYYY-MM-DD, not times
function strictCreateDate(str) {
  var date = new Date(str);
  if (isNaN(date.getTime())) {
      throw new Error("Invalid date: '" + str + "'");
  }
  var parts = str.split("-");
  if (+parts[0] != date.getFullYear() ||
      +parts[1] != date.getMonth() + 1 ||
      +parts[2] != date.getDate()) {
      throw new Error("Invalid date: '" + str + "'");
  }
  return date;
}
function test(str) {
  try {
    var dt = strictCreateDate(str);
    console.log(str + " => " + dt.toISOString());
  }
  catch (e) {
    console.log(str + " => " + e.message);
  }
}
test('2016-11-30');
test('2016-11-31');
test('2016-08-01');

Note: There was an error in the ES5 specification which defined the behavior of new Date("2016-08-01") to interpret the string as UTC, in contravention of the ISO-8601 standard saying it should be interpreted as "local time" because there is no time zone indicator. This specification error was fixed in ES2015, but unfortunately for some time there were browsers in the wild (Firefox in particular) that continued the ES5 version. I just tested both recent Chrome and Firefox, and they both now follow the ES2015 standard, but it's important to beware. If there's any chance your code may run on browsers implementing incorrect behavior, add a timezone indicator in the standard +/-HH:MM format to the end of your string (or add a "Z" to it and use the UTC functions [getUTCFullYear, etc.] instead of the local time functions [getFullYear, etc.] above).

Community
  • 1
  • 1
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • I see. That makes sense. Is there a way to reliably construct a date and fail if the given date is incorrect? – Welton122 Oct 29 '16 at 12:10
  • @Welton122: The only way I know of is to construct the date, then check the individual parts to see if they match the individual parts of the input string. E.g., in your case, the month number and day number wouldn't match, because you'd get back 11 (for December) and 1 instead of 10 (for November) and 31. – T.J. Crowder Oct 29 '16 at 12:12
  • @Welton122: I've udpated the above with an example and some further information to keep in mind about that date format. – T.J. Crowder Oct 29 '16 at 12:26
  • I don't think this is that simple. A string like '2016-11-31' should sensibly return an invalid date, which is what some implementations do. It is really a bug in the implementation, but the standard is ambiguous in regard to parsing Dates. – RobG Oct 30 '16 at 23:41
  • @RobG: Not very ambiguous. It's true that [`Date.parse`](http://www.ecma-international.org/ecma-262/7.0/index.html#sec-date.parse), which is ultimately used by `new Date(string)`, just says *"The function first attempts to parse the format of the String according to the rules (including extended years) called out in Date Time String Format (20.3.1.16)"* but handling out-of-range values in that string is consistent with [the *specified* behavior](http://www.ecma-international.org/ecma-262/7.0/index.html#sec-makeday) of doing so in `new Date(year, month, day, hours, minutes, seconds, ms)`. – T.J. Crowder Oct 31 '16 at 07:19
  • @T.J.Crowder—hence the ambiguity. Any string that's not strictly ISO 8601 can be considered non–conforming, so "implementation-specific heuristics" come into play, which defeats the "illegal element values" returning NaN part. Chromes goes with the former, Safari and Firefox the latter so it's not clear to at least one development team. ;-) – RobG Oct 31 '16 at 23:38
0

This could be considered a bug in whatever implementation is being used (Chrome?). According to ECMA-262:

Unrecognizable Strings or dates containing illegal element values in the format String shall cause Date.parse to return NaN.

Noting that Date must parse strings as for Date.parse.

A string like '2016-11-31' should be treated as ISO 8601, and the value 31 is invalid for November, therefore new Date('2016-11-31') should return an invalid Date (i.e. one with its time value set to NaN). That is the result in Safari and Firefox, but not Chrome.

An implementation might decide that since 31 is invalid, the string is not valid ISO 8601 and therefore parse it in any way it wishes, based on:

If the String does not conform to that format the function may fall back to any implementation-specific heuristics or implementation-specific date formats.

So essentially date strings can be parsed in any way an implementation wants.

The golden rule is to never parse strings with the Date constructor (or Date.parse, they are equivalent for parsing) due to the wide variances in parsing between implementations.

RobG
  • 142,382
  • 31
  • 172
  • 209