JSON Stringify изменяет время даты из-за UTC


99

Мои объекты даты в JavaScript всегда представлены в формате UTC +2 из-за того, где я нахожусь. Следовательно, вот так

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

Проблема заключается в том, чтобы JSON.stringifyпреобразовать указанную выше дату в

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

Мне нужно, чтобы дата и время были соблюдены, но это не так, поэтому это должно быть

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

В основном я использую это:

var jsonData = JSON.stringify(jsonObject);

Я попытался передать параметр replacer (второй параметр в stringify), но проблема в том, что значение уже было обработано.

Я также пробовал использовать toString()и toUTCString()в объекте даты, но они тоже не дают мне того, что я хочу ..

Может кто-нибудь помочь мне?


17
2009-09-28T10:00:00Z не представляет тот же момент времени, что и Mon Sep 28 10:00:00 UTC+0200 2009. Дата Zв ISO 8601 означает всемирное координированное время, а 10 часов по всемирному координированному времени - это другой момент времени по сравнению с 10 часами в +0200. Одно дело - сериализовать дату с правильным часовым поясом, но вы просите нас помочь вам сериализовать ее в однозначно, объективно неверное представление .
Марк Эмери

1
Чтобы добавить комментарий к Marks, в большинстве случаев лучше всего хранить ваши даты в формате UTC, чтобы вы могли поддерживать пользователей в разных часовых поясах
JoeCodeFrog

Ответы:


69

Недавно я столкнулся с той же проблемой. И это было решено с помощью следующего кода:

x = new Date();
let hoursDiff = x.getHours() - x.getTimezoneOffset() / 60;
let minutesDiff = (x.getHours() - x.getTimezoneOffset()) % 60;
x.setHours(hoursDiff);
x.setMinutes(minutesDiff);

да, но это если веб-сайт используется в моей стране, если он используется в другой стране, например в США - это не будет 2 ...
Марк Смит

Очевидно, это значение следует рассчитать.
Анатолий

3
спасибо ... Я действительно нашел здесь отличную библиотеку, blog.stevenlevithan.com/archives/date-time-format - все, что вам нужно для этого (возможно, это поможет вам), вы передаете false, и он не конвертируется. var something = dateFormat (myStartDate, "isoDateTime", false);
Марк Смит

11
это неверно, поскольку это делает ваш код безопасным без часового пояса - вы должны корректировать часовой пояс, когда считываете дату обратно.
olliej

4
Это неверный ответ. OP не понимает, что «2009-09-28T08: 00: 00Z» и «Mon Sep 28 10:00:00 UTC + 0200 2009» - это точно один и тот же момент времени, и что корректировка смещения часового пояса фактически создает неподходящее время.
RobG

41

JSON использует Date.prototype.toISOStringфункцию, которая не представляет местное время - она ​​представляет время в неизмененном UTC - если вы посмотрите на вывод даты, вы увидите, что вы находитесь в UTC + 2 часа, поэтому строка JSON изменяется на два часа, но если это позволяет правильно представить одно и то же время в нескольких часовых поясах.


2
Никогда не задумывался об этом, но ты прав. Вот решение: я могу указать любой формат, который предпочитаю, используя прототипирование.
racs


9

Вот еще один ответ (и лично я считаю, что он более уместен)

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

@Rohaan спасибо, что указали на это, но теги в вопросе упоминают JavaScript.
Август

6

date.toJSON () печатает дату в формате UTC в формате String (поэтому добавляет смещение при преобразовании в формат JSON).

date = new Date();
new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toJSON();

6
Обычно рекомендуется добавить объяснение того, что делает ваш код. Это позволяет новым разработчикам понять, как работает код.
Калеб Клветер

Вы можете объяснить, почему код в ответе должен работать?
Cristik

Это работает и для меня, но можете ли вы объяснить, как вы это сделали?
Девен Патил

Я попытаюсь объяснить вторую строку этого кода ... date.getTime()возвращает время в миллисекундах, поэтому нам также следует преобразовать второй операнд в миллисекунды. Поскольку date.getTimezoneOffset()возвращается смещение в минутах, мы умножаем его на 60000, потому что 1 минута = 60000 миллисекунд. Таким образом, вычитая смещение из текущего времени, мы получаем время в формате UTC.
Максим Павлов

