Как 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 при сохранении часового пояса браузера - использовать time.js и использовать moment.format(), но, конечно, это не сработает, если я сериализую целую команду с помощью чего-то, что использует JSON.stringify внутренне (в данном случае, AngularJS)

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

Для полноты моего домена требуется как местное время, так и смещение.

Ответ 1

Предполагая, что у вас есть какой-то объект, содержащий Date:

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

Вы можете переопределить функцию toJSON прототипа Date. Здесь я использую moment.js для создания объекта moment с даты, затем используйте функцию момента format без параметров, которая испускает расширенный формат ISO8601, включая смещение.

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

Теперь, когда вы сериализуете объект, он будет использовать формат даты, который вы просили:

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

Конечно, это затронет все объекты Date. Если вы хотите изменить поведение только определенного объекта даты, вы можете переопределить только эту функцию объекта toJSON, например:

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

Ответ 2

Основываясь на ответе Мэтта Джонсона, я повторно реализовал 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 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 будет корректировать другие части объекта даты, когда предоставленное значение будет "переполнено". От MDN:

Если указанный параметр находится за пределами ожидаемого диапазона, setHours() пытается соответствующим образом обновить информацию о дате в объекте Date. Например, если вы используете 100 для secondsValue, минуты будут увеличены на 1 (minutesValue + 1), а 40 будут использованы для секунд.

Ответ 3

Однако, когда я его строкую, часовой пояс переходит в прошлое

Thats, потому что Tue Jun 07 2011 04:00:00 GMT+1000 (E. Australia Standard Time) на самом деле является результатом метода toString объекта Date, тогда как stringify, кажется, вызывает метод toISOString.

Итак, если формат toString - это то, что вы хотите, просто укажите, что:

JSON.stringify(date.toString());

Или, так как вы хотите позже выполнить свою "команду", поместите это значение в первую очередь:

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

Ответ 4

let date = new Date (JSON.parse(JSON.stringify (новая дата (2011, 05, 07, 04, 0, 0))));