Rrule: UTC-aber-nicht-UTC-Verwirrung

Erstellt am 5. Apr. 2019  ·  18Kommentare  ·  Quelle: jakubroztocil/rrule

Ich vermute, dass dies kein Problem mit rrule ist, sondern mit meinem Verständnis davon und / oder seiner Zeitzonenunterstützung. Ich habe bereits verwandte Abschnitte in der Dokumentation zu rrule und luxon gelesen .

Codebeispiel

const { RRule, RRuleSet } = require(`rrule`);

const dtstart = new Date();
console.log(`dtstart`, dtstart);

const firstHour = dtstart.getHours();
const secondHour = (firstHour + 2) % 24;

const getRRule = options => new RRule(Object.assign({}, options, {
    freq: RRule.DAILY,
    count: 1,
    dtstart,
    byminute: 0,
    bysecond: 0
}));

const showNextInstance = tzid => {
    const ruleSet = new RRuleSet();
    ruleSet.rrule(getRRule({ tzid, byhour: firstHour }));
    ruleSet.rrule(getRRule({ tzid, byhour: secondHour }));
    console.log(tzid, ruleSet.after(dtstart));
};

showNextInstance(undefined);
showNextInstance(`UTC`);
showNextInstance(`local`);
showNextInstance(`America/Chicago`);

Tatsächliche Leistung

dtstart 2019-04-05T11:51:23.744Z
undefined 2019-04-06T06:00:00.744Z
UTC 2019-04-06T06:00:00.744Z
local 2019-04-06T06:00:00.744Z
America/Chicago 2019-04-06T06:00:00.744Z

Erwartete Ausgabe

Ich würde denken, dass die Angabe von tzid einen gewissen Einfluss auf die Ausgabe hat, aber für jede Zeitzone wird dieselbe Ausgabe generiert.

Ich kann auch nicht sehen, wie die Korrelation zwischen den in den Regeln angegebenen Zeiten und der Ausgabe für die nächste Ereignisinstanz ist. Ich würde erwarten, dass die nächste Instanz ungefähr einer der zwei Stunden entspricht, die durch die bereitgestellten Regeln dargestellt werden, je nachdem, wie die angegebenen Werte für dtstart und tzid interpretiert oder angewendet werden.

Regelversion

2.6.0

Betriebssystem

OS X 10.14.2, Knoten 8.11.4

Lokale Zeitzone

$ date
Fri Apr  5 06:32:33 CDT 2019

Hilfreichster Kommentar

Ich denke, es ist einfach, Probleme mit dem Date -Objekt und Probleme bei der Berechnung von Vorkommen für RRULE , daher werde ich sie separat behandeln, bevor ich die beiden kombiniere. Ich musste dies hauptsächlich aufschreiben, damit ich mein Verständnis des Problems klären konnte und dachte, ich würde hier posten, falls es anderen hilft.

JavaScript-Datumsobjekt

Ich stimme nicht zu, dass das JS-Objekt Date unregelmäßig ist, aber es gibt häufige Missverständnisse mit dem Objekt, die es so erscheinen lassen. Es ist, gelinde gesagt, verwirrend.

Wie @hlee5zebra erwähnt, werden Datumsangaben, die mit dem Date Konstruktor erstellt wurden, immer in UTC dargestellt. Sie können in der lokalen Zeit angezeigt werden, aber hinter den Kulissen werden sie tatsächlich in der Unix-Epoch-Zeit dargestellt. Wir können immer .getTime() oder .toISOString() für das instanziierte Date Objekt ausführen, um uns daran zu erinnern, dass wir in UTC arbeiten.

Die Implementierung folgt auch korrekt der Konvention, die gesamte Verarbeitung in UTC durchzuführen und nur die dem Benutzer angezeigte Ortszeit zu konvertieren. Zufällig sind wir, die Entwickler, der Benutzer; aber dann haben wir unseren eigenen Benutzer im Sinn, der das Wasser trübt. Bei der Entwicklung unserer Apps müssen wir uns an die Konvention halten und Daten in unserem Backend als UTC speichern (JS verarbeitet Daten bereits in UTC, damit sind wir gut da). Wenn Sie Datumsangaben als UTC in Ihrem Datenspeicher speichern, sollten sie als ISO-Strings beim Client ankommen, vom Date Objekt korrekt geparst und in der Ortszeit des Benutzers angezeigt werden, und das alles ohne große Anstrengungen.

