Rrule: DTSTART con TZID: entre produce una hora incorrecta

Creado en 16 jul. 2019  ·  16Comentarios  ·  Fuente: jakubroztocil/rrule

Informar un 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)

salidas esto:

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

Cuando configuro tzid implementado por @davidgoli obtengo un 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) {}

El resultado esperado es:

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

¿Cuál es la razón de esto?
versión de rrule: 2.6.2
Ejecuto el nodo en la zona horaria CDT. La zona horaria de mi sistema es America/Los_Angeles

Todos 16 comentarios

Cloné este repositorio y agregué esta prueba:

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

Tiene éxito solo cuando lo ejecuto en la zona horaria UTC (cuando configuro "env": { "TZ": "UTC" } en launch.json ). La prueba falla en cualquier otra zona horaria.
@davidgoli, ¿ está previsto y me falta algo aquí? En caso afirmativo, ¿cómo utilizar correctamente el parámetro tzid ? Creo que se agregó para decirle a rrule que se ejecute en una zona horaria especificada.

Tengo este mismo problema pero con rrule.all (). UTC funciona, mientras que cualquier otra zona horaria aplica el desplazamiento nuevamente cuando no debería.

Creo que podría tener algo que ver con la línea 1849 de rrule-tz.js.

var rezonedDate = rezoneIfNeeded(res, options);

rezoneIfNeeded , contrariamente a su nombre, no realiza ninguna verificación y siempre rezona a menos que sea UTC.

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

Parece que está rezonificando la fecha a pesar de que ya está correctamente dividida en zonas.

Cambiar rezonedDate() para simplemente devolver this.date parece arreglar las cosas para mí. No puedo estar completamente seguro de haber roto algo en otro lugar, pero a simple vista, parece que todo lo demás funciona bien cuando se realiza este cambio.

Creo que podría tener algo que ver con la línea 1849 de rrule-tz.js.

var rezonedDate = rezoneIfNeeded(res, options);

rezoneIfNeeded , contrariamente a su nombre, no realiza ninguna verificación y siempre rezona a menos que sea UTC.

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

Parece que está rezonificando la fecha a pesar de que ya está correctamente dividida en zonas.

Esto no es correcto. El método rezonedDate comprueba si tzid está configurado para determinar si se aplica una compensación de zona.

@agordeev Analizaré este problema. La "solución" de @ hlee5zebra de simplemente eliminar el soporte de zona horaria no era necesaria. ¿Obtiene los resultados esperados si simplemente no usa el tzid en ninguno de los casos?

Independientemente, la inconsistencia entre el comportamiento de RRule y RRuleSet me preocupa y probablemente es un error que necesita una mirada más profunda. ¡Gracias por el caso de prueba!

Veo el problema que provocó mi problema: el código de mi servidor no manejaba correctamente el valor de tiempo DTSTART, pasando en UTC cuando debería estar localizado en la zona horaria especificada por el TZID. Quizás eso pueda ayudarte con tu problema @agordeev . Gracias @davidgoli por señalarme en la dirección correcta.

Parece que me he encontrado con un problema en el código base. El método toString() en datewithzone.ts generará una hora UTC cuando se proporcione un TZID, cuando en su lugar debería generar la hora localizada.

@ hlee5zebra asegúrese de anotar este texto en el archivo README:

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

Importante: use fechas UTC

Las fechas en JavaScript son complicadas. RRule intenta admitir la mayor flexibilidad posible sin agregar grandes dependencias de terceros requeridas, pero eso significa que también tenemos algunas reglas especiales.

De forma predeterminada, RRule se ocupa de tiempos "flotantes" o zonas horarias UTC. Si desea obtener resultados en una zona horaria específica, RRule también proporciona compatibilidad con la zona horaria. De cualquier manera, el desplazamiento de "zona horaria" incorporado de JavaScript tiende a interferir, por lo que esta biblioteca simplemente no lo usa en absoluto. Todos los tiempos se devuelven con compensación cero, como si no existiera en JavaScript.

La conclusión es que las fechas "UTC" devueltas siempre deben interpretarse como fechas en su zona horaria local. Esto puede significar que debe realizar una conversión adicional para obtener la hora local "correcta" con la compensación aplicada.

Por esta razón, se recomienda encarecidamente utilizar marcas de tiempo en UTC, por ejemplo. nueva Fecha (Fecha.UTC (...)). Las fechas devueltas también estarán en UTC (excepto en Chrome, que siempre devuelve fechas con un desplazamiento de zona horaria).

