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 Darstellung 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 Offset von UTC, wie z. B.
-0700
). ). - Konvertieren Sie in der Anwendung die Zeit in
UTC
und mit einemTIMESTAMP WITH TIME ZONE
gespeichert Spalte. - Geben Sie Zeitanfragen lokal in der Zeitzone eines Benutzers zurück (d. h. konvertieren Sie von
UTC
nachAmerica/Los_Angeles
). - Stellen Sie die
timezone
Ihrer Datenbank ein nachUTC
.
Diese Option funktioniert nicht immer, da es schwierig sein kann, die Zeitzone eines Benutzers zu ermitteln, und daher der abgesicherte Rat, TIMESTAMP WITH TIME ZONE
zu verwenden für Leichtbauanwendungen. Lassen Sie mich jedoch einige Hintergrundaspekte dieser Option 4 ausführlicher erläutern.
Wie Option 3, der Grund für WITH TIME ZONE
weil der Zeitpunkt, zu dem etwas passiert ist, absolut ist Augenblick. WITHOUT TIME ZONE
ergibt einen relativen Zeitzone. Mischen Sie niemals absolute und relative TIMESTAMPs.
Stellen Sie aus programmtechnischer und konsistenter Sicht sicher, dass alle Berechnungen mit UTC als Zeitzone durchgeführt werden. Dies ist keine PostgreSQL-Anforderung, aber es hilft bei der Integration mit anderen Programmiersprachen oder -umgebungen. Setzen eines CHECK
in der Spalte, um sicherzustellen, dass das Schreiben in die Zeitstempelspalte einen Zeitzonenoffset von 0
hat ist eine defensive Position, die einige Klassen von Fehlern verhindert (z. B. ein Skript speichert Daten in einer Datei und etwas anderes sortiert die Zeitdaten mithilfe einer lexikalischen Sortierung). PostgreSQL benötigt dies wiederum nicht, um Datumsberechnungen korrekt durchzuführen oder zwischen Zeitzonen umzurechnen (d. h. PostgreSQL ist sehr geschickt darin, Zeiten zwischen zwei beliebigen Zeitzonen umzurechnen). Um sicherzustellen, 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, aber es bietet eine ausreichend starke Anti-Footshooting-Maßnahme, die sicherstellt, dass die Daten bereits in UTC konvertiert werden. Es gibt viele Meinungen darüber, wie man das macht, aber das scheint meiner Erfahrung nach die beste in der Praxis zu sein.
Kritik an der Zeitzonenbehandlung von Datenbanken ist weitgehend gerechtfertigt (es gibt viele Datenbanken, die damit sehr inkompetent umgehen), aber die Handhabung von Zeitstempeln und Zeitzonen in PostgreSQL ist ziemlich großartig (trotz einiger "Features" 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'
entfernt Zeitzoneninformationen und erstellt einen relativen TIMESTAMP WITHOUT TIME ZONE
Verwenden Sie den Referenzrahmen Ihres Ziels (UTC
).
Beim Konvertieren von einem unvollständigen TIMESTAMP WITHOUT TIME ZONE
zu einem TIMESTAMP WITH TIME ZONE
, die fehlende Zeitzone wird 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)
Die Quintessenz:
- Speichern Sie die Zeitzone eines Benutzers als benanntes Label (z. B.
America/Los_Angeles
) und kein Offset 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
- alle UTC-Zeiten ungleich Null als Eingabefehler behandeln
- Vermischen und vergleichen Sie niemals relative und absolute Zeitstempel
- benutze auch
UTC
alstimezone
wenn möglich in der Datenbank
Anmerkung zur zufälligen Programmiersprache:Pythons datetime
Der Datentyp ist sehr gut darin, die Unterscheidung zwischen absoluten und relativen Zeiten beizubehalten (wenn auch zunächst frustrierend, bis Sie ihn mit einer 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:00 Uhr PST.“ Unabhängig von Ihrer lokalen Zeitzone könnten Sie, wenn Sie sich dorthin teleportieren könnten, wo das Ereignis stattfand, Zeuge des Geschehens werden. Die meisten Zeitdaten in einer Datenbank sind absolut (und sollten daher TIMESTAMP WITH TIME ZONE
sein). , idealerweise mit einem Offset von +0 und einer Textbezeichnung, die die Regeln für die jeweilige Zeitzone darstellt - kein Offset).
Ein relatives Ereignis wäre die Aufzeichnung oder Planung der Zeit von etwas aus der Perspektive einer noch zu bestimmenden Zeitzone. Beispiele:„Die Türen unseres Geschäfts öffnen um 8:00 Uhr und schließen um 21:00 Uhr“, „treffen wir uns jeden Montag um 7:00 Uhr zu einem wöchentlichen Frühstückstreffen“ oder „jedes Halloween um 20:00 Uhr“. Im Allgemeinen wird relative Zeit in einer Vorlage oder Fabrik für Ereignisse verwendet, und absolute Zeit wird für fast alles andere verwendet. Es gibt eine seltene Ausnahme, auf die hingewiesen werden sollte, die den Wert relativer Zeiten veranschaulichen sollte. Verwenden Sie für zukünftige Ereignisse, die weit genug in der Zukunft liegen und bei denen Unsicherheit über den absoluten Zeitpunkt bestehen könnte, zu dem etwas eintreten könnte, einen relativen Zeitstempel. Hier ist ein reales Beispiel:
Angenommen, wir schreiben das Jahr 2004 und Sie müssen eine Lieferung am 31. Oktober 2008 um 13:00 Uhr an der Westküste der USA (d. h. America/Los_Angeles
) planen /PST8PDT
). Wenn Sie dies unter Verwendung der absoluten Zeit mit ’2008-10-31 21:00:00.000000+00’::TIMESTAMP WITH TIME ZONE
gespeichert haben , wäre die Lieferung um 14:00 Uhr erschienen, weil die US-Regierung den Energy Policy Act von 2005 verabschiedet hat, der die Regeln für die Sommerzeit änderte. Im Jahr 2004, als die Lieferung geplant war, das Datum 10-31-2008
wäre die Pacific Standard Time (+8000
), aber ab dem Jahr 2005+ erkannten Zeitzonendatenbanken, dass 10-31-2008
wäre pazifische Sommerzeit gewesen (+0700
). Das Speichern eines relativen Zeitstempels mit der Zeitzone hätte zu einem korrekten Lieferplan geführt, da ein relativer Zeitstempel immun gegen unbedachte Manipulation durch den Kongress ist. Wo die Grenze zwischen der Verwendung relativer und absoluter Zeiten für die Planung von Dingen liegt, ist eine unscharfe Linie, aber meine Faustregel lautet, dass die Planung für alles in der Zukunft über 3-6 Monate hinaus relative Zeitstempel verwenden sollte (geplant =absolut vs. geplant =relativ ???).
Die andere/letzte Art der relativen Zeit ist das INTERVAL
. Beispiel:„Die Sitzung wird 20 Minuten nach der Anmeldung eines Benutzers beendet“. Ein INTERVAL
kann entweder mit absoluten Zeitstempeln (TIMESTAMP WITH TIME ZONE
) oder relative Zeitstempel (TIMESTAMP WITHOUT TIME ZONE
). 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ücksmeeting kann nur 60 Minuten dauern (recurring_start_time + meeting_length)“.
Letzte Verwirrung:DATE
, TIME
, TIME WITHOUT TIME ZONE
und TIME WITH TIME ZONE
sind alles relative Datentypen. Beispiel:'2011-05-28'::DATE
stellt ein relatives Datum dar, da Sie keine Zeitzoneninformationen haben, die verwendet werden könnten, um Mitternacht zu identifizieren. Ebenso '23:23:59'::TIME
ist relativ, da Sie weder die Zeitzone noch das DATE
kennen repräsentiert durch die Zeit. Sogar mit '23:59:59-07'::TIME WITH TIME ZONE
, Sie wissen nicht, was das DATE
ist wäre. Und schließlich DATE
mit einer Zeitzone ist eigentlich kein 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 Daten und Zeitzonen in Datenbanken ist eine gute Sache, aber es ist einfach, subtil falsche Ergebnisse zu erhalten. Es ist nur ein minimaler zusätzlicher Aufwand erforderlich, um Zeitinformationen korrekt und vollständig zu speichern, was jedoch nicht bedeutet, dass der zusätzliche Aufwand immer erforderlich ist.