Moment: Sumar y restar fechas no preserva las horas cuando la zona horaria tiene reglas de DST de medianoche

Creado en 22 ago. 2018  ·  8Comentarios  ·  Fuente: moment/moment

Descripción del problema:

Estoy tratando de crear un rango de fechas en (matriz de fechas de momento) donde cada fecha es 1 día después de la anterior. Esto funciona como se esperaba para todos los usuarios, pero hemos recibido un informe de errores para un usuario en la zona horaria de América / Santiago que ve fechas duplicadas en la interfaz de usuario. Lo que noté es que agregar 1 día a una fecha en particular en realidad solo agrega 23 horas.

Con una fecha en 8/11/2018 00:00:00 en la zona horaria de América / Santiago, agregar 1 día aumenta el tiempo a 8/11/2018 23:00:00 lugar de 8/12/2018 00:00:00 .

Los documentos explican que las horas deben conservarse cuando se cambia de horario de verano _mientras se usan días_, pero eso no parece ser cierto aquí.

También hay consideraciones especiales que se deben tener en cuenta al agregar una hora que cruza el horario de verano. Si agrega años, meses, semanas o días, la hora original siempre coincidirá con la hora agregada.

// This code is from moment.js docs https://momentjs.com/docs/#/manipulating/add/
var m = moment(new Date(2011, 2, 12, 5, 0, 0)); // the day before DST in the US
m.hours(); // 5
m.add(1, 'days').hours(); // 5

Pasos para reproducir

En el siguiente código, la zona horaria que se utiliza tiene su regla DST a medianoche. Tengo una fecha que es a la medianoche del día anterior a la entrada en vigor de esta regla. Agregué 1 día a esta fecha y espero que sea el día siguiente a la medianoche / 0 horas (las horas deben conservarse), pero en realidad solo agrega 23 horas.

fmt = d => d.format() + ' ' + d.tz()

x = moment.tz(new Date('08/11/2018 00:00:00'), 'America/Santiago');
fmt(x);                       // "2018-08-11T00:00:00-04:00 America/Santiago"
fmt(x.clone().add(1, 'day')); // "2018-08-11T23:00:00-04:00 America/Santiago" - offset unchanged, added 23 hours not 1 day
fmt(x.clone().add(2, 'day')); // "2018-08-13T00:00:00-03:00 America/Santiago" - original hour preserved now

Aquí, puede ver que agregar 24 horas lo cambió en 25 horas y se cambió la compensación de la zona horaria.

fmt = d => d.format() + ' ' + d.tz()

x = moment.tz(new Date('08/11/2018 00:00:00'), 'America/Santiago');
fmt(x);                          // "2018-08-11T00:00:00-04:00 America/Santiago"
fmt(x.clone().add(24, 'hours')); // "2018-08-12T01:00:00-03:00 America/Santiago" - offset changed, added 25 hours
fmt(x.clone().add(48, 'hours')); // "2018-08-13T01:00:00-03:00 America/Santiago"

Medio ambiente:

  • Versión 68.0.3440.84 (compilación oficial) (64 bits)
  • Mac OSX El Capitan 10.11.6 (15G31)

Otra información que puede ser de ayuda:

  • Zona horaria de la máquina: zona horaria del este de EE. UU., Horario de verano
  • Se ejecutó el código de fecha y hora: 3:00 pm del 22 de agosto de 2018
  • Reproduje este problema en Chrome Dev Tools en la página web Moment.js

Salida de fecha JS

(nueva fecha ()). toString ()

  • Mié 22 de agosto de 2018 15:13:40 GMT-0400 (hora de verano del este)

(nueva fecha ()). toLocaleString ()

  • 22/8/2018, 3:13:40 p.m.

(nueva fecha ()). getTimezoneOffset ()

  • 240

navigator.userAgent

  • Mozilla / 5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit / 537.36 (KHTML, como Gecko) Chrome / 68.0.3440.84 Safari / 537.36

momento.versión

  • 2.22.2
Bug DST Pending Next Release Up-For-Grabs

Comentario más útil