La arruga adicional está en la mayoría de las implementaciones de JS, usted obtiene la compensación UTC _o_ la compensación local, pero no puede cambiar entre ellas. Esto también varía según la implementación. Por lo tanto, se puede devolver una "fecha UTC" dando un toString() que incluye la compensación en algunos lugares y no en otros.

Por ejemplo, en Chrome:

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

pero en nodo:

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

Por eso, para obtener el mejor resultado, ignore el toString() y utilice exclusivamente los métodos toISOString() y getUTCHours() (etc.).

Tenga en cuenta que este enfoque, usar solo métodos UTC para todas las fechas y luego "interpretarlos" como si estuvieran en la hora local, permite una forma uniforme de acceder a las fechas y horas devueltas por la regla _ sin necesidad de considerar la zona horaria de la regla_.

De acuerdo, he estado jugando con rrule.all() un poco para intentar hacer que este problema sea reproducible, y esto es lo que encontré para reproducir este problema de manera confiable:

  1. Intenté configurar DTSTART como medianoche dentro de las respectivas zonas horarias de 'America / Adak', 'America / Chicago' (mi hora local), 'America / New_York' y 'UTC', y el resultado rrule.toString() se imprime con precisión:
>> 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"

Mientras camina a través de la función iteradora pasada a rrule.all() , la primera instancia del primer parámetro date resulta ser la siguiente para cada zona horaria cuando cada Date se imprime mediante .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 cuando la zona horaria no está configurada en UTC (por ejemplo, América / Adak, América / New_York), entonces el desfase entre su hora local y la zona horaria seleccionada se resta de la fecha DTSTART. Entonces, la cadena ISO de Nueva York muestra 23:00 ya que el desplazamiento entre mi hora local y Nueva York es +1, que cuando se resta de la medianoche del 18/07/2019, da como resultado lo que vemos, que son las 11 p.m. del 17/07. / 2019.

Sin embargo, tenga en cuenta que esto no sucede para UTC, lo cual es curioso.

¿Crees que este podría ser el problema, o hay alguna configuración que deba realizarse que podría haber pasado por alto?

Como solución a lo que he estado viendo, hice lo siguiente, y esto parece proporcionarme de manera confiable una instancia precisa 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 Sí, su enfoque de usar los métodos getUTCxxx es el correcto recomendado por el archivo Léame.

Tenga en cuenta que una fecha pseudo-UTC casi nunca es _realmente_ en la hora UTC. UTC está sobrecargado para ser la zona horaria "neutral", por lo que los métodos getUTCxxx se pueden usar para recuperar la hora _local_ independientemente de la zona original. Por esta razón, debe esperar ver el mismo comportamiento sin usar tzid como lo haría usando su zona horaria local, en su zona horaria local. tzid solo debe usarse para obtener la hora local actual de una recurrencia _en una zona horaria diferente_. Si siempre desea que las recurrencias estén en la zona horaria local del usuario, no debe usar tzid .

Es por eso que mi reescritura de esta biblioteca no usará el objeto JS Date incorporado en absoluto. Simplemente es demasiado confuso.

@agordeev Analizaré este problema. La "solución" de @ hlee5zebra de simplemente eliminar el soporte de zona horaria no era necesaria. ¿Obtiene los resultados esperados si simplemente no usa el parámetro tzid en ninguno de los casos?

Gracias por tu respuesta 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]
    })

produce:

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

Entonces, las fechas / horas son correctas, pero la zona horaria es UTC. rrule considera DTSTART en UTC cuando omito tzid param.

Si siempre desea que las recurrencias estén en la zona horaria local del usuario, no debe usar tzid.

Pensé que la única opción para obtener recurrencias en la zona horaria del usuario es pasar esa zona horaria a tzid param. Teniendo en cuenta que la biblioteca se usa con node.js en el servidor.

Si la recurrencia será a las 1800 en la hora local del usuario _independientemente de la zona horaria_, entonces no necesita usar tzid . Asegúrese de haber leído acerca de los tiempos "flotantes" como se describe en el archivo README: https://github.com/jakubroztocil/rrule#important -use-utc-date

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

Temas relacionados

spurreiter picture spurreiter  ·  3Comentarios

espen picture espen  ·  11Comentarios

kirrg001 picture kirrg001  ·  5Comentarios

grigio picture grigio  ·  7Comentarios

marcoancona picture marcoancona  ·  22Comentarios