Dieser Artikel hat mir geholfen, das Date Objekt zu verstehen und herauszufinden, wie viel ich damit machen kann: https://www.toptal.com/software/definitive-guide-to-datetime-manipulation

RULE

Die Berechnung der Vorkommen für ein RRULE wird schwierig, da es einen grundlegenden Unterschied in der Benutzererwartung und der Verarbeitung von Datumsangaben in UTC gibt, der sich bemerkbar macht, wenn diese beiden Zeitdarstellungen eine Tagesgrenze überschreiten.

Wenn unser Benutzer beispielsweise eine Wiederholung möchte, die jeden Dienstag stattfindet, wird er davon ausgehen, dass alle Vorkommen auf einen Dienstag in seiner Ortszeit fallen. Alle Berechnungen werden jedoch in UTC durchgeführt, also ein RRULE wie:

DTSTART:2019-10-29T00:02:26Z
RRULE:FREQ=WEEKLY;BYWEEKDAY=TU

würde Vorkommnisse dienstags in UTC zurückgeben, aber für jeden Benutzer, der sich deutlich westlich des Nullmeridians befindet, würden Vorkommnisse montags in Ortszeit erscheinen, was offensichtlich nicht das Ergebnis ist, auf das sich irgendjemand erhofft.

Es richtig verstehen

Alles, was der Entwickler tun muss, um dies zu lösen, ist, JavaScript zu zwingen, zu denken, dass das lokale Datum tatsächlich ein UTC-Datum ist, dieses "falsche" Datum auf rrule.js und die Bibliothek ihre Berechnungen durchführen zu lassen diese lokale UTC-Zeit. Wenn Sie die Vorkommen von der Methode .all() , instanziieren Sie dann neue Date Objekte unter Verwendung der "UTC"-Teile dieser Daten. Dadurch entsprechen die Ergebnisse effektiv den Erwartungen.

import { RRule } from 'rrule'

const dtstart = new Date('2019-10-29T00:02:26Z') // same as: `new Date(Date.UTC(2019, 9, 29, 0, 2, 26))`
console.log(dtstart.toISOString()) // 2019-10-29T00:02:26.000Z
console.log(dtstart) // Mon Oct 28 2019 17:02:26 GMT-0700 (Pacific Daylight Time)

const fakeDateStart = setPartsToUTCDate(dtstart)
console.log(fakeDateStart.toISOString()) // 2019-10-28T17:02:26.000Z
console.log(fakeDateStart) // Mon Oct 28 2019 10:02:26 GMT-0700 (Pacific Daylight Time)

const rule = new RRule({
  freq: RRule.WEEKLY,
  byweekday: [RRule.TU],
  count: 2,
  dtstart: fakeDateStart
})

const localUTCOccurrences = rule.all()
console.log(localUTCOccurrences.map(toISOString)) // ["2019-10-29T17:02:26.000Z", "2019-11-05T17:02:26.000Z"]
console.log(localUTCOccurrences) // [Tue Oct 29 2019 10:02:26 GMT-0700 (Pacific Daylight Time), Tue Nov 05 2019 09:02:26 GMT-0800 (Pacific Standard Time)]

const occurrences = localUTCOccurrences.map(setUTCPartsToDate)
console.log(occurrences.map(toISOString)) // ["2019-10-30T00:02:26.000Z", "2019-11-06T01:02:26.000Z"]
console.log(occurrences) // [Tue Oct 29 2019 17:02:26 GMT-0700 (Pacific Daylight Time), Tue Nov 05 2019 17:02:26 GMT-0800 (Pacific Standard Time)]

function setPartsToUTCDate(d) {
  return new Date(
    Date.UTC(
      d.getFullYear(),
      d.getMonth(),
      d.getDate(),
      d.getHours(),
      d.getMinutes(),
      d.getSeconds()
    )
  )
}

