JSON Stringify muda a hora da data devido ao UTC

98

Meus objetos de data em JavaScript são sempre representados por UTC + 2 por causa de onde estou localizado. Portanto, assim

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

O problema é JSON.stringifyconverter a data acima para

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

O que eu preciso é que a data e a hora sejam respeitadas, mas não são, portanto, deveria ser

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

Basicamente, eu uso isso:

var jsonData = JSON.stringify(jsonObject);

Tentei passar um parâmetro de substituição (segundo parâmetro em stringify), mas o problema é que o valor já foi processado.

Eu também tentei usar toString()e toUTCString()no objeto de data, mas eles também não me deram o que eu quero ..

Alguém pode me ajudar?

Mark Smith
fonte
17
2009-09-28T10:00:00Z não representa o mesmo momento no tempo que Mon Sep 28 10:00:00 UTC+0200 2009. Em Zuma data ISO 8601 significa UTC, e 10 horas em UTC é um momento diferente de 10 horas em +0200. Uma coisa seria querer que a data fosse serializada com o fuso horário correto, mas você está nos pedindo para ajudá-lo a serializá-la para uma representação que seja inequívoca e objetivamente errada .
Mark Amery
1
Para adicionar ao comentário de marcas, na maioria dos casos, é uma prática recomendada armazenar seus datetimes como hora UTC, para que você possa oferecer suporte a usuários em diferentes fusos horários
JoeCodeFrog

Respostas:

67

Recentemente, tive o mesmo problema. E foi resolvido com o seguinte código:

x = new Date();
let hoursDiff = x.getHours() - x.getTimezoneOffset() / 60;
let minutesDiff = (x.getHours() - x.getTimezoneOffset()) % 60;
x.setHours(hoursDiff);
x.setMinutes(minutesDiff);
Anatoliy
fonte
sim, mas isso se o site for usado no meu país, se for usado em outro país como os EUA - não seria 2 ...
mark smith
Obviamente, esse valor deve ser calculado.
Anatoliy
3
obrigado ... Na verdade eu encontrei uma ótima biblioteca aqui, blog.stevenlevithan.com/archives/date-time-format tudo que você precisa para fazer isso (talvez te ajude), você passa falso e não converte. var algo = dateFormat (myStartDate, "isoDateTime", false);
Mark Smith
11
isso está incorreto, pois torna o seu código não seguro - você deve corrigir o fuso horário ao ler a data de volta.
olliej
4
Esta resposta está errada. O OP não percebe que "2009-09-28T08: 00: 00Z" e "Seg 28 de setembro 10:00:00 UTC + 0200 2009" são exatamente o mesmo momento no tempo e que o ajuste para a diferença de fuso horário está realmente criando na hora errada.
RobG
41

JSON usa a Date.prototype.toISOStringfunção que não representa a hora local - ela representa a hora em UTC não modificado - se você olhar sua saída de data, verá que está em UTC + 2 horas, razão pela qual a string JSON muda em duas horas, mas se isso permitir que o mesmo tempo seja representado corretamente em vários fusos horários.

olliej
fonte
2
Nunca pensei nisso, mas você está certo. Esta é a solução: posso especificar qualquer formato de minha preferência usando a prototipagem.
corrida de
16

Apenas para registro, lembre-se de que o último "Z" em "2009-09-28T08: 00: 00Z" significa que a hora está realmente em UTC.

Consulte http://en.wikipedia.org/wiki/ISO_8601 para obter detalhes.

Locoluis
fonte
9

Aqui está outra resposta (e pessoalmente acho que é mais apropriada)

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 :)
agosto
fonte
@Rohaan obrigado por apontar isso, mas as tags na pergunta mencionam JavaScript.
agosto de
6

date.toJSON () imprime a data UTC em uma string formatada (então adiciona o deslocamento com ela ao convertê-la para o formato JSON).

date = new Date();
new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toJSON();
ali-myousefi
fonte
6
Normalmente, é uma boa ideia adicionar uma explicação sobre o que seu código faz. Isso permite que os desenvolvedores mais novos entendam como o código funciona.
Caleb Kleveter
Você pode explicar por que o código na resposta deve funcionar?
Cristik
Isso funciona para mim também, mas você pode explicar como você fez isso?
Deven Patil
Vou tentar explicar a segunda linha deste código .. date.getTime()retorna o tempo em milissegundos, então devemos converter o segundo operando para milissegundos também. Como os date.getTimezoneOffset()retornos são deslocados em minutos, nós o multiplicamos por 60.000, porque 1 minuto = 60.000 milissegundos. Então, subtraindo o deslocamento da hora atual, obtemos a hora em UTC.
Maksim Pavlov
4

