75

I'm working on a Phonegap-based iOS app, which is already done for Android. The following lines are working fine for Android but not for iOS. Why?

var d = new Date("2015-12-31 00:00:00");
console.log(d.getDate() + '. ' + d.getMonth() + ' ' + d.getFullYear();

Result for Android:

31.11 2015

Result on iOS:

NaN. NaN NaN

Where is the difference coming from?

dda
  • 6,030
  • 2
  • 25
  • 34
Philipp Siegfried
  • 976
  • 1
  • 10
  • 14

7 Answers7

162

Your date string is not in a format specified to work with new Date. The only formats in the spec are a simplified version of ISO-8601 (added in ES5 in 2009 and updated in ES2015 and ES2016), and the format output by Date.prototype.toString. Your string isn't in either format, but it's really close to the ISO-8601-like format. It would also be easy to change it to a format that isn't in the spec, but is universally supported

Four options for you:

  • Use the upcoming Temporal feature (it's now at Stage 3)
  • The specified format
  • An unspecified format that's near-universally supported
  • Parse it yourself

Use the upcoming Temporal feature

The Temporal proposal is at Stage 3 as of this update in August 2021. You can use it to parse your string, either treating it as UTC or as local time:

Treating the string as UTC:

// (Getting the polyfill)
const {Temporal} = temporal;

const dateString = "2015-12-31 00:00:00";
const instant = Temporal.Instant.from(dateString.replace(" ", "T") + "Z");
// Either use the Temporal.Instant directly:
console.log(instant.toLocaleString());
// ...or get a Date object:
const dt = new Date(instant.epochMilliseconds);
console.log(dt.toString());
<script src="https://unpkg.com/@js-temporal/polyfill/dist/index.umd.js"></script>

Or treating it as local time:

// (Getting the polyfill)
const {Temporal} = temporal;

const dateString = "2015-12-31 00:00:00";

console.log("Parsing as local time:");
const tz = Temporal.Now.timeZone();
const instant = tz.getInstantFor(dateString.replace(" ", "T"));
console.log(instant.toLocaleString());
const dt = new Date(instant.epochMilliseconds);
console.log(dt.toString());
<script src="https://unpkg.com/@js-temporal/polyfill/dist/index.umd.js"></script>

Temporal doesn't have the problem mentioned below that the specified date/time string format historically had when no timezone was specified.

The ISO-8601-like format

If you change the space to a T, you'll be in spec:

var dateString = "2015-12-31 00:00:00";
// Treats the string as local time -- BUT READ BELOW, this varied
var d = new Date(dateString.replace(" ", "T"));
console.log(d.toString());

(I'm assuming you're not actually using a string literal, hence the replace call.)

For reliable timezone handling in old browsers, you'll also want to append a Z (for GMT/UTC) or a timezone indicator (+/- HH:MM), because the handling of strings without them was mis-specified in ES5, updated in ES2015, and then updated further in ES2016. Current versions of modern browsers follow the spec now, which says:

  • If there's a time on the string but and no timezone indicator, parse the string in local time
  • If there's no time on the string and no timezone indicator, parse the string in UTC

(ES5 said always default to UTC. ES2015 said always default to local time. ES2016 is where the current behavior was defined. It's been stable since.)

So it's best to include a timezone indicator, especially if you have to support older browsers. Note that it must be a Z (UTC) or +/- HH:MM; abbreviations like CST are not allowed, as there's no standard for them. Here's a UTC example:

var dateString = "2015-12-31 00:00:00";
// Treat the string as UTC
var d = new Date(dateString.replace(" ", "T") + "Z");
console.log(d.toString());

An unspecified format that's near-universally supported

There's a seconnd format that isn't in the specification but is near-universally supported and has been for a long time: YYYY/MM/DD HH:MM:SS, which is interpreted as local time. So:

var dateString = "2015-12-31 00:00:00";
// Treats the string as local time
var d = new Date(dateString.replace(/-/g, "/"));
console.log(d.toString());

Again, though, that's unspecified behavior, so caveat emptor. But it works in at least IE8+ (probably earlier), Chrome and anything else using the V8 JavaScript engine, Firefox, and Safari.

Parse it yourself

It's also easy to parse that string yourself. Using ES2020+ features:

function parseDate(str) {
    const [dateparts, timeparts] = str.split(" ");
    const [year, month, day] = dateparts.split("-");
    const [hours = 0, minutes = 0, seconds = 0] = timeparts?.split(":") ?? [];
    // Treats the string as UTC, but you can remove the `Date.UTC` part and use
    // `new Date` directly to treat the string as local time
    return new Date(Date.UTC(+year, +month - 1, +day, +hours, +minutes, +seconds));
}

const dateString = "2015-12-31 00:00:00";
const d = parseDate(dateString);
console.log(d.toString());

Or with only ES5-level features (since the question is from 2015):

function parseDate(str) {
    var parts = str.split(" ");
    var dateparts = parts[0].split("-");
    var timeparts = (parts[1] || "").split(":");
    var year = +dateparts[0];
    var month = +dateparts[1];
    var day = +dateparts[2];
    var hours = timeparts[0] ? +timeparts[0] : 0;
    var minutes = timeparts[1] ? +timeparts[1] : 0;
    var seconds = timeparts[2] ? +timeparts[2] : 0;
    // Treats the string as UTC, but you can remove the `Date.UTC` part and use
    // `new Date` directly to treat the string as local time
    return new Date(Date.UTC(year, month - 1, day, hours, minutes, seconds));
}

var dateString = "2015-12-31 00:00:00";
var d = parseDate(dateString);
console.log(d.toString());
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 2
    @NULL: I've never seen a JavaScript engine that didn't support it. IE6 and up do, every version of Firefox I've ever seen, every version of Chrome, every version of Opera. **But**, one must always test on ones target environments (whether something is "standard" or not, sadly). – T.J. Crowder Nov 14 '12 at 15:55
  • 10
    In my own testing I found that the `YYYY/MM/DD HH:MM:SS` solution gives the same date on iOS as I was hoping to get from `YYYY-MM-DD HH:MM:SS`, whereas the solution replacing the space with a `T` adjusted my date by the timezone amount .. (adding on 10 hours to the time). (Summary: **`YYYY/MM/DD HH:MM:SS` works**, `YYYY-MM-DDTHH:MM:SS` gets adjusted for timezone and for some situations that will be wrong) – kris Nov 02 '15 at 02:11
  • 1
    @user1290746: Probably the `Z` error in the specification, I've added a note about it above. – T.J. Crowder Nov 02 '15 at 08:30
  • @user1290746 thats because adding T converts it to universal time format. the other solution is better replacing '-' with '/' – rorypicko Jun 15 '16 at 11:38
  • @rorypicko—UTC is a standard, not a format. The T is required for standards compliance, the missing timezone means it will be treated as "local" per the host timezone offset. Omitting the T means it's treated as invalid in Safari. Replacing '-' with '/' means browsers fall back to their own heuristics, it might be treated as local or an invalid date string. – RobG May 27 '17 at 03:20
  • I went with the `/` solution. Worked perfectly fine. Thanks!. Had the problem on both Chrome and Safari for iOS. – Husterknupp Feb 12 '19 at 17:13
  • var dateString = "2015-12-31 00:00:00"; var d = new Date(dateString.replace(/-/g, '/')); This is working correctly. – Ajay Patidar Apr 08 '19 at 06:16
  • @AjayPatidar - Only because of non-standard parsing, and you can't be sure what timezone it'll use, cross-browser. Use the standard format. It's standadrized. :-) – T.J. Crowder Apr 08 '19 at 07:13
  • 1
    @T.J.Crowder - It's 2020, and the solution still applicable with my project :). Had to deal with date stored in "2015-05-30 00:00:00" format, and JavaScript in iOs (I am assuming its running off Safari) still churned out the NaN. Great solution! – Yazid Sep 05 '20 at 16:07
6

I can't tell you why. Maybe because iOS doesn't support the Javascript Date function as well as Android, or support a different format?

But I can give you a workaround:

var s = "2015-12-31 00:00:00".split(" ")[0].split("-"),
    d = new Date( s[0], s[1], s[2], 0, 0, 0 );

console.log(d);

var s = "2015-12-31 00:00:00".replace(/[ :]/g, "-").split("-"),
    d = new Date( s[0], s[1], s[2], s[3], s[4], s[5] );

console.log(d);
Andreas Louv
  • 46,145
  • 13
  • 104
  • 123
  • I will try it and get back to you – samouray Dec 12 '16 at 15:50
  • 1
    This is a valid solution. Works on both iOS and the rest. However, for some reason if you have a date that consits of 2019 2 1 The date goes backwords by a month for some reason. `var s = d.replace(/[ :]/g, "-").split("-") console.log(new Date(s[0], s[1] - 1, s[2])); ` – Adrian Grzywaczewski Feb 01 '19 at 13:59
6

A solution that works for both IOS and Android, and avoid string manipulation when it isn't required is

let fullDate = "1991-03-29 00:00:00";
let date = new Date(fullDate);

// In case its IOS, parse the fulldate parts and re-create the date object.
if(Number.isNaN(date.getMonth())) {
  let arr = fullDate.split(/[- :]/);
  date = new Date(arr[0], arr[1]-1, arr[2], arr[3], arr[4], arr[5]);
}
Eyal c
  • 1,573
  • 1
  • 11
  • 8
0

If anybody is still looking for it.

works for me in IE, Safari, IOS-FF, IOS-Safari ... etc.

getIOSSaveDateObj = function(dateString){
    if(dateString.indexOf('-') > 0){
        var arr = dateString.split(/[- :]/);
        var date = new Date(arr[0], arr[1]-1, arr[2], arr[3], arr[4], arr[5]);
    }else{
        var arr = dateString.split(/[. :]/);
        var date = new Date(arr[2], arr[1]-1, arr[0], arr[3], arr[4], arr[5]);
    }
    return date;
}
0

I had such a hard time trying to solve this, i finally went for an easier solution and used moment.js. Not much you need to do just declare the dates and works in every device.

gepex
  • 197
  • 1
  • 2
  • 14
-1

Try with var d = new Date("2015/12/31 00:00:00"); Works for me.

dda
  • 6,030
  • 2
  • 25
  • 34
  • This does not work on my iPhone MN4P2X/A with Safari. It needs the T inbetween the date and the time or it returns 'Invalid Date' – NULL pointer Mar 21 '18 at 06:01
-3

Also

var d = new Date("2015/12/31T00:00:00");

works for me :)

Thanks @dda

Zvi
  • 577
  • 6
  • 19