如何 JSON 字符串化一个 javascript 日期和保存时区

我有一个日期对象,由用户创建,时区由浏览器填充,如下所示:

var date = new Date(2011, 05, 07, 04, 0, 0);
> Tue Jun 07 2011 04:00:00 GMT+1000 (E. Australia Standard Time)

然而,当我将其字符串化时,时区就跟它说拜拜了

JSON.stringify(date);
> "2011-06-06T18:00:00.000Z"

在保留浏览器时区的同时获得 ISO8601字符串的最好方法是使用 Moment.js 和 moment.format(),但是当然,如果我通过内部使用 JSON.stringify(在本例中是 AngularJS)来序列化一个完整的命令时,这种方法就不起作用了

var command = { time: date, contents: 'foo' };
$http.post('/Notes/Add', command);

为了完整起见,我的域 是的需要本地时间和偏移量。

87235 次浏览

When I stringify it, though, the timezone goes bye-bye

That’s because Tue Jun 07 2011 04:00:00 GMT+1000 (E. Australia Standard Time) is actually the result of the toString method of the Date object, whereas stringify seems to call the toISOString method instead.

So if the toString format is what you want, then simply stringify that:

JSON.stringify(date.toString());

Or, since you want to stringify your “command” later on, put that value in there in the first place:

var command = { time: date.toString(), contents: 'foo' };

Assuming you have some kind of object that contains a Date:

var o = { d : new Date() };

You can override the toJSON function of the Date prototype. Here I use moment.js to create a moment object from the date, then use moment's format function without parameters, which emits the ISO8601 extended format including the offset.

Date.prototype.toJSON = function(){ return moment(this).format(); }

Now when you serialize the object, it will use the date format you asked for:

var json = JSON.stringify(o);  //  '{"d":"2015-06-28T13:51:13-07:00"}'

Of course, that will affect all Date objects. If you want to change the behavior of only the specific date object, you can override just that particular object's toJSON function, like this:

o.d.toJSON = function(){ return moment(this).format(); }

Based on Matt Johnsons 's answer, I re-implemented toJSON without having to depend on moment (which I think is a splendid library, but a dependency in such a low level method like toJSON bothers me).

Date.prototype.toJSON = function () {
var timezoneOffsetInHours = -(this.getTimezoneOffset() / 60); //UTC minus local time
var sign = timezoneOffsetInHours >= 0 ? '+' : '-';
var leadingZero = (Math.abs(timezoneOffsetInHours) < 10) ? '0' : '';


//It's a bit unfortunate that we need to construct a new Date instance
//(we don't want _this_ Date instance to be modified)
var correctedDate = new Date(this.getFullYear(), this.getMonth(),
this.getDate(), this.getHours(), this.getMinutes(), this.getSeconds(),
this.getMilliseconds());
correctedDate.setHours(this.getHours() + timezoneOffsetInHours);
var iso = correctedDate.toISOString().replace('Z', '');


return iso + sign + leadingZero + Math.abs(timezoneOffsetInHours).toString() + ':00';
}

The setHours method will adjust other parts of the date object when the provided value would "overflow". From MDN:

If a parameter you specify is outside of the expected range, setHours() attempts to update the date information in the Date object accordingly. For example, if you use 100 for secondsValue, the minutes will be incremented by 1 (minutesValue + 1), and 40 will be used for seconds.

let date = new Date(JSON.parse(JSON.stringify(new Date(2011, 05, 07, 04, 0, 0))));

I'd always be inclined to not mess with functions in the prototype of system objects like the date, you never know when that's going to bite you in some unexpected way later on in your code.

Instead, the JSON.stringify method accepts a "replacer" function (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_replacer_parameter) which you can supply, allowing you to override the innards of how JSON.stringify performs its "stringification"; so you could do something like this;

var replacer = function(key, value) {


if (this[key] instanceof Date) {
return this[key].toUTCString();
}
   

return value;
}


console.log(JSON.stringify(new Date(), replacer));
console.log(JSON.stringify({ myProperty: new Date()}, replacer));
console.log(JSON.stringify({ myProperty: new Date(), notADate: "I'm really not", trueOrFalse: true}, replacer));

I've created a small library that preserves the timezone with ISO8601 string after JSON.stringify. The library lets you easily alter the behavior of the native Date.prototype.toJSON method.

npm: https://www.npmjs.com/package/lbdate

Example:

lbDate().init();


const myObj = {
date: new Date(),
};


const myStringObj = JSON.stringify(myObj);


console.log(myStringObj);


// {"date":"2020-04-01T03:00:00.000+03:00"}

The library also gives you options to customize the serialization result if necessary.

If you have a JS Date Object and want to stringify it to preserve the timezone, then you should definitely use toLocaleDateString(). It is a very powerful helper function that can help you format your Date object in every way possible.

For example, if you want to print "Friday, February 1, 2019, Pacific Standard Time",

 const formatDate = (dateObject : Date) => {
const options: any  = {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
timeZoneName: 'long'
};
      

return dateObject.toLocaleDateString('en-CA', options);
};

Thus, by modifying the options object, you can achieve different styles of formatting for your Date Object.

For more information regarding the ways of formatting, refer to this Medium article: https://medium.com/swlh/use-tolocaledatestring-to-format-javascript-dates-2959108ea020