Welchen Zeitstempeltyp sollte ich in einer PostgreSQL-Datenbank wählen?

119

Ich möchte eine bewährte Methode zum Speichern von Zeitstempeln in meiner Postgres-Datenbank im Rahmen eines Projekts mit mehreren Zeitzonen definieren.

ich kann

  1. Wählen Sie aus TIMESTAMP WITHOUT TIME ZONEund merken Sie sich, welche Zeitzone zum Einfügezeitpunkt für dieses Feld verwendet wurde
  2. Wählen TIMESTAMP WITHOUT TIME ZONESie ein weiteres Feld aus und fügen Sie es hinzu, das den Namen der Zeitzone enthält, die zum Zeitpunkt des Einfügens verwendet wurde
  3. Wählen Sie TIMESTAMP WITH TIME ZONEdie Zeitstempel aus und fügen Sie sie entsprechend ein

Ich habe eine leichte Präferenz für Option 3 (Zeitstempel mit Zeitzone), möchte aber eine fundierte Meinung zu diesem Thema haben.

Jerome WAGNER
quelle

Antworten:

142

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:

  1. Speichern Sie den Namen der Zeitzone eines Benutzers als Benutzereinstellung (z . B. America/Los_Angelesnicht -0700).
  2. Lassen Sie Benutzerereignisse / Zeitdaten lokal an ihren Referenzrahmen senden (höchstwahrscheinlich ein Versatz von UTC, z. B. -0700).
  3. Konvertieren Sie in der Anwendung die Zeit in UTCeine TIMESTAMP WITH TIME ZONESpalte und speichern Sie diese .
  4. Zeitanfragen lokal in die Zeitzone eines Benutzers zurückgeben (dh von UTCnach konvertieren America/Los_Angeles).
  5. Stellen Sie Ihre Datenbank timezoneauf ein UTC.

Diese Option funktioniert nicht immer, da es schwierig sein kann, die Zeitzone eines Benutzers und damit die Absicherungsempfehlung TIMESTAMP WITH TIME ZONEfü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 ZONEdarin, dass der Zeitpunkt, zu dem etwas passiert ist, ein absoluter Zeitpunkt ist. WITHOUT TIME ZONEergibt 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 CHECKin der Spalte, um sicherzustellen, dass das Schreiben in die Zeitstempelspalte einen Zeitzonenversatz von aufweist, 0ist 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 ZONEmithilfe des Referenzrahmens Ihres Ziels erstellt wird ( UTC).

Bei der Konvertierung von einer unvollständigen TIMESTAMP WITHOUT TIME ZONEin eine TIMESTAMP WITH TIME ZONEwird 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 UTCals timezonein der Datenbank verwenden

Anmerkung zur zufälligen Programmiersprache: Der datetimeDatentyp 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 ZONEidealerweise 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 ZONEwä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-2008die pazifische Standardzeit ( +8000) gewesen, aber ab dem Jahr 2005 wurden Zeitzonendatenbanken erkannt, 10-31-2008die 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 INTERVALkann 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 ZONEund TIME WITH TIME ZONEsind alle relativen Datentypen. Beispiel: '2011-05-28'::DATEStellt ein relatives Datum dar, da Sie keine Zeitzoneninformationen haben, mit denen Mitternacht identifiziert werden könnte. Ebenso '23:23:59'::TIMEist relativ, weil Sie weder die Zeitzone noch die DATEdurch die Zeit dargestellte kennen. Trotzdem '23:59:59-07'::TIME WITH TIME ZONEwissen Sie nicht, was das DATEwäre. Und schließlich ist DATEmit 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.

Sean
quelle
2
Wenn Sie postgresql genau mitteilen, in welcher Zeitzone sich der Zeitstempel des Benutzers befindet, übernimmt postgresql das schwere Heben hinter den Kulissen. Es selbst zu konvertieren ist nur Ärger zu leihen.
Seth Robertson
1
@ Sean - wie können Sie mit Ihrer Prüfbedingung jemals einen Zeitstempel ohne einfügen set timezone to 'UTC'? Sie wissen, dass alle zeitzonenbezogenen Daten intern in UTC gespeichert sind ?
2
Der Punkt der Überprüfung besteht darin, sicherzustellen, dass Daten mit einem Versatz von Null von UTC gespeichert werden. Das Sortieren und Abrufen von Informationen und der Vergleich von Zeiten mit Offsets ungleich Null ist fehleranfällig. Durch Erzwingen eines UTC-Offsets von Null können Sie konsistent mit den Daten aus einer einzigen Perspektive auf nahezu risikofreie Weise interagieren, die sich in allen Szenarien vorhersehbar verhält. Wenn es für Zeitstempel praktisch wäre, Textdarstellungen von Zeitzonen zu unterstützen, wären meine Gedanken zu diesem Thema anders. : ~]
Sean
6
@ Sean: Aber wie Jack angibt, werden alle zeitzonensensitiven Zeitstempel grundsätzlich intern in UTC gespeichert und bei Verwendung in Ihre lokale Zeitzone konvertiert. effektiv wird extrahieren (Zeitzone von ...) dann immer zurückgeben, unabhängig von der lokalen Zeitzone der Verbindung: Es hat keine Beziehung dazu, wie der Zeitstempel "gespeichert" wurde. Anders ausgedrückt, die Zeitzone ist überhaupt nicht Teil des Typs und kann nicht gespeichert werden: Die "mit Zeitzone" ist nur eine Eigenschaft, wie die Daten bei der Interaktion mit anderen Typen konvertiert werden. Die Daten haben dabei überhaupt keine Darstellung von Zeitzonen, weder in Textform noch auf andere Weise.
Jay Freeman -saurik-
@ JayFreeman-saurik-: du bist absolut richtig. Das '' CHECK () '' dient als Anti-Footshooting-Maßnahme zum Schutz vor möglicherweise zwielichtigem Code. Wenn Sie sicherstellen, dass die Daten beim Schreiben UTC sind, ist dies eine bescheidene Garantie dafür, dass der Code durchdacht wurde oder die Ausführungsumgebung korrekt eingerichtet ist.
Sean
58

