PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Zeitzonen in Rails und PostgreSQL komplett ignorieren

Postgres hat zwei verschiedene Zeitstempel-Datentypen:

  • timestamp with time zone , Kurzname:timestamptz
  • timestamp without time zone , Kurzname:timestamp

timestamptz ist bevorzugt Geben Sie wörtlich die Datums-/Uhrzeitfamilie ein. Es hat typispreferred gesetzt in pg_type , die relevant sein können:

  • Generieren von Zeitreihen zwischen zwei Daten in PostgreSQL

Interner Speicher und Epoche

Zeitstempel belegen intern 8 Byte Speicherplatz auf der Festplatte und im RAM. Es ist ein ganzzahliger Wert, der die Anzahl der Mikrosekunden seit der Postgres-Epoche, 2000-01-01 00:00:00 UTC darstellt.

Postgres verfügt auch über integrierte Kenntnisse der häufig verwendeten UNIX-Zeitzählung von Sekunden aus der UNIX-Epoche 1970-01-01 00:00:00 UTC und verwendet diese in den Funktionen to_timestamp(double precision) oder EXTRACT(EPOCH FROM timestamptz) .

Der Quellcode:

* Timestamps, as well as the h/m/s fields of intervals, are stored as
* int64 values with units of microseconds.  (Once upon a time they were  
* double values with units of seconds.)

Und:

/* Julian-date equivalents of Day 0 in Unix and Postgres reckoning */  
#define UNIX_EPOCH_JDATE        2440588 /* == date2j(1970, 1, 1) */  
#define POSTGRES_EPOCH_JDATE    2451545 /* == date2j(2000, 1, 1) */  

Die Mikrosekundenauflösung entspricht maximal 6 Nachkommastellen für Sekunden.

timestamp

Für timestamp Es wird keine Zeitzone explizit angegeben. Postgres ignoriert jeder Zeitzonenmodifikator, der versehentlich zum Eingabeliteral hinzugefügt wurde!

Es werden keine Stunden für die Anzeige verschoben. Da alles in der gleichen Zeitzone passiert, ist das in Ordnung. Für eine andere Zeitzone die Bedeutung Änderungen, aber Wert und anzeigen gleich bleiben.

timestamp

Umgang mit timestamptz ist dezent anders. Ich zitiere das Handbuch hier:

Für timestamp with time zone , ist der intern gespeicherte Wert immer in UTC (Koordinierte Weltzeit ...)

Fette Hervorhebung von mir. Die Zeitzone selbst wird nie gespeichert . Es ist ein Eingabemodifikator, der verwendet wird, um den entsprechenden UTC-Zeitstempel zu berechnen, der gespeichert wird - oder ein Ausgabedekorator, der verwendet wird, um die lokale Zeit für die Anzeige zu berechnen - mit angehängtem Zeitzonen-Offset. Wenn Sie keinen Offset für timestamptz anhängen bei der Eingabe wird die aktuelle Zeitzoneneinstellung der Sitzung übernommen. Alle Berechnungen werden mit UTC-Zeitstempelwerten durchgeführt. Wenn Sie (möglicherweise) mit mehr als einer Zeitzone zu tun haben, verwenden Sie timestamptz . Mit anderen Worten:Wenn Zweifel oder Missverständnisse bezüglich der angenommenen Zeitzone bestehen, verwenden Sie timestamptz . Gilt für die meisten Anwendungsfälle.

Clients wie psql oder pgAdmin oder jede Anwendung, die über libpq kommuniziert (wie Ruby mit dem pg-Gem), wird der Zeitstempel plus Offset für die aktuelle Zeitzone präsentiert oder nach einem angeforderten Zeitzone (siehe unten). Es ist immer der selbe Zeitpunkt , nur das Anzeigeformat variiert. Oder, wie es im Handbuch heißt:

Alle zeitzonenabhängigen Daten und Zeiten werden intern in UTC gespeichert. Sie werden in die Ortszeit der durch TimeZone angegebenen Zone konvertiert Konfigurationsparameter, bevor sie dem Client angezeigt werden.

Beispiel in psql:

db=# SELECT timestamptz '2012-03-05 20:00+03';
      timestamptz
------------------------
 2012-03-05 18:00:00+01

Was ist hier passiert?
Ich habe einen beliebigen Zeitzonenversatz +3 gewählt für das Eingabeliteral. Für Postgres ist dies nur eine von vielen Möglichkeiten, den UTC-Zeitstempel 2012-03-05 17:00:00 einzugeben . Das Ergebnis der Abfrage wird angezeigt für die aktuelle Zeitzoneneinstellung Wien/Österreich in meinem Test, der einen Offset +1 hat im Winter und +2 während der Sommerzeit ("Daylight Saving Time", DST). Also 2012-03-05 18:00:00+01 da die Sommerzeit erst später einsetzt.

Postgres vergisst das Eingabeliteral sofort. Es erinnert sich nur an den Wert für den Datentyp. Genau wie bei einer Dezimalzahl. numeric '003.4' oder numeric '+3.4' - beide ergeben den exakt gleichen internen Wert.

AT TIME ZONE

