1

I have a two strings 2017-03-15 (date) and 12:26 (time). I want to create a localised date object from it, without using a library.

Remembering that here right now is: Tue Mar 14 2017 12:26:33 GMT+0800 (AWST), if I do:

new Date( date + 'T' + time )

I get the wrong result as the date is considered UTC:

Wed Mar 15 2017 20:26:00 GMT+0800 (AWST)

If I use a space:

new Date( date + ' ' + time )

The result is correct:

Wed Mar 15 2017 12:26:00 GMT+0800 (AWST)

However, this will NOT work on Safari (not without the T). Safari will actually throw an error (!).

I realise that parsing strings as date depends on the implementation. So, the only way to do it "right" would be:

var timeSplit = time.split(':');
var dateSplit = date.split('-');

new Date( dateSplit[0], dateSplit[1] - 1, dateSplit[2], timeSplit[ 0 ], timeSplit[ 1 ] )

But it's just. So. Ugly. Is there a better solution that will work across browsers?

Merc
  • 16,277
  • 18
  • 79
  • 122
  • 1
    Not that ugly. Just put it in a function and you’re good to go. – Ry- Mar 14 '17 at 04:55
  • 1
    Possible dupe — [__Invalid date in safari__](http://stackoverflow.com/questions/4310953/invalid-date-in-safari) – Rayon Mar 14 '17 at 04:57

2 Answers2

4

I get the wrong result as the date is considered UTC:

That was actually an error in the ES5 specification (which said that no timezone indicator meant UTC, which is at odds with the ISO-8601 standard it was meant to be a subset of). ES2015 corrected it to say that no timezone indicator meant local time (in keeping with ISO-8601), but that would have caused compatibility problems with existing code when used on date-only strings (like "2018-01-17"). So ES2016 had to update it again, and it's been stable since. If there's no timezone indicator, then:

  • A date-only string (such as "2019-05-20") is parsed in UTC
  • A date/time string (such as "2019-05-20T10:00") is parsed in local time

As a result of this spec confusion, for a while we had a mix of JavaScript engines that used the old ES5 behavior, the ES2015 behavior, or the ES2016 behavior. And one significant platform still has incorrect behavior: iOS.

As of this updated answer on May 20th, 2019:

  • Desktop versions of Chrome, Firefox, Edge, and even IE11 all correctly implement the ES2016+ specification.
  • Safari (desktop or iOS) incorrectly parses date/time strings without timezone indicators in UTC.
  • All current iOS browsers (Safari of course, but also Chrome, Firefox, Brave, Dolphin...) also incorrectly parse date/time strings without timezone indicators in UTC. This is because the iOS JavaScript engine, JavaScriptCore (JSC), implements this incorrectly, and non-Apple iOS applications can't allocate executable memory, so browsers can't use the engines they normally use (Chrome's V8, Firefox's SpiderMonkey, etc.) because those are optimizing engines that need to create executable code at runtime. So they use JSC instead. (Chrome's V8 has recently added a "JIT-less" pure interpreter version, so Chrome may or may not start using that rather than JSC.)

You can check your current browser here:

var may20 = new Date("2019-05-20");
// UTC hours should be 0 if that was parsed as UTC
console.log(may20.getUTCHours() === 0 ? "OK:" : "Error:", may20.toLocaleString());

var may20At10 = new Date("2019-05-20T10:00");
// Local hours should be 10 if that was parsed in local time
console.log(may20At10.getHours() === 10 ? "OK:" : "Error:", may20At10.toLocaleString());

(That check works unless you're in one of the several west African countries that use GMT year-round. It relies on UTC and local time being different from one another on May 20th, 2019. So it works in the UK because although the UK uses GMT much of the year, it's on British Summer Time [GMT+0100] for the date the check uses; and it works in New York because New York is never on GMT; but it doesn't work in Timbuktu because Timbuktu uses GMT year-round.)

However, this will NOT work on Safari (not without the T).

Right. The only date/time format that a JavaScript engine is required to support is the ISO-8601 subset defined here (and also whatever it returns from toString, but that isn't defined in the spec; it just has to be two-way). And as noted above, at least as of this writing on May 20th, 2019, Safari doesn't implement the specification correctly.

But it's just. So. Ugly. Is there a better solution that will work across browsers?

It's not ugly at all. That is, after all, what the Date constructor has to do to figure it out. Put it in your standard library and you're good to go.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Ouch... so the time SHOULD be treated as local time, but some browser basically get it wrong?!? – Merc Mar 14 '17 at 05:05
  • @Merc: Depends on your definition of wrong. :-) Some JavaScript engines haven't updated the behavior to match the ES2015 specification, and are instead still using the ES5 definition (which was erroneous). Others use the ES2015 behavior (either because they updated it, or because they refused to implement ES5's incorrect definition originally -- I believe Firefox has always done it the ES2015 way, even before the spec said it should, as they identified early that it was a bug in the ES5 spec). – T.J. Crowder Mar 14 '17 at 05:14
  • @T.J.Crowder—and the current behaviour of treating the ISO 8601 date-only form as UTC is inconsistent with ISO 8601. ;-) – RobG Mar 14 '17 at 05:53
  • @RobG: Ah, yes, the further change in *ES2016* to avoid "breaking the web." :-) – T.J. Crowder Mar 14 '17 at 06:09
  • @T.J.Crowder, To which version of Firefox are you referring to? I'm having the same problem as @Merc, in Firefox 66.0.5 (64-bits) for Ubuntu. ``` let dateString = "2019-05-19"; let data1 = new Date(dateString); console.log("data hora:",data1.toLocaleString()," | ", data1.toUTCString()); let data2 = new Date(dateString+"T00:00:00-03:00"); console.log("data hora:",data2.toLocaleString()," | ", data2.toUTCString()); date: 18/05/2019 21:00:00 | Sun, 19 May 2019 00:00:00 GMT date: 19/05/2019 00:00:00 | Sun, 19 May 2019 03:00:00 GMT ``` – Jose Tepedino May 19 '19 at 16:11
  • @JoseTepedino - They've fixed Firefox (that is, SpiderMonkey) since the answer was last edited. I've edited again. :-) – T.J. Crowder May 20 '19 at 13:07
  • @T.J.Crowder, Thank you! I'm using Firefox 67, and it seems that { new Date('yyyy-MM-dd') }, i.e. with no hour specified, will always be considered 00:00 UTC, and then be converted to the local time. What's weird is that { new Date(yyyy,MM-1,dd) } does the opposite, by considering 00:00 local time and then translating the time to UTC (I'm in GMT-03). ```let data3 = new Date(2019,4,28); console.log("data hora:",data3.toLocaleString()," | ", data3.toUTCString())``` data hora: 2019-05-28, 12:00:00 a.m. | Tue, 28 May 2019 03:00:00 GMT – Jose Tepedino May 28 '19 at 17:26
  • @JoseTepedino - *"...will always be considered 00:00 UTC, and then be converted to the local time."* The first part of that is right. The second part depends entirely on how you look, JavaScript dates have an API for accessing the UTC value (`getUTCHours`, `toUTCString`, etc.) and an API for accessing the local time version of the same datetime (`getHours`, `toString`, etc.). *"What's weird is that { new Date(yyyy,MM-1,dd) } does the opposite..."* It's not doing the opposite, the date constructor values are in local time, so again depending on the API you use to access the result... – T.J. Crowder May 28 '19 at 17:29
  • @JoseTepedino - If you want to use the multipart version but work in UTC, use [`Date.UTC`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/UTC): `dt = new Date(Date.UTC(2019, 4, 28))`. Suggested reading: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date – T.J. Crowder May 28 '19 at 17:30
  • @T.J.Crowder, thank you once again! I've just been testing that demo Date page a few minutes ago. :-) The observations I've posted above were based on comparing the outputs of the same functions "date.toLocaleString()" and "date.toUTCString()", in Firefox 67. I think the fact that { new Date('1995-12-17') vs new Date('December 17, 1995'); } return different values very confusing! They will return the same values if we specify the time (eg 00:00:00), so it seems safer to always specify the time, even if I just want to deal with dates. – Jose Tepedino May 28 '19 at 17:58
  • @JoseTepedino - `'December 17, 1995'` is not a format the spec requires `Date` to handle. That means you can't know what `Date` will do. It can parse it as UTC, parse it as local time, or refuse to parse it at all. In this case, whatever JavaScript engine you're using is apparently parsing it in local time. *"They will return the same values if we specify the time (eg 00:00:00)"* Yes, see the answer above, I explain the different handling of date-only and date/time strings. *"so it seems safer to always specify the time"* No, it pefectly safe either way. Just stick to what's specified. :-) – T.J. Crowder May 28 '19 at 18:02
  • @T.J.Crowder - I see, the spec says ISO8601 for DateString. I cited that example because { var date1 = new Date('December 17, 1995 03:24:00') } is the first example in the Date Demo, now I see that's not in the spec. I'll have a careful look at the API documentation. Thanks! – Jose Tepedino May 28 '19 at 18:28
  • @JoseTepedino - Not quite ISO8601, a subset and which treats date-only forms as UTC. – T.J. Crowder May 29 '19 at 05:46
2

I get the wrong result as the date is considered UTC:

Given:

new Date('2017-03-15T12:26')

IE up to version 8 treats it as an invalid string, IE 9+ treats it as UTC. Firefox 38 treats it as local.

If I use a space … The result is correct

Until you try Safari and get an invalid date.

However, this will NOT work on Safari (not without the T). Safari will actually throw an error (!).

Yes, and both results are consistent with all versions of ECMAScript. Safari is saying "this isn't a valid ISO 8601 string", so returns an invalid date. The others are saying the same, but then falling back to implementation specific heuristics (which they are allowed to do).

The bottom line is do not parse strings with the Date constructor (or Date.parse, they are equivalent for parsing).

So, the only way to do it "right" would be…

Yes, manually parsing the bits. You can use a library for convenience, but they will do exactly the same thing.

Is there a better solution that will work across browsers?

No. Get on the ECMA TC39 committee and beat them over the head until they decide to define a Date object that has a decent parser and formatter. There are plenty of very good existing implementations to choose from, including existing javascript libraries and other languages.

Unfortunately parsing has been virtually ignored and responsibility for formatting has passed to ECMA-402, which is very ordinary for date formatting and is really not suitable.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
RobG
  • 142,382
  • 31
  • 172
  • 209