133

My date objects in JavaScript are always represented by UTC +2 because of where I am located. Hence like this

Mon Sep 28 10:00:00 UTC+0200 2009

Problem is doing a JSON.stringify converts the above date to

2009-09-28T08:00:00Z  (notice 2 hours missing i.e. 8 instead of 10)

What I need is for the date and time to be honoured but it's not, hence it should be

2009-09-28T10:00:00Z  (this is how it should be)

Basically I use this:

var jsonData = JSON.stringify(jsonObject);

I tried passing a replacer parameter (second parameter on stringify) but the problem is that the value has already been processed.

I also tried using toString() and toUTCString() on the date object, but these don't give me what I want either..

Can anyone help me?

Rahil Wazir
  • 10,007
  • 11
  • 42
  • 64
mark smith
  • 20,637
  • 47
  • 135
  • 187
  • 24
    `2009-09-28T10:00:00Z` *does not represent the same moment in time* as `Mon Sep 28 10:00:00 UTC+0200 2009`. The `Z` in an [ISO 8601](http://en.wikipedia.org/wiki/ISO_8601) date means UTC, and 10 o'clock in UTC is *a different moment in time* to 10 o'clock in +0200. It would be one thing to want the date to be serialized with the right time zone, but you're asking us to help you serialise it to a representation that is unequivocally, objectively *wrong*. – Mark Amery Feb 22 '15 at 20:45
  • 1
    To add to Marks comment, in most cases it is best practice to store your datetimes as UTC time, so you can support users in different timezones – JoeCodeFrog Jun 06 '17 at 10:10
  • The accepted answer of this solve my problem https://stackoverflow.com/questions/31096130/how-to-json-stringify-a-javascript-date-and-preserve-timezone/31104671 – Chathura Sudarshana Feb 02 '21 at 05:05

20 Answers20

77

Recently I have run into the same issue. And it was resolved using the following code:

x = new Date();
let hoursDiff = x.getHours() - x.getTimezoneOffset() / 60;
let minutesDiff = (x.getHours() - x.getTimezoneOffset()) % 60;
x.setHours(hoursDiff);
x.setMinutes(minutesDiff);
Rohit Pavaskar
  • 308
  • 2
  • 12
Anatoliy
  • 29,485
  • 5
  • 46
  • 45
  • yes but this is if the website is used within my country, if its used in another country like USA - it wouldn't be 2 ... – mark smith Sep 28 '09 at 12:41
  • Obviously, this value should be calculated. – Anatoliy Sep 28 '09 at 12:58
  • 3
    thanks... I actually found a great library here, http://blog.stevenlevithan.com/archives/date-time-format all you need to do this (maybe it will help you) , you pass false and it doesn't convert. var something = dateFormat(myStartDate, "isoDateTime", false); – mark smith Sep 28 '09 at 14:02
  • 15
    this is incorrect as it makes your code non-timezone safe -- you should be correcting the timezone when your read the date back in. – olliej Sep 28 '09 at 17:51
  • I just noticed my date time value was changed in a JSON string. It works like a charm now, thanks :). – Tien Do Apr 05 '13 at 10:43
  • @marksmith the solution on the link you've shared ( http://blog.stevenlevithan.com/archives/date-time-format ) actually worked like charm for me. I was about to go crazy. Thank you. +1 – Laci Feb 12 '14 at 10:05
  • 9
    This answer is wrong. The OP doesn't realise that "2009-09-28T08:00:00Z" and "Mon Sep 28 10:00:00 UTC+0200 2009" are **exactly the same moment in time** and that adjusting for the timezone offset is actually creating the wrong time. – RobG May 11 '17 at 22:51
  • You need to take care of the minutes too. Overwise countries with 30min offsets (India Standard Time is UTC+5:30). add `x.setMinutes(x.getMinutes() - (x.getTimezoneOffset() % 60));` – JScoobyCed Sep 03 '17 at 10:27
51

JSON uses the Date.prototype.toISOString function which does not represent local time -- it represents time in unmodified UTC -- if you look at your date output you can see you're at UTC+2 hours, which is why the JSON string changes by two hours, but if this allows the same time to be represented correctly across multiple time zones.