Jetzt fehlt nur noch ein Werkzeug, um Zeitstempelliterale entsprechend einer bestimmten Zeitzone zu interpretieren oder darzustellen. Dort befindet sich AT TIME ZONE Konstrukt kommt ins Spiel. Es gibt zwei verschiedene Anwendungsfälle. timestamptz wird in timestamp umgewandelt und umgekehrt.

Zur Eingabe der UTC timestamptz 2012-03-05 17:00:00+0 :

SELECT timestamp '2012-03-05 17:00:00' AT TIME ZONE 'UTC'

... was äquivalent ist zu:

SELECT timestamptz '2012-03-05 17:00:00 UTC'

Um den gleichen Zeitpunkt wie EST timestamp anzuzeigen (Eastern Standard Time):

SELECT timestamp '2012-03-05 17:00:00' AT TIME ZONE 'UTC' AT TIME ZONE 'EST'

Richtig, AT TIME ZONE 'UTC' zweimal . Der erste interpretiert den timestamp Wert als (gegebener) UTC-Zeitstempel, der den Typ timestamptz zurückgibt . Der zweite konvertiert den timestamptz zum timestamp in der angegebenen Zeitzone 'EST' - was eine Wanduhr zu diesem Zeitpunkt in der Zeitzone EST anzeigt.

Beispiele

SELECT ts AT TIME ZONE 'UTC'
FROM  (
   VALUES
      (1, timestamptz '2012-03-05 17:00:00+0')
    , (2, timestamptz '2012-03-05 18:00:00+1')
    , (3, timestamptz '2012-03-05 17:00:00 UTC')
    , (4, timestamp   '2012-03-05 11:00:00'  AT TIME ZONE '+6') 
    , (5, timestamp   '2012-03-05 17:00:00'  AT TIME ZONE 'UTC') 
    , (6, timestamp   '2012-03-05 07:00:00'  AT TIME ZONE 'US/Hawaii')  -- ①
    , (7, timestamptz '2012-03-05 07:00:00 US/Hawaii')                  -- ①
    , (8, timestamp   '2012-03-05 07:00:00'  AT TIME ZONE 'HST')        -- ①
    , (9, timestamp   '2012-03-05 18:00:00+1')  -- ② loaded footgun!
      ) t(id, ts);

Gibt 8 (oder 9) identisch zurück Zeilen mit einem timestamptz Spalten mit demselben UTC-Zeitstempel 2012-03-05 17:00:00 . Die 9. Reihe funktioniert zufällig in meiner Zeitzone, ist aber eine böse Falle. Siehe unten.

① Zeilen 6 - 8 mit Zeitzone name und Abkürzung der Zeitzone für die Hawaii-Zeit unterliegen der Sommerzeit (DST) und können abweichen, wenn auch derzeit nicht. Ein Zeitzonenname wie 'US/Hawaii' kennt die Sommerzeitregeln und alle historischen Verschiebungen automatisch, während eine Abkürzung wie HST ist nur ein dummer Code für einen festen Offset. Eventuell müssen Sie ein anderes Kürzel für Sommer-/Normalzeit anhängen. Der Name interpretiert any richtig Zeitstempel in der angegebenen Zeitzone. Eine Abkürzung ist billig, muss aber für den gegebenen Zeitstempel richtig sein:

  • Zeitzonennamen mit identischen Eigenschaften führen zu unterschiedlichen Ergebnissen, wenn sie auf den Zeitstempel angewendet werden

Die Sommerzeit gehört nicht zu den klügsten Ideen, die die Menschheit je hatte.

② Reihe 9, markiert als geladenes Fußgewehr funktioniert für mich , aber nur zufällig. Wenn Sie explizit ein Literal in timestamp [without time zone] umwandeln , ein eventueller Zeitzonenoffset wird ignoriert! Es wird nur der bloße Zeitstempel verwendet. Der Wert wird dann automatisch in timestamptz umgewandelt im Beispiel passend zum Spaltentyp. Für diesen Schritt die timezone wird die Einstellung der aktuellen Sitzung angenommen, die zufällig dieselbe Zeitzone +1 ist in meinem Fall (Europa/Wien). Aber wahrscheinlich nicht in Ihrem Fall - was zu einem anderen Wert führen wird. Kurz gesagt:Caste nicht timestamptz Literale in timestamp oder Sie verlieren den Zeitzonen-Offset.

Ihre Fragen

Der Benutzer speichert eine Uhrzeit, z. B. 17. März 2012, 19:00 Uhr. Ich möchte nicht, dass Zeitzonenumrechnungen oder die Zeitzone gespeichert werden.

Die Zeitzone selbst wird nie gespeichert. Verwenden Sie eine der oben genannten Methoden, um einen UTC-Zeitstempel einzugeben.

Ich verwende nur die vom Benutzer angegebene Zeitzone, um Aufzeichnungen 'vor' oder 'nach' der aktuellen Zeit in der lokalen Zeitzone des Benutzers zu erhalten.

Sie können eine Abfrage für alle Clients in verschiedenen Zeitzonen verwenden.
Für absolute globale Zeit:

SELECT * FROM tbl WHERE time_col > (now() AT TIME ZONE 'UTC')::time

Für Zeit nach lokaler Uhr:

SELECT * FROM tbl WHERE time_col > now()::time

Noch nicht genug von Hintergrundinformationen? Im Handbuch steht mehr.