Как в формате 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);

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

8 ответов

Решение

Предполагая, что у вас есть какой-то объект, который содержит 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(); }

Я всегда был бы склонен не связываться с функциями в прототипе системных объектов, такими как дата, вы никогда не знаете, когда это будет неожиданно для вас кусаться в вашем коде.

Вместо этого метод JSON.stringify принимает функцию "замена" ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify), которую вы можете предоставить, что позволяет переопределить внутреннюю часть того, как 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));

Основываясь на ответе Мэтта Джонсона, я повторно реализовал toJSON без необходимости зависеть от moment (который я считаю великолепной библиотекой, но зависимость в таком низкоуровневом методе, как toJSON беспокоит меня).

Date.prototype.toJSON = function () {
  var timezoneOffsetInHours = -(this.getTimezoneOffset() / 60); //UTC minus local time
  var sign = timezoneOffsetInHours >= 0 ? '+' : '-';
  var leadingZero = (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 Метод будет корректировать другие части объекта даты, когда предоставленное значение будет "переполнено". От MDN:

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

Однако когда я его преобразую, часовой пояс прощается

Это потому что 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' };

Я создал небольшую библиотеку, которая сохраняет часовой пояс со строкой ISO8601 после JSON.stringify. Библиотека позволяет легко изменять поведение родного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"}

Библиотека также дает вам возможность настроить результат сериализации, если это необходимо.

Если у вас есть объект даты JS и вы хотите преобразовать его в строку, чтобы сохранить часовой пояс, вам обязательно следует использоватьtoLocaleDateString(). Это очень мощная вспомогательная функция, которая может помочь вам отформатировать объект Date всеми возможными способами.

Например, если вы хотите напечатать « Пятница, 1 февраля 2019 г., стандартное тихоокеанское время »,

       const formatDate = (dateObject : Date) => {
    const options: any  = {
        weekday: 'long',
        year: 'numeric',
        month: 'long',
        day: 'numeric',
        timeZoneName: 'long'
      };
      
    return dateObject.toLocaleDateString('en-CA', options);
  };

Таким образом, изменяяoptionsобъект, вы можете добиться различных стилей форматирования для вашего объекта даты.

Для получения дополнительной информации о способах форматирования обратитесь к этой статье на Medium: https://medium.com/swlh/use-tolocaledatestring-to-format-javascript-dates-2959108ea020 .

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

Ответ bvgheluwe, предоставляющий измененный Date.prototype.toJSON(), является хорошим началом, но не работает для нецелочисленных часовых поясов (Аделаида UTC+9,5, Маркизские острова UTC -9,5 и т. д.)

Основываясь на его ответе, я предоставляю обновление, которое работает во всех часовых поясах.

      Date.prototype.toJSON = function () {
    const offsetHoursDecimalMins = -this.getTimezoneOffset() / 60; //Will give 9.5 for Adelaide Australia (UTC +9.5) and -9.5 for Marquesas Islands ( UTC -9.5)
    const sign= offsetHoursDecimalMins>0?'+':'-';
    const nAbsOffsetHours = Math.abs(Math.trunc(offsetHoursDecimalMins)); //Absolute value of offset hours with decimal truncated
                      
    const offsetMins = Math.abs(this.getTimezoneOffset() % 60); //Will give 30 for Adelaide Australia and 30 for Marquesas Islands
    const strUTCOffset = `${sign}${nAbsOffsetHours.toString().padStart(2, '0')}:${offsetMins.toString().padStart(2, '0')}`; //"-09:30" for Marquesas Islands
                    
    /*The method Date.toISOString() returns UTC time in the format "2025-08-19T23:15:32.000Z", 
    but we want Local Time in this same format. To achieve this we create a new 
    date object (we don't want to change this one), and then add the offset hours
    and mins and call toISOString() on this new object*/
                    
    const correctedDate = new Date(this.getFullYear(), this.getMonth(),
        this.getDate(), this.getHours(), this.getMinutes(), this.getSeconds(),
        this.getMilliseconds());
                    
    correctedDate.setHours(this.getHours() + offsetHoursDecimalMins); //Will add Hours and Mins (e.g 9.5 for Adelaide, -9.5 for Marquesas Islands )
                
    const x = correctedDate.toISOString().replace('Z', '');
    return `${x}${strUTCOffset}`;

    };
Другие вопросы по тегам