4

вы можете использовать moment.js для форматирования с местным временем:

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

не перезаписывать общий класс Date. Это может легко сломать ваше приложение, если использовать какие-то внешние модули.
Вячеслав Добромыслов,

3

Я немного опоздал, но вы всегда можете перезаписать функцию toJson в случае Date, используя Prototype, например:

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

В моем случае Util.getDateTimeString (this) возвращает такую ​​строку: «2017-01-19T00: 00: 00Z»


2
Обратите внимание, что перезапись глобальных файлов браузера может нарушить работу встроенных сторонних библиотек и является большим анти-шаблоном. Никогда не делайте этого на производстве.
jakub.g 07

3

Готовое решение для принудительного JSON.stringifyигнорирования часовых поясов:

  • Чистый javascript (на основе ответа Анатолия):

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

Использование библиотек момент + момент-часовой пояс:

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>


2

Обычно вы хотите, чтобы даты были представлены каждому пользователю в его собственное местное время -

поэтому мы используем GMT (UTC).

Используйте Date.parse (jsondatestring), чтобы получить строку местного времени,

если вы не хотите, чтобы ваше местное время показывалось каждому посетителю.

В таком случае используйте метод Анатолия.


2

Обойти эту проблему можно с помощью moment.jsбиблиотеки (версия без часового пояса).

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

Я пользовался flatpickr.min.jsбиблиотекой. Время созданного результирующего объекта JSON совпадает с предоставленным местным временем, но с указателем даты.


2

Я столкнулся с этим, немного работая с устаревшими материалами, где они работают только на восточном побережье США и не хранят даты в UTC, это все EST. Мне нужно фильтровать даты на основе ввода данных пользователем в браузере, поэтому я должен передавать дату по местному времени в формате JSON.

Просто чтобы подробнее рассказать об уже опубликованном решении - вот что я использую:

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

Итак, я понимаю, что это будет продолжаться и вычесть время (в миллисекундах (следовательно, 60000) из начальной даты на основе смещения часового пояса (возвращает минуты) - в ожидании добавления времени, которое будет добавлено toJSON ().


1

Вот что-то действительно изящное и простое (по крайней мере, я так считаю :)) и не требует манипуляций с датой для клонирования или перегрузки каких-либо встроенных функций браузера, таких как toJSON (ссылка: Как JSON преобразовать дату javascript в строку и сохранить часовой пояс , любезно Шосон)

Передайте JSON.stringify функцию replacer, которая преобразует все в вашу душу !!! Таким образом, вам не придется выполнять часовые и минутные различия или любые другие манипуляции.

Я добавил console.logs, чтобы увидеть промежуточные результаты, чтобы было понятно, что происходит и как работает рекурсия. Это обнаруживает кое-что достойное внимания: параметр value для replacer уже преобразован в формат даты ISO :). Используйте этот [ключ] для работы с исходными данными.

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

JavaScript обычно конвертирует местный часовой пояс в UTC.

date = new Date();
date.setMinutes(date.getMinutes()-date.getTimezoneOffset())
JSON.stringify(date)

0

Все сводится к тому, является ли серверная часть вашего сервера независимой от часового пояса или нет. Если это не так, то вам нужно предположить, что часовой пояс сервера совпадает с часовым поясом клиента, или передать информацию о часовом поясе клиента и включить это также в вычисления.

пример на базе PostgreSQL:

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'

Последний, вероятно, то, что вы хотите использовать в базе данных, если вы не желаете правильно реализовать логику часовых поясов.


0

Вместо этого toJSONвы можете использовать функцию форматирования, которая всегда дает правильную дату и время +GMT

Это самый надежный вариант отображения. Он берет строку токенов и заменяет их соответствующими значениями.


0

Я пробовал это в angular 8:

  1. создать модель:

    export class Model { YourDate: string | Date; }
  2. в вашем компоненте

    model : Model;
    model.YourDate = new Date();
  3. отправьте дату в ваш API для сохранения

  4. При загрузке данных из API вы сделаете следующее:

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

вы получите дату правильно с вашим часовым поясом.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.