Rrule: DTSTART avec TZID : entre produit une heure incorrecte

Créé le 16 juil. 2019  ·  16Commentaires  ·  Source: jakubroztocil/rrule

Signaler un problème

const ruleStr = [
'DTSTART;TZID=America/Los_Angeles:20190603T181500', 
'RRULE:FREQ=WEEKLY;WKST=SU;BYDAY=MO,TU,WE,FR,SA'
].join('\n')
const rule = RRule.fromString(ruleStr)
const result = rule.between(
new Date('2019-07-16T07:00:00.000-07:00'), 
new Date('2019-07-23T07:00:00.000-07:00')
)
console.log(result)

renvoie ceci :

0:Tue Jul 16 2019 15:15:00 GMT-0500 (CDT) {}
1:Wed Jul 17 2019 15:15:00 GMT-0500 (CDT) {}
2:Fri Jul 19 2019 15:15:00 GMT-0500 (CDT) {}
3:Sat Jul 20 2019 15:15:00 GMT-0500 (CDT) {}
4:Mon Jul 22 2019 15:15:00 GMT-0500 (CDT) {}

15:15:00 GMT-0500 (CDT) correspond à 17:15 PDT, tandis que DTSTART correspond à DTSTART;TZID=America/Los_Angeles:20190603T181500

Lorsque je définis tzid implémenté par @davidgoli, j'obtiens un résultat différent :

const ruleStr = [
'DTSTART:20190603T181500', 
'RRULE:FREQ=WEEKLY;WKST=SU;BYDAY=MO,TU,WE,FR,SA'
].join('\n')
const rule = RRule.fromString(ruleStr)
const ruleSet = new RRuleSet()
ruleSet.rrule(rule)
ruleSet.tzid('America/Los_Angeles')
const result = ruleSet.between(
new Date('2019-07-16T07:00:00.000-07:00'), 
new Date('2019-07-23T07:00:00.000-07:00')
)
console.log(result)
0:Tue Jul 16 2019 13:15:00 GMT-0500 (CDT) {}
1:Wed Jul 17 2019 13:15:00 GMT-0500 (CDT) {}
2:Fri Jul 19 2019 13:15:00 GMT-0500 (CDT) {}
3:Sat Jul 20 2019 13:15:00 GMT-0500 (CDT) {}
4:Mon Jul 22 2019 13:15:00 GMT-0500 (CDT) {}

Le résultat attendu est :

0:Tue Jul 16 2019 20:15:00 GMT-0500 (CDT) {}
1:Wed Jul 17 2019 20:15:00 GMT-0500 (CDT) {}
2:Fri Jul 19 2019 20:15:00 GMT-0500 (CDT) {}
3:Sat Jul 20 2019 20:15:00 GMT-0500 (CDT) {}
4:Mon Jul 22 2019 20:15:00 GMT-0500 (CDT) {}

Quelle est la raison de cela?
version de la règle : 2.6.2
J'exécute le nœud sous le fuseau horaire CDT. Mon fuseau horaire système est America/Los_Angeles

Tous les 16 commentaires

J'ai cloné ce dépôt et ajouté ce test :

  it('generates correct recurrences when recurrence is WEEKLY and has BYDAY specified', () => {
    const rrule = new RRule({
      freq: RRule.WEEKLY,
      dtstart: new Date(Date.UTC(2019, 6, 17, 18, 0, 0)),
      tzid: 'America/Los_Angeles',
      count: 10,
      interval: 1,
      wkst: RRule.SU,
      byweekday: [RRule.MO, RRule.TU, RRule.WE, RRule.FR, RRule.SA]
    })

    expect(rrule.all()).to.deep.equal([
      new Date('2019-07-17T18:00:00.000-07:00'), // WE
      new Date('2019-07-19T18:00:00.000-07:00'), // FR
      new Date('2019-07-20T18:00:00.000-07:00'), // SA
      new Date('2019-07-22T18:00:00.000-07:00'), // MO
      new Date('2019-07-23T18:00:00.000-07:00'), // TU
      new Date('2019-07-24T18:00:00.000-07:00'),
      new Date('2019-07-26T18:00:00.000-07:00'),
      new Date('2019-07-27T18:00:00.000-07:00'),
      new Date('2019-07-29T18:00:00.000-07:00'),
      new Date('2019-07-30T18:00:00.000-07:00')
    ])
  })