El comportamiento aquí parece que se cambió entre la zona horaria de momento v0.5.4 y la v0.5.26.
En la versión anterior, agregar 1 día a '2018-08-11' le daba las 11 p.m. del mismo día
En la nueva versión, le da '2018-08-12' a la medianoche. Pero este tiempo no existe realmente, y agregar 1 minuto retrocede una hora, pero se suponía que este cambio de horario de verano agregaba 1 hora.

Versión antigua, se comporta como se describe en este número:
https://jsfiddle.net/eqyvuxht/1/

Nueva versión, cambio de comportamiento, pero creo que sigue mal.
https://jsfiddle.net/0h6atn4b/4/

Aquí hay una solución:

function switchZone(m, zone) {
  let arr = [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second()];
  if(zone) {
    return moment.tz(arr, zone);
  }
  return moment(arr);
}

function safeAddDays(m, days) {
   let oldZone = m.tz();
   let utc = switchZone(m, 'UTC');
   utc.add(days, 'days');
   return switchZone(utc, oldZone);
}

Todos 8 comentarios

Intenté reproducir esto en la zona horaria de Europa / Roma y no pude:

Europa / Roma - https://www.timeanddate.com/time/change/italy/rome

28 de octubre de 2018: finaliza el horario de verano
Cuando el horario de verano local está a punto de llegar
Domingo, 28 de octubre de 2018, 3:00:00 am, los relojes se retrasan 1 hora para
Domingo, 28 de octubre de 2018, 2:00:00 am hora estándar local en su lugar.

No hay ningún error en este código.

d => d.format() + ' ' + d.tz()

x = moment.tz(new Date('10/27/2018 00:00:00'), 'Europe/Rome');
fmt(x)                       // "2018-10-27T06:00:00+02:00 Europe/Rome"
fmt(x.clone().add(1, 'day')) // "2018-10-28T06:00:00+01:00 Europe/Rome" - tz offset changed, hour preserved as expected
fmt(x.clone().add(2, 'day')) // "2018-10-29T06:00:00+01:00 Europe/Rome"

Puede ver que la TZ cambia, pero las horas permanecen constantes.

Según los pasos de reproducción, parece que hay una discrepancia en el código cuando la fecha agregada aterriza perfectamente en el límite de DST. En la zona horaria de Santiago, la regla del horario de verano es que a la medianoche los relojes retroceden una hora.

Parece que este caso límite puede ser específicamente para zonas horarias donde el límite de DST es a la medianoche, porque no pude reproducir con una zona horaria donde el límite es a las 3 am, con la fecha x a las 3 am.

Europa / Roma - https://www.timeanddate.com/time/change/italy/rome

28 de octubre de 2018: finaliza el horario de verano
Cuando el horario de verano local está a punto de llegar
Domingo, 28 de octubre de 2018, 3:00:00 am, los relojes se retrasan 1 hora para
Domingo, 28 de octubre de 2018, 2:00:00 am hora estándar local en su lugar.

No hay ningún error en este código.

fmt = d => d.format() + ' ' + d.tz()

x = moment.tz(new Date('October 27, 2018 03:00:00'), 'Europe/Rome');
fmt(x);                       // "2018-10-27T09:00:00+02:00 Europe/Rome"
fmt(x.clone().add(1, 'days')) // "2018-10-28T09:00:00+01:00 Europe/Rome" - tz offset changed, hour preserved as expected
fmt(x.clone().add(2, 'days')) // "2018-10-29T09:00:00+01:00 Europe/Rome"

Pude confirmar que este error solo existe para las zonas horarias cuando el límite de horario de verano es a la medianoche, y está agregando tiempo a una fecha que hace que la fecha llegue exactamente al límite.

America / Punta_Arenas - https://www.timeanddate.com/time/zone/chile/punta-arenas
En 2016, esta zona horaria también tenía un límite a la medianoche.

Este código parece tener un error. Ver comentario después de cada línea

fmt = d => d.format() + ' ' + d.tz()

x = moment.tz(new Date('08/13/2016 00:00:00'), 'America/Punta_Arenas')
fmt(x);                        // "2016-08-13T00:00:00-04:00 America/Punta_Arenas"
fmt(x.clone().add(1, 'days')); // "2016-08-13T23:00:00-04:00 America/Punta_Arenas" - 23 hours added, not 1 day, no tz offset change
fmt(x.clone().add(2, 'days')); // "2016-08-15T00:00:00-03:00 America/Punta_Arenas"