você pode usar o moment.js para formatar com a hora local:

Date.prototype.toISOString = function () {
    return moment(this).format("YYYY-MM-DDTHH:mm:ss");
};
karim fereidooni
fonte
não sobrescrever Data de classe comum. Ele pode facilmente quebrar seu aplicativo se usar alguns módulos externos.
Viacheslav Dobromyslov
3

Estou um pouco atrasado, mas você sempre pode sobrescrever a função toJson no caso de um Date usando Prototype como:

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

No meu caso, Util.getDateTimeString (this) retorna uma string como esta: "2017-01-19T00: 00: 00Z"

charles-emile begin lavigne
fonte
2
Observe que a substituição de globais do navegador pode quebrar bibliotecas de terceiros que você incorpora e é um grande anti-padrão. Nunca faça isso na produção.
jakub.g
2

Normalmente, você deseja que as datas sejam apresentadas a cada usuário em seu próprio horário local-

é por isso que usamos GMT (UTC).

Use Date.parse (jsondatestring) para obter a string de hora local,

a menos que você queira que seu horário local seja mostrado a cada visitante.

Nesse caso, use o método de Anatoly.

kennebec
fonte
2

Contorne esse problema usando a moment.jsbiblioteca (a versão sem fuso horário).

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

Eu estava usando a flatpickr.min.jsbiblioteca. A hora do objeto JSON resultante criado corresponde à hora local fornecida, mas ao selecionador de data.

Paulo
fonte
2

Solução JSON.stringifypronta para usar para forçar a ignorar fusos horários:

  • Javascript puro (com base na resposta de Anatoliy):

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

Usando a biblioteca moment.js:

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>

Benjamin Caure
fonte
2

Eu me deparei com isso trabalhando com coisas legadas onde eles só funcionam na costa leste dos Estados Unidos e não armazenam datas no UTC, é tudo EST. Tenho que filtrar as datas com base na entrada do usuário no navegador, portanto, devo passar a data na hora local no formato JSON.

Só para elaborar essa solução já postada - é o que eu uso:

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

Portanto, meu entendimento é que isso irá em frente e subtrairá o tempo (em milissegundos (portanto, 60.000) da data de início com base no deslocamento de fuso horário (retorna minutos) - em antecipação à adição de tempo para que JSON () será adicionado.

MrRobboto
fonte
1

Aqui está algo realmente legal e simples (pelo menos eu acredito que sim :)) e não requer manipulação de data para ser clonado ou sobrecarregando qualquer uma das funções nativas do navegador, como toJSON (referência: Como criar uma string JSON em uma data javascript e preservar o fuso horário , Courty Shawson)

Passe uma função substituta para JSON.stringify que restringe as coisas de acordo com sua vontade !!! Desta forma, você não precisa fazer diferenças de horas e minutos ou qualquer outra manipulação.

Coloquei console.logs para ver os resultados intermediários, para que fique claro o que está acontecendo e como a recursão está funcionando. Isso revela algo digno de nota: o valor param para substituir já está convertido para o formato de data ISO :). Use esta [tecla] para trabalhar com dados originais.

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.
*/
Atul Lohiya
fonte
1

JavaScript normalmente converte o fuso horário local em UTC.

date = new Date();
date.setMinutes(date.getMinutes()-date.getTimezoneOffset())
JSON.stringify(date)
Sarath Ak
fonte
0

Tudo se resume a se o back-end do servidor é independente do fuso horário ou não. Se não for, você precisa assumir que o fuso horário do servidor é o mesmo que o do cliente ou transferir informações sobre o fuso horário do cliente e incluí-lo nos cálculos.

um exemplo de back-end 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'

O último é provavelmente o que você deseja usar no banco de dados, se não estiver disposto a implementar a lógica de fuso horário adequadamente.

Corc
fonte
0

Em vez de toJSON, você pode usar a função de formato que sempre dá a data e hora corretas +GMT

Esta é a opção de exibição mais robusta. Ele pega uma string de tokens e os substitui por seus valores correspondentes.

Asqan
fonte
0

Eu tentei isso no angular 8:

  1. criar modelo:

    export class Model { YourDate: string | Date; }
  2. em seu componente

    model : Model;
    model.YourDate = new Date();
  3. enviar Data para sua API para salvar

  4. Ao carregar seus dados da API, você fará o seguinte:

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

você obterá sua data corretamente com seu fuso horário.

Mahmoud AbdElzaher
fonte