Il ne réussit que lorsque je l'exécute sous le fuseau horaire UTC (lorsque je définis "env": { "TZ": "UTC" } dans launch.json ). Le test échoue dans tout autre fuseau horaire.
@davidgoli est-ce prévu et il me manque quelque chose ici ? Si oui, comment utiliser correctement le paramètre tzid ? Je pense qu'il a été ajouté pour dire à rrule de s'exécuter dans un fuseau horaire spécifié.

J'ai le même problème mais avec rrule.all(). L'UTC fonctionne, tandis que tout autre fuseau horaire applique à nouveau le décalage alors qu'il ne devrait pas.

Je pense que cela pourrait avoir quelque chose à voir avec la ligne 1849 de rrule-tz.js.

var rezonedDate = rezoneIfNeeded(res, options);

rezoneIfNeeded , contrairement à son nom, n'effectue aucune vérification et rezone toujours à moins qu'il ne s'agisse d'UTC.

function rezoneIfNeeded(date, options) { return new datewithzone_DateWithZone(date, options.tzid).rezonedDate(); }

On dirait qu'il modifie le zonage de la date même s'il est déjà correctement zoné.

Changer rezonedDate() pour simplement retourner this.date semble arranger les choses pour moi. Je ne peux pas être complètement sûr d'avoir cassé quelque chose ailleurs, mais d'un coup d'œil rapide, il semble que tout le reste fonctionne correctement lorsque ce changement est effectué.

Je pense que cela pourrait avoir quelque chose à voir avec la ligne 1849 de rrule-tz.js.

var rezonedDate = rezoneIfNeeded(res, options);

rezoneIfNeeded , contrairement à son nom, n'effectue aucune vérification et rezone toujours à moins qu'il ne s'agisse d'UTC.

function rezoneIfNeeded(date, options) { return new datewithzone_DateWithZone(date, options.tzid).rezonedDate(); }

On dirait qu'il modifie le zonage de la date même s'il est déjà correctement zoné.

Ce n'est pas correct. La méthode rezonedDate vérifie si tzid est défini pour déterminer s'il faut appliquer un décalage de zone.

@agordeev Je vais me pencher sur ce problème. Le "correctif" de @hlee5zebra consistant à supprimer simplement la prise en charge du fuseau horaire n'était pas nécessaire. Obtenez-vous les résultats attendus si vous n'utilisez tout simplement pas le paramètre tzid dans les deux cas ?

Quoi qu'il en soit, l'incohérence entre le comportement de RRule et RRuleSet me concerne et constitue probablement un bogue qui nécessite un examen plus approfondi. Merci pour le cas test !

Je vois le problème qui a conduit à mon problème - mon code de serveur ne gérait pas correctement l'heure de la valeur DTSTART, passant en UTC alors qu'il devrait être localisé dans le fuseau horaire spécifié par le TZID. Peut-être que cela peut vous aider avec votre problème @agordeev . Merci @davidgoli de m'avoir orienté dans la bonne direction.

On dirait que je suis peut-être tombé sur un problème dans la base de code. La méthode toString() dans datewithzone.ts affichera une heure UTC lorsqu'un TZID est fourni, alors qu'elle devrait plutôt afficher l'heure localisée.

@hlee5zebra assurez-vous de noter ce texte dans le README :

https://github.com/jakubroztocil/rrule#important -use-utc-dates

Important : utilisez les dates UTC

Les dates en JavaScript sont délicates. RRule essaie de prendre en charge autant de flexibilité que possible sans ajouter de grandes dépendances tierces requises, mais cela signifie que nous avons également des règles spéciales.