function setUTCPartsToDate(d) {
  return new Date(
    d.getUTCFullYear(),
    d.getUTCMonth(),
    d.getUTCDate(),
    d.getUTCHours(),
    d.getUTCMinutes(),
    d.getUTCSeconds()
  )
}

function toISOString(d) {
  return d.toISOString()
}

https://codesandbox.io/s/rrule-localutc-conversion-zxlki

Oder kurz:

import { RRule } from 'rrule'

const date = new Date('2019-10-29T00:02:26Z')

const rule = new RRule({
  freq: RRule.WEEKLY,
  byweekday: [RRule.TU],
  count: 2,
  dtstart: setPartsToUTCDate(date)
})

const occurrences = rule.all().map(setUTCPartsToDate)

https://codesandbox.io/s/rrule-localutc-conversion-in-short-ez7g0

Soweit ich sehen kann, zeigen Chrome, Firefox und Safari alle Datumsangaben als Zeichenfolgen in Ortszeit an, wenn Sie sich an der Konsole anmelden, und Node.js als ISO-Zeichenfolgen in UTC.

Alle 18 Kommentare

Ich bin auch schrecklich verwirrt.

Ok, ich habe es verstanden, es gab diesen wichtigen Absatz in der Readme:

image

Sie müssen diese beiden Hilfsfunktionen für die lokale Zeitzone verwenden. Sie müssten offset ändern, um den Offset für die X-Zeitzone zu erhalten, wenn Sie dies ändern möchten, um tzid zu unterstützen.

/**
 * Mutates date. Add timezone to the date.
 *
 * <strong i="9">@param</strong> {Date} [date]
 */
export function localizeDate(date = new Date()) {
  const offset = new Date().getTimezoneOffset() * 60 * 1000; // get offset for local timezone (can modify here to support tzid)
  date.setTime(date.getTime() - offset);
  return date;
}

/**
 * Mutates a date. De-timezones a date.
 *
 * <strong i="10">@param</strong> {Date} [date]
 */
export function utcifyDate(date = new Date()) {
  const offset = new Date().getTimezoneOffset() * 60 * 1000; // get offset for local timezone (can modify here to support tzid)
  date.setTime(date.getTime() + offset);
  return date;
}

Dann würde sich Ihr Code ändern in:

const { RRule, RRuleSet } = require(`rrule`);

const dtstart = localizeDate(new Date());
console.log(`dtstart`, utcifyDate(new Date(dtstart))); // even though it is utc string, this is the local time

const firstHour = dtstart.getUTCHours();
const secondHour = (firstHour + 2) % 24;

const getRRule = options => new RRule(Object.assign({}, options, {
    freq: RRule.DAILY,
    count: 1,
    dtstart,
    byminute: 0,
    bysecond: 0
}));

