Moment: L'ajout et la soustraction de dates ne préserve pas les heures lorsque le fuseau horaire a des règles d'heure d'été à minuit

Créé le 22 août 2018  ·  8Commentaires  ·  Source: moment/moment

Description du problème:

J'essaie de créer une plage de dates dans (tableau de dates de moment) où chaque date est 1 jour après la précédente. Cela fonctionne comme prévu pour tous les utilisateurs, mais nous avons reçu un rapport de bogue pour un utilisateur du fuseau horaire Amérique / Santiago qui voit des dates dupliquées dans l'interface utilisateur. Ce que j'ai remarqué, c'est que l'ajout d'un jour à une date particulière n'ajoute en fait que 23 heures.

Avec une date sur 8/11/2018 00:00:00 dans le fuseau horaire Amérique / Santiago, l'ajout d'un jour augmente le temps à 8/11/2018 23:00:00 au lieu de 8/12/2018 00:00:00 .

Les documents expliquent que les heures doivent être préservées lors du passage à l'heure d'été _ tout en utilisant des jours_, mais cela ne semble pas être le cas ici.

Il y a également des considérations spéciales à garder à l'esprit lors de l'ajout de temps qui traverse l'heure d'été. Si vous ajoutez des années, des mois, des semaines ou des jours, l'heure d'origine correspondra toujours à l'heure ajoutée.

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

Étapes à suivre pour reproduire

Dans le code suivant, le fuseau horaire utilisé a sa règle DST à minuit. J'ai une date qui est à minuit la veille de l'entrée en vigueur de cette règle. J'ai ajouté 1 jour à cette date et je m'attends à ce que ce soit le jour suivant à minuit / 0 heure (les heures doivent être préservées), mais cela ne fait qu'ajouter 23 heures.

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

Ici, vous pouvez voir que l'ajout de 24 heures l'a décalé de 25 heures et le décalage du fuseau horaire a été modifié.

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"

Environnement:

  • Version 68.0.3440.84 (version officielle) (64 bits)
  • Mac OSX El Capitan 10.11.6 (15G31)

Autres informations qui peuvent être utiles:

  • Fuseau horaire de la machine: fuseau horaire de l'Est des États-Unis, heure d'été
  • Le code d'heure et de date a été exécuté: 15 h 00 le 22 août 2018
  • J'ai reproduit ce problème dans Chrome Dev Tools sur la page Web Moment.js

Sortie de date JS

