80

I have the following code

var c = new Date(Date.parse("2011-06-21T14:27:28.593Z"));
console.log(c);

On Chrome it correctly prints out the date on the console. In Safari it fails. Who is correct and more importantly what is the best way to handle this?

bradgonesurfing
  • 30,949
  • 17
  • 114
  • 217
  • They both give me `Tue Jun 21 2011 10:27:28 GMT-0400 (Eastern Daylight Time)` – Robert Jun 21 '11 at 14:52
  • 2
    You sure. http://jsfiddle.net/A26Gu/ run on safari Version 5.0.4 (6533.20.27) gives me an output in the console of "invalid date" – bradgonesurfing Jun 21 '11 at 14:54
  • Why do you create a Date object twice? What is your definition of correct? You may use the 'Date.toISOString()' method. But be aware: It is not supported by older browsers. – Wolfgang Kuehn Jun 21 '11 at 14:55
  • Hmm, odd... I just ran it in console, didn't bother making a page for it. It worked in console when I pulled up JSFiddle without having done anything, but any other page returns NaN. Also, I'm running 5.0.5 (7533.21.1) – Robert Jun 21 '11 at 15:00
  • Maybe I change my question a bit to. What is the best universal string format for date time that includes time zone and is easily parsed in javascript? – bradgonesurfing Jun 21 '11 at 15:46
  • 2
    Javascript Date supports 2 timezones, UTC and the local one from the OS. You can't be sure the local timezone is set correct. And as Javascript is client side, you can't really trust it does anything correct - not even parsing dates. Any application critical calculations should be done server side. – Erik Jun 21 '11 at 18:26
  • 1
    @Erik—good comment, Dates in browsers are very unreliable. The use of *Date.parse* in `new Date(Date.parse(string))` is redundant since if the [*Date constructor*](http://ecma-international.org/ecma-262/5.1/#sec-15.9.3.2) is called with a string, it's passed to *Date.parse* anyway. Also, Safari has a few bugs with creating dates that are difficult (if not impossible) to work around. – RobG Sep 30 '14 at 01:11
  • not sure if this is a problem anymore; Safari on Mojave is fine with this code; as is iOS back to version 10.3; in fact this was the only format I could use to get reliable results between all recent version of iOS and Android – Reece Jan 10 '19 at 23:07

11 Answers11

123

You can't really use Date.parse. I suggest you use: new Date (year, month [, date [, hours [, minutes [, seconds [, ms ] ] ] ] ] )

To split the string you could try

var s = '2011-06-21T14:27:28.593Z';
var a = s.split(/[^0-9]/);
//for (i=0;i<a.length;i++) { alert(a[i]); }
var d=new Date (a[0],a[1]-1,a[2],a[3],a[4],a[5] );
alert(s+ " "+d);
Erik
  • 4,120
  • 2
  • 27
  • 20
  • These date strings are coming from the server. If you have a way to parse them into the seperate components I'd be more than happy to hear. – bradgonesurfing Jun 21 '11 at 14:54
  • I would use the split funktion: http://javascript.about.com/od/hintsandtips/a/javascriptsplit.htm – Erik Jun 21 '11 at 15:05
  • It's definitely the best method to split the string from the server and then use the pieces to create the new date object. Otherwise you can't be sure what locale's date formatting logic might come into play when parsing. Will "2011-06-09" be June 9 or September 6? I assume you're 100% sure what format your server-side code is outputting. – nnnnnn Jun 22 '11 at 05:00
  • @nnnnnn - This should be OK in modern browsers. See the spec on [Date.parse](http://www.ecma-international.org/ecma-262/5.1/#sec-15.9.4.2) and [Date Time String Format](http://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.15); it should always try to parse as ISO 8601 before trying any implementation-specific oddities, and 2011-06-09 should always parse as yyyy-MM-dd UTC. – mrec Mar 12 '14 at 12:50
  • 3
    Date.parse just caught me out with old safari, thanks for your answer. – Peter Mellett Jun 25 '14 at 14:28
  • You can use Date.parse as long as you understand the issues involved... If you can live without setting time zones, and parsing everything to UTC... it'll work for you and should be faster than starting up the regex engine. If it weren't for the issues in Safari, we'd have a much better time. – Ray Foss Apr 01 '15 at 12:18
  • 13
    This only works if dates are given in local time. It strips timezone and applies local. Example: you are in -0500 timezone you send it 11am utc, you should get back 6am local, but instead you get back 11am local. – dlsso Oct 07 '16 at 19:38
  • 2
    As disso says, this answer ignores the timezone. It takes a string with offset 00:00 ("Z") and parses it as if it was local. Date.UTC should have been used. – RobG May 10 '17 at 02:32
  • My similar issue was caused by Safari not knowing how to read the timezone in a RFC 822 time zone format. I was able to fix this by using the ISO 8601 format. If you have control of the date format I got this working with "yyyy-MM-dd'T'HH:mm:ss.sssXXX" which creates ie. "2018-02-06T20:00:00.000+00:00". For whatever reason Safari can't read "2018-02-06T20:00:00.000+0000", notice the lack of colon in the timezone format. – Olmstov Mar 06 '18 at 19:03
  • As RobG mentioned please change the answer to new Date(Date.UTC(...)), the timezone is pretty important. – chris Dec 26 '18 at 01:51
  • Not usable, does not care about timezone, incorrect result for anyone who is not in GMT timezone. It should be mentioned in answer. – mikep Dec 27 '19 at 16:09
  • i updated my code per above suggestion where the date object is created by `Date.UTC()` and this works beautifully. Thank you! – LiquidMusic1 Jul 15 '20 at 17:36
28

My similar issue was caused by Safari not knowing how to read the timezone in a RFC 822 time zone format. I was able to fix this by using the ISO 8601 format. If you have control of the date format I got this working with java's SimpleDateFormat "yyyy-MM-dd'T'HH:mm:ss.sssXXX" which produces for me ie. "2018-02-06T20:00:00.000+04:00". For whatever reason Safari can't read "2018-02-06T20:00:00.000+0400", notice the lack of colon in the timezone format.

// Works
var c = new Date("2018-02-06T20:00:00.000+04:00"));
console.log(c);

// Doesn't work
var c = new Date("2018-02-06T20:00:00.000+0400"));
console.log(c);
Olmstov
  • 563
  • 7
  • 18
  • 1
    Found the same behavior; haven't yet been able to find documentation of this being a known bug. However, both formats (with colon, and without colon) do seem to be intended as [acceptable formats](https://en.wikipedia.org/wiki/ISO_8601#Time_offsets_from_UTC): `The UTC offset is appended to the time in the same way that 'Z' was above, in the form ±[hh]:[mm], ±[hh][mm], or ±[hh].` And indeed, Chrome & Firefox are able to parse the offsets without colons – Jack Koppa Dec 19 '19 at 20:13
  • 2
    [This blog post](http://farai.github.io/blog/2015/02/16/javascript-nan-for-new-date-object-in-ie-and-safari/) explains the problem & one regex solution, for Safari & Internet Explorer, but still doesn't indicate whether Safari or IE have somewhere acknowledged that they don't match the ISO spec – Jack Koppa Dec 19 '19 at 20:33
  • 1
    Ah, here it is: https://bugs.webkit.org/show_bug.cgi?id=160287 Yet unaddressed, and given that a fix definitely wouldn't come for IE, probably simplest to include the colon, if one is able to. – Jack Koppa Dec 19 '19 at 20:38
  • 1
    Perfect if you need dates with a timezone. Note that `new Date("2020-04-16T20:00+04:00"))` works as well across browsers (Chrome, Firefox, Safari, Edge). – G. Egli Apr 16 '20 at 21:10
19

I tend to avoid Date.parse, as per the other answers for this question. It doesn't seem to be a portable way to reliably deal with dates.

Instead, I have used something like the function below. This uses jQuery to map the string array into a number array, but that's a pretty easy dependency to remove / change. I also include what I consider sensible defaults, to allow you to parse 2007-01-09 and 2007-01-09T09:42:00 using the same function.

function dateFromString(str) {
  var a = $.map(str.split(/[^0-9]/), function(s) { return parseInt(s, 10) });
  return new Date(a[0], a[1]-1 || 0, a[2] || 1, a[3] || 0, a[4] || 0, a[5] || 0, a[6] || 0);
}
jabley
  • 2,212
  • 2
  • 19
  • 23
18

I've checked it in several browsers, and yes, safari returns invalid date. By the way, you don't have to use Date.parse here, just new Date([datestring]) will work too. Safari evidently requires more formatting of the datestring you supply. If you replace '-' with '/', remove the T and everything after the dot (.593Z), it will give you a valid date. This code is tested and works in Safari

var datestr = '2011-06-21T14:27:28.593Z'.split(/[-T.]/);
var safdat = new Date( datestr.slice(0,3).join('/')+' '+datestr[3] );

Or using String.replace(...):

new Date("2016-02-17T00:05:01+0000".replace(/-/g,'/').replace('T',' ').replace(/(\..*|\+.*/,""))
Daniel Sokolowski
  • 11,982
  • 4
  • 69
  • 55
KooiInc
  • 119,216
  • 31
  • 141
  • 177
  • 9
    This causes you to lose the timezone and the fractions of a second the resulting Date object. I guess you could add some additional code at the end to re-add those components. – Tim Tisdall Feb 22 '13 at 15:37
  • 2
    Parenthesis in 3rd regex not needed: `new Date("2016-02-17T00:05:01+0000".replace(/-/g,'/').replace('T',' ').replace(/\..*|\+.*/,""))` – steevee Dec 01 '16 at 19:35
  • just using ```.replace(/-/g,'/')``` works. my datestring is like this "2019-06-26 23:59:59" – mars-o Oct 22 '19 at 17:54
14

I use the following function for parsing dates with timezone. Works fine both Chrome and Safari:

function parseDate(date) {
  const parsed = Date.parse(date);
  if (!isNaN(parsed)) {
    return parsed;
  }

  return Date.parse(date.replace(/-/g, '/').replace(/[a-z]+/gi, ' '));
}

console.log(parseDate('2017-02-09T13:22:18+0300'));  // 1486635738000 time in ms
Londeren
  • 3,202
  • 25
  • 26
  • 2
    This is the safest way to do it. I guess you should change the last replace to something like `/[a-z]+/gi`, to match a wider audience, like dates with UTC instead of only T. – Narayon Feb 16 '17 at 10:30
  • 1
    @Narayon thanks for your remark, I've changed the answer. – Londeren Feb 17 '17 at 07:22
5

I ended up using a library to offset this:

http://zetafleet.com/blog/javascript-dateparse-for-iso-8601

Once that library was included, you use this code to create the new date:

var date = new Date(Date.parse(datestring));

Our project wasn't using millisecond specifiers, but I don't believe that will cause an issue for you.

AaronSieb
  • 8,106
  • 8
  • 39
  • 58
2

Instead of using 'Z' at the end of the date string, you can add the local client timezone offset. You'll probably want a method to generate that for you:

let timezoneOffset = () => {
    let date = new Date(),
        timezoneOffset = date.getTimezoneOffset(),
        hours = ('00' + Math.floor(Math.abs(timezoneOffset/60))).slice(-2),
        minutes = ('00' + Math.abs(timezoneOffset%60)).slice(-2),
        string = (timezoneOffset >= 0 ? '-' : '+') + hours + ':' + minutes;
    return string;
}

So the end result would be:

var c = new Date("2011-06-21T14:27:28.593" + timezoneOffset());

adjwilli
  • 9,658
  • 4
  • 35
  • 29
  • 1
    The Z is the timezone offset, it stands for "zero hour offset" also known as "Zulu time" (UTC), or +00:00 – seBaka28 Jan 23 '20 at 09:44
2

Here is a more robust ISO 8601 parser than what others have posted. It does not handle week format, but it should handle all other valid ISO 8601 dates consistently across all browsers.

function newDate(value) {
  var field = value.match(/^([+-]?\d{4}(?!\d\d\b))(?:-?(?:(0[1-9]|1[0-2])(?:-?([12]\d|0[1-9]|3[01]))?)(?:[T\s](?:(?:([01]\d|2[0-3])(?::?([0-5]\d))?|24\:?00)([.,]\d+(?!:))?)?(?::?([0-5]\d)(?:[.,](\d+))?)?([zZ]|([+-](?:[01]\d|2[0-3])):?([0-5]\d)?)?)?)?$/) || [];
  var result = new Date(field[1], field[2] - 1 | 0, field[3] || 1, field[4] | 0, field[5] | 0, field[7] | 0, field[8] | 0)
  if (field[9]) {
    result.setUTCMinutes(result.getUTCMinutes() - result.getTimezoneOffset() - ((field[10] * 60 + +field[11]) || 0));
  }
  return result;
}

console.log(newDate('2011-06-21T14:27:28.593Z'));
console.log(newDate('1970-12-31T06:00Z'));
console.log(newDate('1970-12-31T06:00-1200'));
Adam Leggett
  • 3,714
  • 30
  • 24
2

Use this to both (Safari / Chrome):

Date.parse("2018-02-06T20:00:00.000-03:00")
Ivan Ferrer
  • 558
  • 1
  • 5
  • 12
1

i tried converted date by truncating it and parsing it like that , its working fine with safari and ios .

var dateString = "2016-01-22T08:18:10.000+0000";
 var hours = parseInt(dateString.split("+")[1].substr("0","2"));
 var mins = parseInt(dateString.split("+")[1].substr("2"));
 var date = new Date(dateString.split("+")[0]);
 date.setHours(date.getHours()-hours);
 date.setMinutes(date.getMinutes()-mins);
Anky
  • 270
  • 1
  • 5
  • 20
0

Instead of using a 3rd party library, this is my - relatively simple - solution for this:

function parseDateTime(datetime, timezone) {

  base = new Date(datetime.replace(/\s+/g, 'T') + 'Z');

  hoursUTC = base.toLocaleTimeString('de-AT',{ timeZone: 'UTC' }).split(':')[0];
  hoursLocal = base.toLocaleTimeString('de-AT',{ timeZone: 'Europe/Vienna' }).split(':')[0];
  
  timeZoneOffsetSign = (hoursLocal-hoursUTC) < 0 ? '-':'+';
  timeZoneOffset = Math.abs(hoursLocal-hoursUTC);
  timeZoneOffset = timeZoneOffsetSign + timeZoneOffset.toString().padStart(2, '0') + ':00';
  
  return new Date(datetime.replace(/\s+/g, 'T') + timeZoneOffset);
}

localDate = parseDateTime('2020-02-25 16:00:00','Europe/Vienna');
console.log(localDate);
console.log(localDate.toLocaleString('de-AT','Europe/Vienna'));
schnere
  • 186
  • 6