const showNextInstance = tzid => {
    const ruleSet = new RRuleSet();
    ruleSet.rrule(getRRule({ byhour: firstHour })); // remove tzid as i dont support this
    ruleSet.rrule(getRRule({ byhour: secondHour })); // removed tzid as i dont support this
    console.log('local:', utcifyDate(ruleSet.after(dtstart));
};

showNextInstance(undefined);
showNextInstance(`UTC`);
showNextInstance(`local`);
showNextInstance(`America/Chicago`);

Ich vermute, dass es in dieser Bibliothek keinen guten Weg geben wird, dies zu umgehen, da sie auf der verwirrenden und unregelmäßigen Date Implementierung von JS basiert. Ich arbeite derzeit an einer kompletten Neufassung, aber es wird einige Zeit dauern, bis es richtig ist.

Danke @davidgoli, deine Arbeit wird so sehr geschätzt!!!

Ich denke, wir brauchen jemanden, der es versteht, einen Artikel zu schreiben, um anderen zu helfen, die Implementierung von unregelmäßigen Daten zu verstehen. Ich verstehe es ehrlich gesagt immer noch nicht, ich habe nur die Dinge optimiert, bis es funktioniert hat.

Dies liegt daran, dass das zurückgegebene Datum, das Sie an die Konsole ausgeben, ein JavaScript-Datum ist, das in eine Zeichenfolge konvertiert wird, und JavaScript druckt Datumsangaben automatisch in UTC an die Konsole (siehe das 'Z' am Ende Ihrer Zeitstempel, das angibt Nullpunktverschiebung). Da die nächsten Vorkommnisse in jedem Ihrer vier Fälle genau zur gleichen Zeit stattfinden (wenn auch in verschiedenen Zeitzonen), wird viermal genau der gleiche Zeitstempel gedruckt.

Beachten Sie auch, dass das JavaScript-Objekt Date die Zeit anhand der Anzahl der Millisekunden verfolgt, die seit Donnerstag, dem 1. eine Art String-Interpretation von "12:34:56pm" oder ähnliches.

@davidgoli

Ich arbeite derzeit an einer kompletten Neufassung, aber es wird einige Zeit dauern, bis es richtig ist.

Vielleicht möchten Sie die Quelle von DateTime Objekt (nicht von Luxon) durchgeführt, das eine Eingabe nimmt und sie in ein gleitendes utc-Datum umwandelt, das der Eingabe entspricht. Die Iteration erfolgt mit dieser UTC-Datumszeit, sodass DST kein Problem darstellt. Nachdem ein Vorkommen gefunden wurde, wird es vor der Übergabe an den Benutzer wieder in ein normales Datum in der entsprechenden Zeitzone umgewandelt.

Zum Beispiel: dieses Datum

import { DateTime as LuxonDateTime } from 'luxon';

const date = LuxonDateTime.fromObject({
  year: 2019,
  month: 1,
  day: 1,
  zone: 'America/Los_Angeles'
})

auf dieses UTC-Datum umgerechnet und die Zeitzone gespeichert

const fakeDate = Date.UTC(2019, 0, 1)
const fakeDateZone = 'America/Los_Angeles'

Beachten Sie, dass dieses UTC-Datum dem Luxon-Datum ähnlich sieht (beide sehen aus wie 2019/1/1 ), aber da sie sich in verschiedenen Zeitzonen befinden, repräsentieren sie nicht die gleiche Zeit.

Abgesehen davon können wir durch die Iteration mit diesem speziellen "UTC" -Datum iterieren, ohne uns um die Sommerzeit kümmern zu müssen. Nachdem das richtige Datum gefunden wurde, können wir es wieder in ein Luxon-Datum umwandeln.

Zum Beispiel:

LuxonDateTime.fromObject({
  year: fakeDate.year,
  month: fakeDate.month,
  day: fakeDate.day,
  zone: fakeDateZone,
})

Dies hält die Iterationslogik schön UTC-freundlich und unterstützt gleichzeitig beliebige Zeitzonen.

@thefliik Ich habe die Primitiven runter; Es sind Wochen, die hier die wahren Kopfschmerzen bereiten. Obwohl ich sehe, dass rschedule (bisher) auch auf byweekno & bysetpos-Unterstützung gestochen hat ...

Ach ja. byweekno ist sehr nervig.

irgendwelche Neuigkeiten?

Ich denke, es ist einfach, Probleme mit dem Date -Objekt und Probleme bei der Berechnung von Vorkommen für RRULE , daher werde ich sie separat behandeln, bevor ich die beiden kombiniere. Ich musste dies hauptsächlich aufschreiben, damit ich mein Verständnis des Problems klären konnte und dachte, ich würde hier posten, falls es anderen hilft.

JavaScript-Datumsobjekt

Ich stimme nicht zu, dass das JS-Objekt Date unregelmäßig ist, aber es gibt häufige Missverständnisse mit dem Objekt, die es so erscheinen lassen. Es ist, gelinde gesagt, verwirrend.

Wie @hlee5zebra erwähnt, werden Datumsangaben, die mit dem Date Konstruktor erstellt wurden, immer in UTC dargestellt. Sie können in der lokalen Zeit angezeigt werden, aber hinter den Kulissen werden sie tatsächlich in der Unix-Epoch-Zeit dargestellt. Wir können immer .getTime() oder .toISOString() für das instanziierte Date Objekt ausführen, um uns daran zu erinnern, dass wir in UTC arbeiten.

Die Implementierung folgt auch korrekt der Konvention, die gesamte Verarbeitung in UTC durchzuführen und nur die dem Benutzer angezeigte Ortszeit zu konvertieren. Zufällig sind wir, die Entwickler, der Benutzer; aber dann haben wir unseren eigenen Benutzer im Sinn, der das Wasser trübt. Bei der Entwicklung unserer Apps müssen wir uns an die Konvention halten und Daten in unserem Backend als UTC speichern (JS verarbeitet Daten bereits in UTC, damit sind wir gut da). Wenn Sie Datumsangaben als UTC in Ihrem Datenspeicher speichern, sollten sie als ISO-Strings beim Client ankommen, vom Date Objekt korrekt geparst und in der Ortszeit des Benutzers angezeigt werden, und das alles ohne große Anstrengungen.

Dieser Artikel hat mir geholfen, das Date Objekt zu verstehen und herauszufinden, wie viel ich damit machen kann: https://www.toptal.com/software/definitive-guide-to-datetime-manipulation

RULE

Die Berechnung der Vorkommen für ein RRULE wird schwierig, da es einen grundlegenden Unterschied in der Benutzererwartung und der Verarbeitung von Datumsangaben in UTC gibt, der sich bemerkbar macht, wenn diese beiden Zeitdarstellungen eine Tagesgrenze überschreiten.

Wenn unser Benutzer beispielsweise eine Wiederholung möchte, die jeden Dienstag stattfindet, wird er davon ausgehen, dass alle Vorkommen auf einen Dienstag in seiner Ortszeit fallen. Alle Berechnungen werden jedoch in UTC durchgeführt, also ein RRULE wie:

DTSTART:2019-10-29T00:02:26Z
RRULE:FREQ=WEEKLY;BYWEEKDAY=TU

würde Vorkommnisse dienstags in UTC zurückgeben, aber für jeden Benutzer, der sich deutlich westlich des Nullmeridians befindet, würden Vorkommnisse montags in Ortszeit erscheinen, was offensichtlich nicht das Ergebnis ist, auf das sich irgendjemand erhofft.

Es richtig verstehen

Alles, was der Entwickler tun muss, um dies zu lösen, ist, JavaScript zu zwingen, zu denken, dass das lokale Datum tatsächlich ein UTC-Datum ist, dieses "falsche" Datum auf rrule.js und die Bibliothek ihre Berechnungen durchführen zu lassen diese lokale UTC-Zeit. Wenn Sie die Vorkommen von der Methode .all() , instanziieren Sie dann neue Date Objekte unter Verwendung der "UTC"-Teile dieser Daten. Dadurch entsprechen die Ergebnisse effektiv den Erwartungen.

import { RRule } from 'rrule'

const dtstart = new Date('2019-10-29T00:02:26Z') // same as: `new Date(Date.UTC(2019, 9, 29, 0, 2, 26))`
console.log(dtstart.toISOString()) // 2019-10-29T00:02:26.000Z
console.log(dtstart) // Mon Oct 28 2019 17:02:26 GMT-0700 (Pacific Daylight Time)

const fakeDateStart = setPartsToUTCDate(dtstart)
console.log(fakeDateStart.toISOString()) // 2019-10-28T17:02:26.000Z
console.log(fakeDateStart) // Mon Oct 28 2019 10:02:26 GMT-0700 (Pacific Daylight Time)

const rule = new RRule({
  freq: RRule.WEEKLY,
  byweekday: [RRule.TU],
  count: 2,
  dtstart: fakeDateStart
})

const localUTCOccurrences = rule.all()
console.log(localUTCOccurrences.map(toISOString)) // ["2019-10-29T17:02:26.000Z", "2019-11-05T17:02:26.000Z"]
console.log(localUTCOccurrences) // [Tue Oct 29 2019 10:02:26 GMT-0700 (Pacific Daylight Time), Tue Nov 05 2019 09:02:26 GMT-0800 (Pacific Standard Time)]

const occurrences = localUTCOccurrences.map(setUTCPartsToDate)
console.log(occurrences.map(toISOString)) // ["2019-10-30T00:02:26.000Z", "2019-11-06T01:02:26.000Z"]
console.log(occurrences) // [Tue Oct 29 2019 17:02:26 GMT-0700 (Pacific Daylight Time), Tue Nov 05 2019 17:02:26 GMT-0800 (Pacific Standard Time)]

function setPartsToUTCDate(d) {
  return new Date(
    Date.UTC(
      d.getFullYear(),
      d.getMonth(),
      d.getDate(),
      d.getHours(),
      d.getMinutes(),
      d.getSeconds()
    )
  )
}

function setUTCPartsToDate(d) {
  return new Date(
    d.getUTCFullYear(),
    d.getUTCMonth(),
    d.getUTCDate(),
    d.getUTCHours(),
    d.getUTCMinutes(),
    d.getUTCSeconds()
  )
}

function toISOString(d) {
  return d.toISOString()
}

https://codesandbox.io/s/rrule-localutc-conversion-zxlki

Oder kurz:

import { RRule } from 'rrule'

const date = new Date('2019-10-29T00:02:26Z')

const rule = new RRule({
  freq: RRule.WEEKLY,
  byweekday: [RRule.TU],
  count: 2,
  dtstart: setPartsToUTCDate(date)
})

const occurrences = rule.all().map(setUTCPartsToDate)

https://codesandbox.io/s/rrule-localutc-conversion-in-short-ez7g0

Soweit ich sehen kann, zeigen Chrome, Firefox und Safari alle Datumsangaben als Zeichenfolgen in Ortszeit an, wenn Sie sich an der Konsole anmelden, und Node.js als ISO-Zeichenfolgen in UTC.

@tim-phillips Ich leide gerade auch darunter und suche nach einer Art Affenpflaster, um das zu umgehen.

Ich habe ein wiederkehrendes Ereignis um 20:00 Uhr EST, das gut funktioniert, aber um 21:00 Uhr EST verschiebt es sich auf den Tag, bevor es sein sollte.

Bin gespannt, ob du hierauf eine Antwort findest.

@tim-phillips, das ist eine ausgezeichnete Aufschlüsselung. Ich bin immer noch dabei, aber es sieht so aus, als würde es mir helfen, endlich zu verstehen, danke!

@tim-phillips Vielen Dank für Ihre Analyse.

Können Sie jedoch Folgendes erklären? Es scheint, dass Ihre Lösung je nach lokaler Zeitzone Ihres Computers immer noch Mittwoch zurückkehren kann.

image

@tonylau Schauen Sie sich nicht die stringifizierte Ausgabe in Ihrer Konsole an, die immer in Ihre lokale Zeitzone konvertiert wird. Sie sollten die getUTC*() Methoden verwenden, um die Teile des Datums zu erhalten, die unabhängig von Ihrer lokalen Zeitzone gleich sind.

@davidgoli

Ich habe meine Zeitzone auf UTC+14 (Kiritimati Island/Line Islands Time) eingestellt und Folgendes erhalten, auch wenn ich getUTCDay() verwende. Der Tag wird immer noch als Mittwoch zurückgegeben, obwohl es Dienstag sein sollte?

Sandbox: https://codesandbox.io/s/rrule-localutc-conversion-in-short-0uzt1

image

Potenziell relevant für diese Diskussion: https://github.com/tc39/proposal-temporal

@tim-phillips danke!!!!!!!!!! das war so hilfreich!!!!!!! Weißt du zufällig, wie man die ics mit der Regel macht? Ich habe das Tagesproblem wieder, wenn ich das ics-Paket in Node verwende und wenn ich die Kalendereinladungen erhalte, haben sie einen freien Tag. (Aber ich kann aufgrund Ihres Codes alles richtig in meine Datenbank einfügen !! vielen Dank.)

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

Prinzhorn picture Prinzhorn  ·  15Kommentare

shavenwalrus picture shavenwalrus  ·  7Kommentare

mapidemic picture mapidemic  ·  7Kommentare

spurreiter picture spurreiter  ·  3Kommentare

zeluspudding picture zeluspudding  ·  11Kommentare