JSON Stringify cambia la hora de la fecha debido a UTC

98

Mis objetos de fecha en JavaScript siempre están representados por UTC +2 debido a mi ubicación. Por lo tanto, así

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

El problema es JSON.stringifyconvertir la fecha anterior a

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

Lo que necesito es que se respete la fecha y la hora, pero no lo es, por lo que debería ser

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

Básicamente uso esto:

var jsonData = JSON.stringify(jsonObject);

Intenté pasar un parámetro de reemplazo (segundo parámetro en stringify) pero el problema es que el valor ya ha sido procesado.

También intenté usar toString()y toUTCString()en el objeto de fecha, pero tampoco me dan lo que quiero.

¿Alguien puede ayudarme?

Mark Smith
fuente
17
2009-09-28T10:00:00Z no representa el mismo momento en el tiempo que Mon Sep 28 10:00:00 UTC+0200 2009. En Zuna fecha ISO 8601 significa UTC, y las 10 en punto en UTC es un momento diferente en el tiempo a las 10 en punto en +0200. Una cosa sería querer que la fecha se serializara con la zona horaria correcta, pero nos está pidiendo que lo ayudemos a serializarla en una representación que sea inequívoca y objetivamente incorrecta .
Mark Amery
1
Para agregar al comentario de Marcas, en la mayoría de los casos es una buena práctica almacenar sus fechas y horas como hora UTC, para que pueda brindar soporte a los usuarios en diferentes zonas horarias
JoeCodeFrog

Respuestas:

67

Recientemente me encontré con el mismo problema. Y se resolvió usando el siguiente 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
fuente
sí, pero esto es si el sitio web se usa dentro de mi país, si se usa en otro país como EE. UU., no sería 2 ...
Mark Smith
Obviamente, este valor debe calcularse.
Anatoliy
3
gracias ... De hecho, encontré una gran biblioteca aquí, blog.stevenlevithan.com/archives/date-time-format todo lo que necesitas para hacer esto (tal vez te ayude), pasas falso y no se convierte. var algo = dateFormat (myStartDate, "isoDateTime", falso);
Mark Smith
11
esto es incorrecto, ya que hace que su código no sea seguro para la zona horaria; debe corregir la zona horaria cuando vuelva a leer la fecha.
olliej
4
Esta respuesta es incorrecta. El OP no se da cuenta de que "2009-09-28T08: 00: 00Z" y "Lun 28 de septiembre 10:00:00 UTC + 0200 2009" son exactamente el mismo momento en el tiempo y que el ajuste para el desplazamiento de la zona horaria está creando mal momento.
RobG
41

JSON usa la Date.prototype.toISOStringfunción que no representa la hora local, representa la hora en UTC sin modificar, si observa la salida de la fecha, puede ver que está en UTC + 2 horas, por lo que la cadena JSON cambia en dos horas, pero si esto permite que el mismo tiempo se represente correctamente en múltiples zonas horarias.

olliej
fuente
2
Nunca pensé en esto, pero tienes razón. Esta es la solución: puedo especificar cualquier formato que prefiera usando el prototipo.
racs
9

Aquí hay otra respuesta (y personalmente creo que es más apropiada)

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 :)
ago
fuente
@Rohaan, gracias por señalarlo, pero las etiquetas de la pregunta mencionan JavaScript.
aug
6

date.toJSON () imprime la fecha UTC en una cadena formateada (por lo tanto, agrega el desplazamiento cuando la convierte a formato JSON).

date = new Date();
new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toJSON();
Ali-myousefi
fuente
6
Por lo general, es una buena idea agregar una explicación sobre lo que hace su código. Esto permite a los desarrolladores más nuevos comprender cómo funciona el código.
Caleb Kleveter
¿Puede explicar por qué debería funcionar el código de la respuesta?
Cristik
Esto también funciona para mí, pero ¿puedes explicar cómo lo hiciste?
Deven Patil
Intentaré explicar la segunda línea de este código ... date.getTime()devuelve el tiempo en milisegundos, por lo que también deberíamos convertir el segundo operando en milisegundos. Dado que date.getTimezoneOffset()devuelve el desplazamiento en minutos, lo multiplicamos 60000, porque 1 minuto = 60000 milisegundos. Entonces, al restar el desplazamiento de la hora actual, obtenemos la hora en UTC.
Maksim Pavlov
4