Seans Antwort ist zu komplex und irreführend.

Tatsache ist, dass sowohl "WITH TIME ZONE" als auch "WITHOUT TIME ZONE" den Wert als unixartigen absoluten UTC-Zeitstempel speichern. Der Unterschied besteht darin, wie der Zeitstempel angezeigt wird. Bei "MIT Zeitzone" ist der angezeigte Wert der in die Benutzerzone übersetzte UTC-gespeicherte Wert. Bei "OHNE Zeitzone" wird der gespeicherte UTC-Wert verdreht, um unabhängig von der vom Benutzer eingestellten Zone dasselbe Zifferblatt anzuzeigen.

Die einzige Situation, in der eine "OHNE Zeitzone" verwendet werden kann, besteht darin, dass ein Zifferblattwert unabhängig von der tatsächlichen Zone anwendbar ist. Zum Beispiel, wenn ein Zeitstempel angibt, wann Wahlkabinen geschlossen werden könnten (dh sie schließen um 20:00 Uhr, unabhängig von der Zeitzone einer Person).

Verwenden Sie Auswahl 3. Verwenden Sie immer "MIT Zeitzone", es sei denn, es gibt einen ganz bestimmten Grund, dies nicht zu tun.

Jay
quelle
10
David E. Wheeler, ein bedeutender Postgres-Experte, würde Ihrer Einschätzung gemäß seiner Veröffentlichung " Immer TIMESTAMP WITH TIME ZONE verwenden" zustimmen .
Basil Bourque
2
Was ist, wenn der Browser den UTC-Zeitstempel in die lokale Zeitzone konvertiert? Die Datenbank führt also niemals die Konvertierung durch und enthält nur UTC. Wäre "OHNE Zeitzone" akzeptabel?
Dman
5

Ich bevorzuge Option 3, da Postgres dann einen Großteil der Arbeit erledigen kann, um Zeitstempel in Bezug auf die Zeitzone für Sie neu zu berechnen, während Sie dies bei den anderen beiden selbst tun müssen. Der zusätzliche Speicheraufwand für das Speichern des Zeitstempels mit einer Zeitzone ist wirklich vernachlässigbar, es sei denn, Sie sprechen von Millionen von Datensätzen. In diesem Fall haben Sie wahrscheinlich ohnehin schon ziemlich fleischige Speicheranforderungen.

GordonM
quelle
19
Falsch. Es gibt keinen Overhead… Postgres speichert die Zeitzone nicht ('Offset' ist übrigens der richtige Begriff, nicht die Zeitzone). Der TIMESTAMP WITH TIME ZONEName ist irreführend. Es bedeutet wirklich "Achten Sie beim Einfügen / Aktualisieren auf einen bestimmten Versatz und verwenden Sie diesen Versatz, um das Datum und die Uhrzeit auf UTC einzustellen". Der TIMESTAMP WITHOUT TIME ZONEName bedeutet "Ignorieren Sie alle Offsets, die während des Einfügens / Aktualisierens vorhanden sein könnten, und betrachten Sie die Datums- und Zeitabschnitte als in UTC, ohne dass eine Anpassung erforderlich ist." Lesen Sie das Dokument sorgfältig durch.
Basil Bourque
1
@BasilBourque Vielen Dank für diese Information. Unglaublich nützlich. Für andere, die dies lesen, heißt es in der Zeile aus dem Dokument: "In einem Literal, das als Zeitstempel ohne Zeitzone festgelegt wurde, ignoriert PostgreSQL stillschweigend alle Zeitzonenangaben. Das heißt, der resultierende Wert wird aus den Datums- / Zeitfeldern in abgeleitet der Eingabewert und ist nicht für die Zeitzone angepasst. "
Aidan Rosswood