Par défaut, RRule traite des heures "flottantes" ou des fuseaux horaires UTC. Si vous voulez des résultats dans un fuseau horaire spécifique, RRule fournit également une prise en charge des fuseaux horaires. Quoi qu'il en soit, le décalage de "fuseau horaire" intégré de JavaScript a tendance à gêner, donc cette bibliothèque ne l'utilise tout simplement pas du tout. Toutes les heures sont renvoyées avec un décalage de zéro, comme si elles n'existaient pas en JavaScript.

En fin de compte, les dates "UTC" renvoyées sont toujours destinées à être interprétées comme des dates dans votre fuseau horaire local. Cela peut signifier que vous devez effectuer une conversion supplémentaire pour obtenir l'heure locale « correcte » avec le décalage appliqué.

Pour cette raison, il est fortement recommandé d'utiliser des horodatages en UTC, par exemple. nouvelle Date(Date.UTC(...)). Les dates renvoyées seront également en UTC (sauf sur Chrome, qui renvoie toujours les dates avec un décalage horaire).

La ride supplémentaire se trouve dans la plupart des implémentations JS, vous obtenez soit un décalage UTC _ou_ un décalage local, mais vous ne pouvez pas basculer entre eux. Cela varie également en fonction de la mise en œuvre. Ainsi, une "date UTC" peut être renvoyée donnant un toString() qui inclut le décalage à certains endroits, et pas à d'autres.

Par exemple, dans Chrome :

> new Date(Date.UTC(2016, 10, 5))
Fri Nov 04 2016 17:00:00 GMT-0700 (Pacific Daylight Time)

mais en nœud :

> new Date(Date.UTC(2016, 10, 5))
2016-11-05T00:00:00.000Z

C'est pourquoi pour le meilleur résultat, ignorez la toString() et utilisez exclusivement les méthodes toISOString() et getUTCHours() (etc).

Notez que cette approche - en utilisant uniquement des méthodes UTC pour toutes les dates, puis en les "interprétant" comme étant en heure locale - permet un accès uniforme aux dates et heures renvoyées par rrule _sans avoir à considérer le fuseau horaire de la rrule_.

D'accord, j'ai bricolé un peu rrule.all() pour essayer de rendre ce problème reproductible, et voici ce que j'ai trouvé pour reproduire ce problème de manière fiable :

  1. J'ai essayé de définir DTSTART à minuit dans les fuseaux horaires respectifs de 'America/Adak', 'America/Chicago' (mon heure locale), 'America/New_York' et 'UTC', et le rrule.toString() résultant
>> rRule.toString()
"DTSTART;TZID=America/Adak:20190718T000000
RRULE:FREQ=DAILY"

>> rRule.toString()
"DTSTART;TZID=America/Chicago:20190718T000000
RRULE:FREQ=DAILY"

>> rRule.toString()
"DTSTART;TZID=America/New_York:20190718T000000
RRULE:FREQ=DAILY"

>> rRule.toString()
"DTSTART:20190718T000000Z
RRULE:FREQ=DAILY"

En parcourant la fonction itérateur passée à rrule.all() , la première instance du premier paramètre date s'avère être la suivante pour chaque fuseau horaire lorsque chaque Date est imprimé via .toISOString() :

Amérique/Adak :
"2019-07-18T04:00:00.000Z"

Amérique/Chicago :
"2019-07-18T00:00:00.000Z"

Amérique/New_York :
"2019-07-17T23:00:00,000Z"

UTC:
"2019-07-18T00:00:00.000Z"

Il semble que lorsque le fuseau horaire n'est pas défini sur UTC (par exemple America/Adak, America/New_York), le décalage entre votre heure locale et le fuseau horaire sélectionné est soustrait de la date DTSTART. Ainsi, la chaîne ISO de New York affiche 23h00 car le décalage entre mon heure locale et New York est de +1, ce qui, une fois soustrait de minuit le 18/07/2019, donne ce que nous voyons, qui est 23h00 le 17/07/2019 /2019.