Liam
  • 27,717
  • 28
  • 128
  • 190
olliej
  • 35,755
  • 9
  • 58
  • 55
  • 2
    Never thought of this, but you are right. This is the solution: I can specify any format I prefer by using the prototyping. – racs Sep 12 '13 at 04:02
25

date.toJSON() prints the UTC-Date into a String formatted (So adds the offset with it when converts it to JSON format).

date = new Date();
new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toJSON();
ali-myousefi
  • 822
  • 2
  • 11
  • 26
  • 10
    Usually it is a good idea to add an explanation on what your code does. This allows newer developers to understand how the code works. – Caleb Kleveter Jan 04 '17 at 15:44
  • Can you explain why the code in the answer should work? – Cristik Jan 04 '17 at 21:58
  • This Works for me too, But can you explain how you did that? – Deven Patil Apr 23 '18 at 21:01
  • I'll try to explain the second line of this code.. `date.getTime()` returns time in milleseconds, so we should convert the second operand to milliseconds as well. Since `date.getTimezoneOffset()` returns offset in minutes, we multiply it 60000, because 1 minute = 60000milliseconds. So by subtracting offset from current time we get the time in UTC. – Maksim Pavlov Apr 24 '18 at 09:50
  • For me this is the best answer – auron344 Jul 02 '21 at 08:45
22

Just for the record, remember that the last "Z" in "2009-09-28T08:00:00Z" means that the time is indeed in UTC.

See http://en.wikipedia.org/wiki/ISO_8601 for details.

Locoluis
  • 784
  • 6
  • 8
17

Out-of-the-box solution to force JSON.stringify ignore timezones:

  • Pure javascript (based on Anatoliy answer):

// Before: JSON.stringify apply timezone offset
const date =  new Date();
let string = JSON.stringify(date);
console.log(string);

// After: JSON.stringify keeps date as-is!
Date.prototype.toJSON = function(){
    const hoursDiff = this.getHours() - this.getTimezoneOffset() / 60;
    this.setHours(hoursDiff);
    return this.toISOString();
};
string = JSON.stringify(date);
console.log(string);

Using moment + moment-timezone libraries:

const date =  new Date();
let string = JSON.stringify(date);
console.log(string);

Date.prototype.toJSON = function(){
    return moment(this).format("YYYY-MM-DDTHH:mm:ss:ms");;
};
string = JSON.stringify(date);
console.log(string);
<html>
  <header>
    <script src="https://momentjs.com/downloads/moment.min.js"></script>
    <script src="https://momentjs.com/downloads/moment-timezone-with-data-10-year-range.min.js"></script>
