如何 JSON 字符串化 javascript 日期并保留时区

How to JSON stringify a javascript Date and preserve timezone

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

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);

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

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

那是因为Tue Jun 07 2011 04:00:00 GMT+1000 (E. Australia Standard Time)实际上是Date对象的toString方法的结果,而stringify似乎调用了toISOString方法.

因此,如果 toString 格式是您想要的,那么只需将 that:

字符串化
JSON.stringify(date.toString());

或者,因为你想稍后将你的“命令”字符串化,所以首先把 那个 值放在那里:

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(); }

基于 Matt Johnsons 的 ,我重新实现了 toJSON 而不必依赖 moment(我认为这是一个出色的库,但依赖于此类像 toJSON 这样的低级方法困扰着我)。

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';
}

setHours 方法将在提供的值 "overflow" 时调整日期对象的其他部分。来自 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))));

我总是倾向于不混淆系统对象原型中的函数,比如日期,你永远不知道什么时候它会在你的代码中以某种意想不到的方式出现。

相反,JSON.stringify 方法接受您可以提供的“替换”函数 (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_replacer_parameter),允许您覆盖 JSON.stringify 如何执行其“字符串化”的内部结构;所以你可以做这样的事情;

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));

我创建了一个小型库,它在 JSON.stringify 之后使用 ISO8601 字符串保留时区。该库可让您轻松更改本机 Date.prototype.toJSON 方法的行为。

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

示例:

lbDate().init();

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

const myStringObj = JSON.stringify(myObj);

console.log(myStringObj);

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

如果需要,该库还为您提供自定义序列化结果的选项。