Notez que cela ne se produit pas pour UTC, ce qui est curieux.

Pensez-vous que cela pourrait être le problème, ou y a-t-il une configuration à faire que j'ai peut-être manquée ?

Pour contourner ce que j'ai vu, j'ai fait ce qui suit, et cela semble m'obtenir de manière fiable une instance précise de Date de JavaScript :

rRule.all(function (date, i) {
    if (this.getSelectedTzid() !== 'UTC') {
        date = moment.tz({
            year: date.getUTCFullYear(),
            month: date.getUTCMonth(),
            date: date.getUTCDate(),
            hours: date.getUTCHours(),
            minutes: date.getUTCMinutes()
        }, this.getLocalTzid()).toDate();
    }

    ...
}.bind(this));

@hlee5zebra Oui, votre approche d'utilisation des méthodes getUTCxxx est la bonne recommandée par le fichier readme.

Gardez à l'esprit qu'une date pseudo-UTC n'est presque jamais _vraiment_ à l'heure UTC. UTC est juste surchargé pour être le fuseau horaire "neutre", donc les méthodes getUTCxxx peuvent être utilisées pour récupérer l'heure _local_ quel que soit le fuseau d'origine. Pour cette raison, vous devriez vous attendre à voir le même comportement sans utiliser tzid qu'en utilisant votre fuseau horaire local, dans votre fuseau horaire local. tzid ne doit être utilisé que pour obtenir l'heure locale actuelle d'une récurrence _dans un fuseau horaire différent_. Si vous voulez toujours que les récurrences soient dans le fuseau horaire local de l'utilisateur, vous ne devez pas utiliser tzid .

C'est pourquoi ma réécriture de cette bibliothèque n'utilisera pas du tout l'objet JS Date intégré. C'est tout simplement trop confus.

@agordeev Je vais me pencher sur ce problème. Le "correctif" de @hlee5zebra consistant à supprimer simplement la prise en charge du fuseau horaire n'était pas nécessaire. Obtenez-vous les résultats escomptés si vous n'utilisez tout simplement pas le paramètre tzid dans les deux cas ?

Merci pour ta réponse David.

    const rrule = new RRule({
      freq: RRule.WEEKLY,
      dtstart: new Date(Date.UTC(2019, 6, 17, 18, 0, 0)),
      // tzid: 'America/Los_Angeles',
      count: 10,
      interval: 1,
      wkst: RRule.SU,
      byweekday: [RRule.MO, RRule.TU, RRule.WE, RRule.FR, RRule.SA]
    })

produit :

  -  [Date: 2019-07-17T18:00:00.000Z]
  -  [Date: 2019-07-19T18:00:00.000Z]
  -  [Date: 2019-07-20T18:00:00.000Z]
  -  [Date: 2019-07-22T18:00:00.000Z]
  etc..

Les dates/heures sont donc correctes, mais le fuseau horaire est UTC. rrule considère DTSTART en UTC lorsque j'omet le param tzid.

Si vous voulez toujours que les récurrences soient dans le fuseau horaire local de l'utilisateur, vous ne devez pas utiliser tzid.

Je pensais que la seule option pour obtenir des récurrences dans le fuseau horaire de l'utilisateur était de transmettre ce fuseau horaire à tzid param ? Gardez à l'esprit que la bibliothèque est utilisée avec node.js sur le serveur.

Si la récurrence sera à 1800 dans l'heure locale de l'utilisateur _quel que soit le fuseau horaire_, alors vous n'avez pas besoin d'utiliser tzid . Veuillez vous assurer d'avoir lu les heures "flottantes" décrites dans le README : https://github.com/jakubroztocil/rrule#important -use-utc-dates

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

Questions connexes

mapidemic picture mapidemic  ·  7Commentaires

jimmywarting picture jimmywarting  ·  9Commentaires

espen picture espen  ·  10Commentaires

kirrg001 picture kirrg001  ·  5Commentaires

elazar picture elazar  ·  18Commentaires