Zunächst einmal ist das Zeithandling und die Arithmetik von PostgreSQL fantastisch und Option 3 ist im allgemeinen Fall in Ordnung. Es ist jedoch eine unvollständige Ansicht von Zeit und Zeitzonen und kann ergänzt werden:
- Speichern Sie den Namen der Zeitzone eines Benutzers als Benutzereinstellung (z . B.
America/Los_Angeles
nicht -0700
).
- Lassen Sie Benutzerereignisse / Zeitdaten lokal an ihren Referenzrahmen senden (höchstwahrscheinlich ein Versatz von UTC, z. B.
-0700
).
- Konvertieren Sie in der Anwendung die Zeit in
UTC
eine TIMESTAMP WITH TIME ZONE
Spalte und speichern Sie diese .
- Zeitanfragen lokal in die Zeitzone eines Benutzers zurückgeben (dh von
UTC
nach konvertieren America/Los_Angeles
).
- Stellen Sie Ihre Datenbank
timezone
auf ein UTC
.
Diese Option funktioniert nicht immer, da es schwierig sein kann, die Zeitzone eines Benutzers und damit die Absicherungsempfehlung TIMESTAMP WITH TIME ZONE
für leichte Anwendungen zu ermitteln. Lassen Sie mich jedoch einige Hintergrundaspekte dieser Option 4 näher erläutern.
Wie bei Option 3 liegt der Grund dafür WITH TIME ZONE
darin, dass der Zeitpunkt, zu dem etwas passiert ist, ein absoluter Zeitpunkt ist. WITHOUT TIME ZONE
ergibt eine relative Zeitzone. Mischen Sie niemals absolute und relative TIMESTAMPs.
Stellen Sie aus programmatischer und konsistenter Sicht sicher, dass alle Berechnungen mit UTC als Zeitzone durchgeführt werden. Dies ist keine PostgreSQL-Anforderung, hilft jedoch bei der Integration in andere Programmiersprachen oder -umgebungen. Das Festlegen von a CHECK
in der Spalte, um sicherzustellen, dass das Schreiben in die Zeitstempelspalte einen Zeitzonenversatz von aufweist, 0
ist eine Verteidigungsposition, die einige Fehlerklassen verhindert (z. B. ein Skript speichert Daten in eine Datei und etwas anderes sortiert die Zeitdaten mithilfe von a lexikalische Sortierung). Auch hier benötigt PostgreSQL dies nicht, um Datumsberechnungen korrekt durchzuführen oder zwischen Zeitzonen zu konvertieren (dh PostgreSQL ist sehr geschickt darin, Zeiten zwischen zwei beliebigen Zeitzonen zu konvertieren). So stellen Sie sicher, dass in die Datenbank eingehende Daten mit einem Offset von Null gespeichert werden:
CREATE TABLE my_tbl (
my_timestamp TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
CHECK(EXTRACT(TIMEZONE FROM my_timestamp) = '0')
);
test=> SET timezone = 'America/Los_Angeles';
SET
test=> INSERT INTO my_tbl (my_timestamp) VALUES (NOW());
ERROR: new row for relation "my_tbl" violates check constraint "my_tbl_my_timestamp_check"
test=> SET timezone = 'UTC';
SET
test=> INSERT INTO my_tbl (my_timestamp) VALUES (NOW());
INSERT 0 1
Es ist nicht 100% perfekt, bietet jedoch eine ausreichend starke Anti-Footshooting-Maßnahme, um sicherzustellen, dass die Daten bereits in UTC konvertiert wurden. Es gibt viele Meinungen dazu, aber dies scheint aus meiner Erfahrung die beste in der Praxis zu sein.
Die Kritik an der Behandlung von Zeitzonen in Datenbanken ist weitgehend gerechtfertigt (es gibt viele Datenbanken, die dies mit großer Inkompetenz behandeln). Die Behandlung von Zeitstempeln und Zeitzonen durch PostgreSQL ist jedoch ziemlich beeindruckend (trotz einiger "Funktionen" hier und da). Zum Beispiel eine solche Funktion:
-- Make sure we're all working off of the same local time zone
test=> SET timezone = 'America/Los_Angeles';
SET
test=> SELECT NOW();
now
-------------------------------
2011-05-27 15:47:58.138995-07
(1 row)
test=> SELECT NOW() AT TIME ZONE 'UTC';
timezone
----------------------------
2011-05-27 22:48:02.235541
(1 row)
Beachten Sie, dass AT TIME ZONE 'UTC'
Zeitzoneninformationen entfernt und ein Verwandter TIMESTAMP WITHOUT TIME ZONE
mithilfe des Referenzrahmens Ihres Ziels erstellt wird ( UTC
).
Bei der Konvertierung von einer unvollständigen TIMESTAMP WITHOUT TIME ZONE
in eine TIMESTAMP WITH TIME ZONE
wird die fehlende Zeitzone von Ihrer Verbindung geerbt:
test=> SET timezone = 'America/Los_Angeles';
SET
test=> SELECT EXTRACT(TIMEZONE_HOUR FROM NOW());
date_part
-----------
-7
(1 row)
test=> SELECT EXTRACT(TIMEZONE_HOUR FROM TIMESTAMP WITH TIME ZONE '2011-05-27 22:48:02.235541');
date_part
-----------
-7
(1 row)
-- Now change to UTC
test=> SET timezone = 'UTC';
SET
-- Create an absolute time with timezone offset:
test=> SELECT NOW();
now
-------------------------------
2011-05-27 22:48:40.540119+00
(1 row)
-- Creates a relative time in a given frame of reference (i.e. no offset)
test=> SELECT NOW() AT TIME ZONE 'UTC';
timezone
----------------------------
2011-05-27 22:48:49.444446
(1 row)
test=> SELECT EXTRACT(TIMEZONE_HOUR FROM NOW());
date_part
-----------
0
(1 row)
test=> SELECT EXTRACT(TIMEZONE_HOUR FROM TIMESTAMP WITH TIME ZONE '2011-05-27 22:48:02.235541');
date_part
-----------
0
(1 row)
Das Fazit:
- Speichern Sie die Zeitzone eines Benutzers als benannte Bezeichnung (z. B.
America/Los_Angeles
) und nicht als Versatz von UTC (z. B. -0700
).
- Verwenden Sie UTC für alles, es sei denn, es gibt einen zwingenden Grund, einen Offset ungleich Null zu speichern
- Behandeln Sie alle UTC-Zeiten ungleich Null als Eingabefehler
- Mischen Sie niemals relative und absolute Zeitstempel
- Wenn möglich auch
UTC
als timezone
in der Datenbank verwenden
Anmerkung zur zufälligen Programmiersprache: Der datetime
Datentyp von Python ist sehr gut darin, die Unterscheidung zwischen absoluten und relativen Zeiten beizubehalten (wenn auch zunächst frustrierend, bis Sie ihn durch eine Bibliothek wie PyTZ ergänzen ).
BEARBEITEN
Lassen Sie mich den Unterschied zwischen relativ und absolut etwas näher erläutern.
Die absolute Zeit wird verwendet, um ein Ereignis aufzuzeichnen. Beispiele: "Benutzer 123 angemeldet" oder "Eine Abschlussfeier beginnt am 28.05.2011 um 14 Uhr PST." Unabhängig von Ihrer lokalen Zeitzone können Sie das Ereignis beobachten, wenn Sie sich dorthin teleportieren können, wo das Ereignis aufgetreten ist. Die meisten Zeitdaten in einer Datenbank sind absolut (und sollten daher TIMESTAMP WITH TIME ZONE
idealerweise mit einem Versatz von +0 und einer Textbezeichnung versehen sein, die die Regeln für die jeweilige Zeitzone darstellt - kein Versatz).
Ein relatives Ereignis wäre, die Zeit von etwas aus der Perspektive einer noch zu bestimmenden Zeitzone aufzuzeichnen oder zu planen. Beispiele: "Die Türen unseres Unternehmens öffnen um 8 Uhr und schließen um 21 Uhr", "Treffen wir uns jeden Montag um 7 Uhr zu einem wöchentlichen Frühstückstreffen" oder "an jedem Halloween um 20 Uhr". Im Allgemeinen wird die relative Zeit in einer Vorlage oder Factory für Ereignisse verwendet, und die absolute Zeit wird für fast alles andere verwendet. Es gibt eine seltene Ausnahme, auf die hingewiesen werden sollte, die den Wert der relativen Zeiten veranschaulichen sollte. Verwenden Sie für zukünftige Ereignisse, die weit genug in der Zukunft liegen und bei denen Ungewissheit über den absoluten Zeitpunkt bestehen könnte, zu dem etwas auftreten könnte, einen relativen Zeitstempel. Hier ist ein Beispiel aus der Praxis:
Angenommen, es ist das Jahr 2004 und Sie müssen eine Lieferung am 31. Oktober 2008 um 13:00 Uhr an der Westküste der USA (dh America/Los_Angeles
/ PST8PDT
) planen . Wenn Sie dies mit absoluter Zeit gespeichert hätten, ’2008-10-31 21:00:00.000000+00’::TIMESTAMP WITH TIME ZONE
wäre die Lieferung um 14 Uhr erschienen, da die US-Regierung den Energy Policy Act von 2005 verabschiedet hat, der die Regeln für die Sommerzeit geändert hat. Im Jahr 2004, als die Lieferung geplant war, wäre das Datum 10-31-2008
die pazifische Standardzeit ( +8000
) gewesen, aber ab dem Jahr 2005 wurden Zeitzonendatenbanken erkannt, 10-31-2008
die die pazifische Sommerzeit gewesen wären ().+0700
). Das Speichern eines relativen Zeitstempels mit der Zeitzone hätte zu einem korrekten Lieferplan geführt, da ein relativer Zeitstempel gegen schlecht informierte Manipulationen durch den Kongress immun ist. Wo der Grenzwert zwischen der Verwendung von relativen und absoluten Zeiten für die Planung von Dingen liegt, ist eine unscharfe Linie, aber meine Faustregel lautet, dass für die Planung für alles in der Zukunft, das weiter als 3-6 Monate dauert, relative Zeitstempel verwendet werden sollten (geplant = absolut vs. geplant = relativ ???).
Die andere / letzte Art der relativen Zeit ist die INTERVAL
. Beispiel: "Die Sitzung läuft 20 Minuten nach der Anmeldung eines Benutzers ab". An INTERVAL
kann entweder mit absoluten Zeitstempeln ( TIMESTAMP WITH TIME ZONE
) oder relativen Zeitstempeln ( TIMESTAMP WITHOUT TIME ZONE
) korrekt verwendet werden . Es ist ebenso richtig zu sagen, "eine Benutzersitzung läuft 20 Minuten nach einer erfolgreichen Anmeldung ab (login_utc + session_duration)" oder "unser morgendliches Frühstückstreffen kann nur 60 Minuten dauern (wiederkehrende_Startzeit + Besprechungslänge)".
Letzte Bits Verwirrung: DATE
, TIME
, TIME WITHOUT TIME ZONE
und TIME WITH TIME ZONE
sind alle relativen Datentypen. Beispiel: '2011-05-28'::DATE
Stellt ein relatives Datum dar, da Sie keine Zeitzoneninformationen haben, mit denen Mitternacht identifiziert werden könnte. Ebenso '23:23:59'::TIME
ist relativ, weil Sie weder die Zeitzone noch die DATE
durch die Zeit dargestellte kennen. Trotzdem '23:59:59-07'::TIME WITH TIME ZONE
wissen Sie nicht, was das DATE
wäre. Und schließlich ist DATE
mit einer Zeitzone nicht tatsächlich ein DATE
, es ist ein TIMESTAMP WITH TIME ZONE
:
test=> SET timezone = 'America/Los_Angeles';
SET
test=> SELECT '2011-05-11'::DATE AT TIME ZONE 'UTC';
timezone
---------------------
2011-05-11 07:00:00
(1 row)
test=> SET timezone = 'UTC';
SET
test=> SELECT '2011-05-11'::DATE AT TIME ZONE 'UTC';
timezone
---------------------
2011-05-11 00:00:00
(1 row)
Das Einfügen von Datums- und Zeitzonen in Datenbanken ist eine gute Sache, aber es ist leicht, subtil falsche Ergebnisse zu erhalten. Ein minimaler zusätzlicher Aufwand ist erforderlich, um Zeitinformationen korrekt und vollständig zu speichern. Dies bedeutet jedoch nicht, dass immer ein zusätzlicher Aufwand erforderlich ist.