puede usar moment.js para formatear con la hora local:

Date.prototype.toISOString = function () {
    return moment(this).format("YYYY-MM-DDTHH:mm:ss");
};
karim fereidooni
fuente
no sobrescriba la fecha de clase común. Puede romper fácilmente su aplicación si usa algunos módulos externos.
Viacheslav Dobromyslov
3

Llego un poco tarde, pero siempre puedes sobrescribir la función toJson en caso de una fecha usando Prototype así:

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

En mi caso, Util.getDateTimeString (this) devuelve una cadena como esta: "2017-01-19T00: 00: 00Z"

charles-emile begin lavigne
fuente
2
Tenga en cuenta que sobrescribir los valores globales del navegador puede romper las bibliotecas de terceros que incrusta, y es un gran anti-patrón. Nunca haga esto en producción.
jakub.g
2

Por lo general, desea que las fechas se presenten a cada usuario en su propia hora local.

por eso usamos GMT (UTC).

Use Date.parse (jsondatestring) para obtener la cadena de hora local,

a menos que desee que se muestre su hora local a cada visitante.

En ese caso, use el método de Anatoly.

Kennebec
fuente
2

Solucioné este problema utilizando la moment.jsbiblioteca (la versión sin zona horaria).

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

Estaba usando la flatpickr.min.jsbiblioteca. La hora del objeto JSON resultante creado coincide con la hora local proporcionada, pero con el selector de fecha.

Pablo
fuente
2

Solución lista para usar para forzar JSON.stringifyignorar zonas horarias:

  • Javascript puro (basado en la respuesta 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 la 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
fuente
2

Me encuentro con esto trabajando un poco con material heredado donde solo funcionan en la costa este de EE. UU. Y no almacenan fechas en UTC, todo es EST. Tengo que filtrar las fechas según la entrada del usuario en el navegador, por lo que debo pasar la fecha en la hora local en formato JSON.

Solo para desarrollar esta solución ya publicada, esto es lo que 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();

Entonces, según tengo entendido, esto continuará y restará el tiempo (en milisegundos (por lo tanto, 60000) de la fecha de inicio en función del desplazamiento de la zona horaria (devuelve minutos), en anticipación a la adición de tiempo que se agregará a JSON ().

MrRobboto
fuente
1

Aquí hay algo realmente limpio y simple (al menos eso creo :)) y no requiere manipulación de la fecha para ser clonado o sobrecargar ninguna de las funciones nativas del navegador como toJSON (referencia: Cómo JSON encadena una fecha de javascript y preserva la zona horaria , cortejo Shawson)

Pase una función de reemplazo a JSON.stringify que encadena las cosas al contenido de su corazón. De esta manera no tienes que hacer diferencias de hora y minuto ni ninguna otra manipulación.

He puesto console.logs para ver los resultados intermedios para que quede claro qué está pasando y cómo funciona la recursividad. Eso revela algo digno de mención: value param to replacecer ya está convertido al formato de fecha ISO :). Utilice esta [tecla] para trabajar con datos originales.

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
fuente
1

JavaScript normalmente convierte la zona horaria local a UTC.

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

Todo se reduce a si el backend de su servidor es independiente de la zona horaria o no. Si no es así, debe asumir que la zona horaria del servidor es la misma que la del cliente, o transferir información sobre la zona horaria del cliente e incluirla también en los cálculos.

un ejemplo basado en backend de 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'

El último es probablemente el que desea usar en la base de datos, si no está dispuesto a implementar correctamente la lógica de la zona horaria.

korc
fuente
0

En lugar de toJSON, puede usar la función de formato que siempre da la fecha y hora correctas +GMT

Ésta es la opción de visualización más robusta. Toma una cadena de tokens y los reemplaza con sus valores correspondientes.

Asqan
fuente
0

Intenté esto en angular 8:

  1. crear modelo:

    export class Model { YourDate: string | Date; }
  2. en tu componente

    model : Model;
    model.YourDate = new Date();
  3. enviar fecha a su API para guardar

  4. Al cargar sus datos desde la API, hará esto:

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

obtendrá su fecha correctamente con su zona horaria.

Mahmoud AbdElzaher
fuente