Rrule: DTSTART com TZID: entre produz tempo incorreto

Criado em 16 jul. 2019  ·  16Comentários  ·  Fonte: jakubroztocil/rrule

Relatando um problema

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)

produz isto:

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) é 17:15 PDT, enquanto DTSTART é DTSTART;TZID=America/Los_Angeles:20190603T181500

Quando defino tzid implementado por @davidgoli , obtenho um resultado diferente:

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

O resultado esperado é:

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

Qual é a razão disso?
versão da regra: 2.6.2
Eu executo o nó no fuso horário CDT. O fuso horário do meu sistema é America/Los_Angeles

Todos 16 comentários

Clonei este repo e adicionei este teste:

  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')
    ])
  })

Ele tem sucesso apenas quando eu o executo no fuso horário UTC (quando eu defino "env": { "TZ": "UTC" } em launch.json ). O teste falha em qualquer outro fuso horário.
@davidgoli é intencional e estou faltando alguma coisa aqui? Se sim, como usar corretamente o parâmetro tzid ? Acho que foi adicionado para dizer a rrule para executar em um fuso horário especificado.

Eu tenho esse mesmo problema, mas com rrule.all (). O UTC funciona, enquanto qualquer outro fuso horário aplica o deslocamento novamente, quando não deveria.

Acho que pode ter algo a ver com a linha 1849 do rrule-tz.js.

var rezonedDate = rezoneIfNeeded(res, options);

rezoneIfNeeded , ao contrário do seu nome, não realiza nenhuma verificação e sempre zera novamente a menos que seja UTC.

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

Parece que ele está zoneando novamente a data, embora já esteja zoneado corretamente.

Alterar rezonedDate() para simplesmente retornar this.date parece consertar as coisas para mim. Não posso ter certeza absoluta de que quebrou algo em outro lugar, mas, olhando rapidamente, parece que tudo funciona bem quando essa alteração é feita.

Acho que pode ter algo a ver com a linha 1849 do rrule-tz.js.

var rezonedDate = rezoneIfNeeded(res, options);

rezoneIfNeeded , ao contrário do seu nome, não realiza nenhuma verificação e sempre zera novamente a menos que seja UTC.

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

Parece que ele está zoneando novamente a data, embora já esteja zoneado corretamente.

Isso não está correto. O método rezonedDate verifica se tzid está definido para determinar se deve ser aplicado um deslocamento de zona.

@agordeev examinarei esse problema. A "correção" de @ hlee5zebra de simplesmente remover o suporte de fuso horário não foi necessária. Você obtém os resultados esperados se simplesmente não usar o parâmetro tzid em nenhum dos casos?

Independentemente disso, a inconsistência entre o comportamento do RRule e do RRuleSet é preocupante para mim e provavelmente um bug que precisa de uma análise mais aprofundada. Obrigado pelo caso de teste!

Eu vejo o problema que levou ao meu problema - meu código de servidor não estava lidando com o valor de hora DTSTART corretamente, passando em UTC quando deveria ser localizado no fuso horário especificado pelo TZID. Talvez isso possa ajudá-lo com seu problema @agordeev . Obrigado @davidgoli por me apontar na direção certa.

Parece que me deparei com um problema na base de código. O método toString() em datewithzone.ts produzirá uma hora UTC quando um TZID for fornecido, quando, em vez disso, deveria imprimir a hora localizada.

@ hlee5zebra certifique-se de anotar este texto no README:

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

Importante: use datas UTC

Datas em JavaScript são complicadas. O RRule tenta oferecer o máximo de flexibilidade possível sem adicionar grandes dependências de terceiros necessárias, mas isso significa que também temos algumas regras especiais.

Por padrão, o RRule lida em horários "flutuantes" ou fusos horários UTC. Se você deseja resultados em um fuso horário específico, o RRule também fornece suporte para fuso horário. De qualquer forma, o deslocamento de "fuso horário" embutido do JavaScript tende a apenas atrapalhar, então esta biblioteca simplesmente não o usa. Todos os tempos são retornados com deslocamento zero, como se não existisse em JavaScript.

O resultado final é que as datas "UTC" retornadas devem sempre ser interpretadas como datas em seu fuso horário local. Isso pode significar que você precisa fazer uma conversão adicional para obter a hora local "correta" com o deslocamento aplicado.

Por esse motivo, é altamente recomendável usar carimbos de data / hora em UTC, por exemplo. nova data (Date.UTC (...)). As datas retornadas também serão em UTC (exceto no Chrome, que sempre retorna datas com uma diferença de fuso horário).