Encontré lo que parece ser un problema similar al restar los mismos tipos de zonas horarias (horario de verano a medianoche).

Al restar las fechas exactamente en un turno de DST, la hora no se conserva solo en ese día, sino que se cambia la fecha.

fmt = d => d.format() + ' ' + d.tz()

x = moment.tz(new Date('08/13/2018 23:00:00'), 'America/Santiago');
fmt(x);                             // "2018-08-14T00:00:00-03:00 America/Santiago"
fmt(x.clone().subtract(1, 'days')); // "2018-08-13T00:00:00-03:00 America/Santiago"
fmt(x.clone().subtract(2, 'days')); // "2018-08-12T01:00:00-03:00 America/Santiago" - hour not preserved, but date changed
fmt(x.clone().subtract(3, 'days')); // "2018-08-11T00:00:00-04:00 America/Santiago" - original hour preserved now

Recibimos otro informe de este problema, de un usuario con America/Asuncion como su zona horaria.

La función que produce el error en nuestra aplicación se ve así:

function generateDayRange(start, end) {
    const days = [];
    let current = start.clone();

    while (current <= end) {
        days.push(current.clone());
        current = current.add(1, 'days');
    }

    return days;
}

Cuando usamos esta función, pasamos una fecha de inicio que es al comienzo del día y una fecha de finalización donde la hora no es tan significativa:

generateDayRange(startDate.clone().startOf('week'), startDate.clone().endOf('week'));
generateDayRange(getAppDate(startDate), getAppDate(endDate));
generateDayRange(startRange, startRange.clone().add(27, 'days'));

El problema es que la matriz resultante de fechas de momento contiene un día duplicado, que se refleja en la interfaz de usuario de nuestra aplicación (un calendario tiene un día duplicado).

Mi mayor preocupación es que, dado que la biblioteca no se comporta como se esperaba, puede haber problemas más sutiles que pasen desapercibidos, como una pérdida de datos percibida, solicitudes incorrectas, etc.

Trabajaré para obtener un conjunto de pruebas en un codepen o algo así, para que los lectores curiosos puedan intentar resolver este problema.

El problema relacionado # 4785 tiene enlaces al código:
ejemplo de momento https://runkit.com/embed/1r62d83amq7x
sin embargo, luxon maneja esto correctamente
https://runkit.com/embed/t49achvensqf

Sé que hubo cambios recientes de DST, por lo que deberíamos verificar el código actualmente en develop (pero no publicado). O podemos esperar hasta el próximo lanzamiento para ver si las cosas siguen igual.

El comportamiento aquí parece que se cambió entre la zona horaria de momento v0.5.4 y la v0.5.26.
En la versión anterior, agregar 1 día a '2018-08-11' le daba las 11 p.m. del mismo día
En la nueva versión, le da '2018-08-12' a la medianoche. Pero este tiempo no existe realmente, y agregar 1 minuto retrocede una hora, pero se suponía que este cambio de horario de verano agregaba 1 hora.

Versión antigua, se comporta como se describe en este número:
https://jsfiddle.net/eqyvuxht/1/

Nueva versión, cambio de comportamiento, pero creo que sigue mal.
https://jsfiddle.net/0h6atn4b/4/

Aquí hay una solución:

function switchZone(m, zone) {
  let arr = [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second()];
  if(zone) {
    return moment.tz(arr, zone);
  }
  return moment(arr);
}

function safeAddDays(m, days) {
   let oldZone = m.tz();
   let utc = switchZone(m, 'UTC');
   utc.add(days, 'days');
   return switchZone(utc, oldZone);
}

Nos encontramos con el mismo problema este mes (septiembre de 2019) en el que nuestros usuarios de Santiago que utilizan nuestro selector de fechas ven un calendario con fechas que van 1, 2, 3, 4, 5, 6, 7, 7, 7, 7, para el mes de septiembre ... Bajo el capó, también estamos usando .add(1, 'days') . Ocurrirá nuevamente el próximo año en septiembre de 2020 si no tenemos una solución para entonces.

La solución alternativa de

¿Fue útil esta página
0 / 5 - 0 calificaciones