(nouvelle date ()). toString ()

  • Mer 22 août 2018 15:13:40 GMT-0400 (Heure avancée de l'Est)

(nouvelle Date ()). toLocaleString ()

  • 22/08/2018, 15:13:40

(nouvelle Date ()). getTimezoneOffset ()

  • 240

navigator.userAgent

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

moment.version

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

Commentaire le plus utile

Le comportement ici semble avoir été modifié entre le fuseau horaire du moment v0.5.4 et v0.5.26.
Dans l'ancienne version, ajouter 1 jour à '2018-08-11' vous donnait 23 heures le même jour
Dans la nouvelle version, il vous donne le '2018-08-12' à minuit. Mais cette heure n'existe pas réellement, et ajouter 1 minute revient en arrière d'une heure - mais ce changement d'heure d'été était censé ajouter 1 heure.

Ancienne version, se comporte comme décrit dans ce problème:
https://jsfiddle.net/eqyvuxht/1/

Nouvelle version, comportement changé, mais toujours faux je pense?
https://jsfiddle.net/0h6atn4b/4/

Voici une solution de contournement:

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

Tous les 8 commentaires

J'ai essayé de reproduire ceci dans le fuseau horaire Europe / Rome, et je n'ai pas pu:

Europe / Rome - https://www.timeanddate.com/time/change/italy/rome

28 oct.2018 - Fin de l'heure d'été
Lorsque l'heure avancée locale est sur le point d'atteindre
Dimanche 28 octobre 2018, 3 h 00, les horloges sont reculées d'une heure à
Dimanche 28 octobre 2018, 02:00:00 heure normale locale à la place.

Il n'y a pas de bogue dans ce code.

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"

Vous pouvez voir que la TZ change, mais les heures restent constantes.

Sur la base des étapes de reproduction, il semble qu'il y ait une divergence dans le code lorsque la date ajoutée arrive parfaitement sur le seuil DST. Dans le fuseau horaire de Santiago, la règle DST est qu'à minuit, les horloges reculent d'une heure.

Il semble que ce cas limite puisse concerner spécifiquement les fuseaux horaires où la coupure DST est à minuit, car je n'ai pas pu reproduire avec un fuseau horaire où la coupure est à 3 heures du matin, avec x date à 3 heures du matin.

Europe / Rome - https://www.timeanddate.com/time/change/italy/rome

28 oct.2018 - Fin de l'heure d'été
Lorsque l'heure avancée locale est sur le point d'atteindre
Dimanche 28 octobre 2018, 3 h 00, les horloges sont reculées d'une heure à
Dimanche 28 octobre 2018, 02:00:00 heure normale locale à la place.

Il n'y a pas de bogue dans ce code.

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"

J'ai pu confirmer que ce bogue n'existe que pour les fuseaux horaires lorsque la date limite de l'heure d'été est à minuit, et vous ajoutez du temps à une date qui fait que la date atterrit exactement à la date limite.

Amérique / Punta_Arenas - https://www.timeanddate.com/time/zone/chile/punta-arenas
En 2016, ce fuseau horaire avait également une coupure à minuit.

Ce code semble avoir un bogue. Voir le commentaire après chaque ligne

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"

J'ai trouvé ce qui semble être un problème similaire lors de la soustraction sur les mêmes types de fuseaux horaires (DST à minuit).

Lorsque vous soustrayez les dates exactement sur un décalage DST, l'heure n'est pas conservée uniquement ce jour-là, mais la date est modifiée.

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

Nous avons reçu un autre rapport sur ce problème, d'un utilisateur avec America/Asuncion comme fuseau horaire.

La fonction qui produit le bogue dans notre application ressemble à ceci:

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

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

    return days;
}

Lorsque nous utilisons cette fonction, nous passons une date de début qui est au début de la journée, et une date de fin où l'heure n'est pas aussi significative:

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

Le problème est que le tableau résultant de dates de moment contient un jour en double, qui est reflété dans l'interface utilisateur de notre application (un calendrier a un jour en double).

Ma plus grande préoccupation est que, puisque la bibliothèque ne se comporte pas comme prévu, il peut y avoir des problèmes plus subtils qui passent inaperçus, comme une perte de données perçue, de mauvaises demandes, etc.

Je vais travailler pour obtenir une série de tests dans un codepen ou quelque chose du genre, afin que les lecteurs curieux puissent tenter de résoudre ce problème.

Le problème lié # 4785 contient des liens vers le code:
exemple de moment https://runkit.com/embed/1r62d83amq7x
pourtant luxon gère cela correctement
https://runkit.com/embed/t49achvensqf

Je sais qu'il y a eu des changements d'heure d'été récents, nous devrions donc vérifier le code actuellement sur develop (mais pas publié). Ou, nous pouvons attendre la prochaine version pour voir si les choses sont toujours les mêmes.

Le comportement ici semble avoir été modifié entre le fuseau horaire du moment v0.5.4 et v0.5.26.
Dans l'ancienne version, ajouter 1 jour à '2018-08-11' vous donnait 23 heures le même jour
Dans la nouvelle version, il vous donne le '2018-08-12' à minuit. Mais cette heure n'existe pas réellement, et ajouter 1 minute revient en arrière d'une heure - mais ce changement d'heure d'été était censé ajouter 1 heure.

Ancienne version, se comporte comme décrit dans ce problème:
https://jsfiddle.net/eqyvuxht/1/

Nouvelle version, comportement changé, mais toujours faux je pense?
https://jsfiddle.net/0h6atn4b/4/

Voici une solution de contournement:

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

Nous rencontrons le même problème ce mois-ci (septembre 2019) où nos utilisateurs de Santiago utilisant notre sélecteur de dates voient un calendrier avec des dates allant de 1, 2, 3, 4, 5, 6, 7, 7, 7, 7, pour le mois de septembre ... Sous le capot, nous utilisons également .add(1, 'days') . Cela se reproduira l'année prochaine en septembre 2020 si nous n'avons pas de solution à ce problème d'ici là.

La solution de contournement

Cette page vous a été utile?
0 / 5 - 0 notes