O problema adicional está na maioria das implementações JS, você obtém o deslocamento UTC _ou_ o deslocamento local, mas não pode alternar entre eles. Isso também varia dependendo da implementação. Portanto, uma "data UTC" pode ser retornada dando um toString() que inclui o deslocamento em alguns lugares, e não em outros.

Por exemplo, no Chrome:

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

mas no nó:

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

É por isso que para obter o melhor resultado, ignore o toString() e use exclusivamente os métodos toISOString() e getUTCHours() (etc).

Observe que essa abordagem - usando apenas métodos UTC para todas as datas e, em seguida, "interpretando-os" como sendo de hora local - permite uma maneira uniforme de acessar datas e horas retornadas por rrule _sem a necessidade de considerar o fuso horário da regra_.

Ok, estou mexendo em rrule.all() por um tempo para tentar tornar esse problema reproduzível e aqui está o que descobri para reproduzir esse problema de maneira confiável:

  1. Tentei definir DTSTART como meia-noite nos respectivos fusos horários de 'America / Adak', 'America / Chicago' (meu horário local), 'America / New_York' e 'UTC', e o resultado rrule.toString() é impresso com precisão:
>> 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"

Ao percorrer a função iteradora passada para rrule.all() , a primeira instância do primeiro parâmetro date acaba sendo o seguinte para cada fuso horário quando cada Date é impresso via .toISOString() :

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

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

America / New_York:
"2019-07-17T23: 00: 00.000Z"

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

Parece que quando o fuso horário não está definido para UTC (por exemplo, América / Adak, América / New_York), a diferença entre a hora local e o fuso horário selecionado é subtraída da data DTSTART. Portanto, a string ISO de Nova York está mostrando 23:00, pois o deslocamento entre meu horário local e Nova York é +1, que quando subtraído da meia-noite de 18/07/2019 resulta no que vemos, que é 23h de 17/07 / 2019.

Observe que isso não acontece com o UTC, o que é curioso.

Você acha que esse pode ser o problema ou há alguma configuração que precise ser feita que eu possa ter esquecido?

Como uma solução alternativa para o que tenho visto, fiz o seguinte, e isso parece me fornecer de forma confiável uma instância precisa do Date do 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 Sim, sua abordagem de usar os métodos getUTCxxx é a correta recomendada pelo leia-me.

Lembre-se de que uma data pseudo-UTC quase nunca está _realmente_ no horário UTC. O UTC está apenas sobrecarregado para ser o fuso horário "neutro", então os métodos getUTCxxx podem ser usados ​​para recuperar a hora _local_ independentemente do fuso original. Por esse motivo, você deve esperar ver o mesmo comportamento sem usar tzid como faria usando seu fuso horário local, em seu fuso horário local. tzid só deve ser usado para obter a hora local atual de uma recorrência _ em um fuso horário diferente_. Se você quiser que as recorrências sempre estejam no fuso horário local do usuário, não deve usar tzid .

É por isso que minha reescrita desta biblioteca não usará o objeto JS Date integrado. É simplesmente muito confuso.

@agordeev examinarei esse problema. A "correção" de @ hlee5zebra de simplesmente remover o suporte de fuso horário não foi necessária. Você obtém os resultados esperados se simplesmente não usar o parâmetro tzid em nenhum dos casos?

Obrigado pela sua resposta 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]
    })

produz:

  -  [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..

Portanto, as datas / horas estão corretas, mas o fuso horário é UTC. rrule considera DTSTART em UTC quando omito o parâmetro tzid.

Se você sempre deseja que as recorrências estejam no fuso horário local do usuário, não deve usar tzid.

Achei que a única opção para obter recorrências no fuso horário do usuário seria passar esse fuso horário para o parâmetro tzid. Lembre-se de que a biblioteca é usada com node.js no servidor.

Se a recorrência for às 18h00 no horário local do usuário _ independentemente do fuso horário_, você não precisa usar tzid . Certifique-se de ler sobre tempos "flutuantes" conforme descrito no README: https://github.com/jakubroztocil/rrule#important -use-utc-date

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

shorlbeck picture shorlbeck  ·  21Comentários

espen picture espen  ·  10Comentários

zeluspudding picture zeluspudding  ·  11Comentários

berardo picture berardo  ·  9Comentários

maconfr picture maconfr  ·  6Comentários