</header>
</html>
Benjamin Caure
  • 2,090
  • 20
  • 27
  • Thanks but it´s better to create new instance of Date – Kjartan Valur Þórðarson Mar 05 '22 at 11:20
  • It depends on your project: if you have tons of existing dates and you figure out this issue when deploying to other countries, you have no choice other than apply a global patch (sometimes you don't have access directly to JSON.stringify, e.g. with Angular or Axios). However, if you can modify every piece of code that create date instances, the other issues are better. – Benjamin Caure Mar 05 '22 at 16:28
12

Here is another answer (and personally I think it's more appropriate)

var currentDate = new Date(); 
currentDate = JSON.stringify(currentDate);

// Now currentDate is in a different format... oh gosh what do we do...

currentDate = new Date(JSON.parse(currentDate));

// Now currentDate is back to its original form :)
aug
  • 11,138
  • 9
  • 72
  • 93
  • @Rohaan thanks for pointing that out but the tags on the question mention JavaScript. – aug Jan 22 '15 at 17:27
4

you can use moment.js to format with local time:

Date.prototype.toISOString = function () {
    return moment(this).format("YYYY-MM-DDTHH:mm:ss");
};
3

I'm a little late but you can always overwrite the toJson function in case of a Date using Prototype like so:

Date.prototype.toJSON = function(){
    return Util.getDateTimeString(this);
};

In my case, Util.getDateTimeString(this) return a string like this: "2017-01-19T00:00:00Z"

  • 4
    Note that overwriting browser globals can break third-party libraries that you embed, and is a big anti-pattern. Never do this in production. – jakub.g May 07 '19 at 12:04
3

I run into this a bit working with legacy stuff where they only work on east coast US and don't store dates in UTC, it's all EST. I have to filter on the dates based on user input in the browser so must pass the date in local time in JSON format.

Just to elaborate on this solution already posted - this is what I use:

// Could be picked by user in date picker - local JS date
date = new Date();

// Create new Date from milliseconds of user input date (date.getTime() returns milliseconds)
// Subtract milliseconds that will be offset by toJSON before calling it
new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toJSON();

So my understanding is this will go ahead and subtract time (in milliseconds (hence 60000) from the starting date based on the timezone offset (returns minutes) - in anticipation for the addition of time toJSON() is going to add.

MrRobboto
  • 722
  • 3
  • 10
3

JavaScript normally convert local timezone to UTC .

date = new Date();
date.setMinutes(date.getMinutes()-date.getTimezoneOffset())
JSON.stringify(date)
Sarath Ak
  • 7,903
  • 2
  • 47
  • 48
2

Usually you want dates to be presented to each user in his own local time-

that is why we use GMT (UTC).

Use Date.parse(jsondatestring) to get the local time string,

unless you want your local time shown to each visitor.

In that case, use Anatoly's method.

kennebec
  • 102,654
  • 32
  • 106
  • 127
2

Got around this issue by using the moment.js library (the non-timezone version).

var newMinDate = moment(datePicker.selectedDates[0]);
var newMaxDate = moment(datePicker.selectedDates[1]);

// Define the data to ask the server for
var dataToGet = {"ArduinoDeviceIdentifier":"Temperatures",
                "StartDate":newMinDate.format('YYYY-MM-DD HH:mm'),
                "EndDate":newMaxDate.format('YYYY-MM-DD HH:mm')
};

alert(JSON.stringify(dataToGet));

I was using the flatpickr.min.js library. The time of the resulting JSON object created matches the local time provided but the date picker.

Sky
  • 1,435
  • 1
  • 15
  • 24
Paul
  • 21
  • 1
2

Here is something really neat and simple (atleast I believe so :)) and requires no manipulation of date to be cloned or overloading any of browser's native functions like toJSON (reference: How to JSON stringify a javascript Date and preserve timezone, courtsy Shawson)

Pass a replacer function to JSON.stringify that stringifies stuff to your heart's content!!! This way you don't have to do hour and minute diffs or any other manipulations.

I have put in console.logs to see intermediate results so it is clear what is going on and how recursion is working. That reveals something worthy of notice: value param to replacer is already converted to ISO date format :). Use this[key] to work with original data.

var replacer = function(key, value)
{
    var returnVal = value;
    if(this[key] instanceof Date)
    {
        console.log("replacer called with key - ", key, " value - ", value, this[key]); 

        returnVal = this[key].toString();

        /* Above line does not strictly speaking clone the date as in the cloned object 
         * it is a string in same format as the original but not a Date object. I tried 
         * multiple things but was unable to cause a Date object being created in the 
         * clone. 
         * Please Heeeeelp someone here!

        returnVal = new Date(JSON.parse(JSON.stringify(this[key])));   //OR
        returnVal = new Date(this[key]);   //OR
        returnVal = this[key];   //careful, returning original obj so may have potential side effect

*/
    }
    console.log("returning value: ", returnVal);

    /* if undefined is returned, the key is not at all added to the new object(i.e. clone), 
     * so return null. null !== undefined but both are falsy and can be used as such*/
    return this[key] === undefined ? null : returnVal;
};

ab = {prop1: "p1", prop2: [1, "str2", {p1: "p1inner", p2: undefined, p3: null, p4date: new Date()}]};
var abstr = JSON.stringify(ab, replacer);
var abcloned = JSON.parse(abstr);
console.log("ab is: ", ab);
console.log("abcloned is: ", abcloned);

/* abcloned is:
 * {
  "prop1": "p1",
  "prop2": [
    1,
    "str2",
    {
      "p1": "p1inner",
      "p2": null,
      "p3": null,
      "p4date": "Tue Jun 11 2019 18:47:50 GMT+0530 (India Standard Time)"
    }
  ]
}
Note p4date is string not Date object but format and timezone are completely preserved.
*/
1

I ran into the same problem. The way I resolvet it was:

  var currentTime = new Date();

  Console.log(currentTime); //Return: Wed Sep 15 13:52:09 GMT-05:00 2021
  Console.log(JSON.stringify(currentTime));  //Return: "2021-09-15T18:52:09.891Z"

var currentTimeFixed = new Date(currentTime.setHours(currentTime.getHours() - (currentTime.getUTCHours() - currentTime.getHours())));

  Console.log(JSON.stringify(currentTimeFixed)); //Return:  "2021-09-15T13:52:09.891Z"
Elha
  • 53
  • 3
1

I wrote the following code blog where it makes service calls.. it will try to serializable the json in every post submission, it will format to local date it again.

protected async post(endPoint: string, data, panelName?: string, hasSuccessMessage: boolean = false): Promise<Observable<any>> {
            const options = this.InitHeader(true);
            const url: string = this._baseUrl + endPoint;
    
            Date.prototype.toJSON = function () {
                return moment(this).format("YYYY-MM-DDThh:mm:00.000Z");;
            };
    
            return await this._http.post(url, data, options).pipe(map(response => {
                return this.Map<any>(response, null);
            }));
        }
Onur Dikmen
  • 339
  • 1
  • 6
  • 17
  • I installed the moment package, which is a nice solution on the angular side. this package detail info https://momentjs.com/guides/ – Onur Dikmen Apr 03 '22 at 10:51
0

All boils down to if your server backend is timezone-agnostic or not. If it is not, then you need to assume that timezone of server is the same as client, or transfer information about client's timezone and include that also into calculations.

a PostgreSQL backend based example:

select '2009-09-28T08:00:00Z'::timestamp -> '2009-09-28 08:00:00' (wrong for 10am)
select '2009-09-28T08:00:00Z'::timestamptz -> '2009-09-28 10:00:00+02'
select '2009-09-28T08:00:00Z'::timestamptz::timestamp -> '2009-09-28 10:00:00'

The last one is probably what you want to use in database, if you are not willing properly implement timezone logic.

korc
  • 249
  • 2
  • 2
0

Instead of toJSON, you can use format function which always gives the correct date and time + GMT

This is the most robust display option. It takes a string of tokens and replaces them with their corresponding values.

Asqan
  • 4,319
  • 11
  • 61
  • 100
0

I tried this in angular 8 :

  1. create Model :

    export class Model { YourDate: string | Date; }
    
  2. in your component

    model : Model;
    model.YourDate = new Date();
    
  3. send Date to your API for saving

  4. When loading your data from API you will make this :

    model.YourDate = new Date(model.YourDate+"Z");

you will get your date correctly with your time zone.

sDev
  • 1,463
  • 9
  • 17
0

In this case I think you need transform the date to UNIX timestamp

timestamp = testDate.getTime();
strJson = JSON.stringify(timestamp);

After that you can re use it to create a date object and format it. Example with javascript and toLocaleDateString ( https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Date/toLocaleDateString )

newDateObject = new Date(JSON.parse(strJson));
newDateObject = newDateObject.toLocalDateStrin([
  "fr-FR",
]);

If you use stringify to use AJAX, now it's not useful. You just need to send timestamp and get it in your script:

$newDateObject = new \DateTime();
$newDateObject->setTimestamp(round($timestamp/1000));

Be aware that getTime() will return a time in milliseconds and the PHP function setTimestamp take time in seconds. It's why you need to divide by 1000 and round.

Ichinator
  • 357
  • 2
  • 11
0

In Angular place the following in index.js script section:

setTimeout(function (){
    Date.prototype.toJSON = function(){
    return new Date(this).toLocaleDateString("en-US") + " "+new Date(this).toLocaleTimeString();
}},1000);
Zach Jensz
  • 3,650
  • 5
  • 15
  • 30