wake-up-neo.net

Zeitzonen werden in Rails und PostgreSQL insgesamt ignoriert

Ich beschäftige mich mit Datum und Uhrzeit in Rails und Postgres und stelle mich auf diese Ausgabe:

Die Datenbank ist in UTC.

Der Benutzer legt eine Zeitzone der Wahl in der Rails-App fest. Diese Zeitzone wird nur verwendet, wenn die Ortszeit des Benutzers zum Zeitvergleich abgerufen wird.

Benutzer speichert eine Zeit, sagen wir 17. März 2012, 19 Uhr. Ich möchte nicht, dass Zeitzonen-Konvertierungen oder Zeitzonen gespeichert werden. Ich möchte nur, dass Datum und Uhrzeit gespeichert werden. Wenn der Benutzer seine Zeitzone geändert hat, würde er immer noch den 17. März 2012, 19:00 Uhr anzeigen.

Ich verwende nur die von den Benutzern angegebene Zeitzone, um Datensätze "vor" oder "nach" der aktuellen Zeit in der lokalen Zeitzone der Benutzer abzurufen. 

Ich verwende zurzeit "Zeitstempel ohne Zeitzone", aber wenn ich die Datensätze abrufe, konvertiert Rails (?) Sie in die Zeitzone der App, die ich nicht möchte. 

Appointment.first.time
 => Fri, 02 Mar 2012 19:00:00 UTC +00:00 

Da die Datensätze in der Datenbank scheinbar als UTC-Daten erscheinen, nimmt mein Hack die aktuelle Zeit, entfernt die Zeitzone mit 'Date.strptime (str, "% m /% d /% Y")' und führt dann meine aus Frage damit:

.where("time >= ?", date_start)

Es scheint, dass es einen einfacheren Weg geben muss, Zeitzonen in der Umgebung einfach zu ignorieren. Irgendwelche Ideen?

143
99miles

Der Datentyp timestamp ist die Kurzbezeichnung für timestamp without time zone.
Die andere Option timestamptz ist die Abkürzung für timestamp with time zone.

timestamptz ist der Typ bevorzugt in der Datums-/Zeitfamilie, wörtlich. Es ist typispreferred in pg_type gesetzt, was relevant sein kann:

Epoche

Intern , Zeitstempel werden als Zählung von Epoche gespeichert. Postgres verwendet eine Epoche des ersten Moments des ersten Tages des Jahres 2000 in UTC, dh 2000-01-01T00: 00: 00Z. Acht Oktetts werden zum Speichern der Zählnummer verwendet. Abhängig von einer Kompilierungszeitoption lautet diese Nummer entweder:

  • Eine 8-Byte-Ganzzahl (Standard) mit 0 bis 6 Stellen pro Sekundenbruchteil
  • Eine Gleitkommazahl (veraltet) mit 0 bis 10 Stellen in Bruchteilen einer Sekunde, bei der die Genauigkeit für Werte, die weiter von Epoch entfernt sind, schnell abnimmt.
    Moderne Postgres-Installationen verwenden die 8-Byte-Ganzzahl.

Beachten Sie, dass Postgres nicht nix-Zeit ​​verwendet. Die Postgres-Epoche ist der erste Moment des 01.01.2000 und nicht der des 01.01.1970 unter Unix. Während die Unix-Zeit eine Auflösung von ganzen Sekunden hat, behält Postgres Bruchteile von Sekunden.

timestamp

Wenn Sie einen Datentyp timestamp[without time zone] definieren, teilen Sie Postgres mit: "Ich gebe keine explizite Zeitzone an, nehmen Sie stattdessen die aktuelle Zeitzone an. Postgres speichert Der Zeitstempel wie er ist - ignoriert ein Zeitzonen-Modifikator, falls Sie einen hinzufügen sollten!

Wenn Sie später timestamp anzeigen, erhalten Sie zurück, was Sie wörtlich eingegeben haben. Mit der gleichen Zeitzoneneinstellung ist alles in Ordnung. Wenn sich die Zeitzoneneinstellung für die Sitzung ändert, ändert sich auch die Bedeutung von timestamp - value bleibt gleich.

timestamptz

Die Handhabung von timestamp with time zone unterscheidet sich geringfügig. Ich zitiere das Handbuch hier :

Für timestamp with time zone ist der intern gespeicherte Wert immer in UTC (Universal Coordinated Time ...).

Meine kühne Betonung. Die Zeitzone selbst wird niemals gespeichert . Es ist ein Eingabemodifikator, der zum Berechnen des entsprechenden UTC-Zeitstempels verwendet wird, der gespeichert wird, oder ein Ausgabemodifikator, der zum Berechnen der anzuzeigenden Ortszeit mit angefügtem Zeitzonenversatz verwendet wird. Wenn Sie bei der Eingabe keinen Versatz für timestamptz anhängen, wird die aktuelle Zeitzoneneinstellung der Sitzung angenommen. Alle Berechnungen werden mit UTC-Zeitstempelwerten durchgeführt. Wenn Sie mit mehr als einer Zeitzone arbeiten müssen (oder müssen), verwenden Sie timestamptz.

Clients wie psql oder pgAdmin oder jede andere Anwendung, die über libpq (wie Ruby mit dem pg-Edelstein) kommuniziert, erhalten den Zeitstempel plus Offset für die aktuelle Zeitzone ) oder entsprechend einer angeforderten Zeitzone (siehe unten). Es ist immer der gleiche Zeitpunkt , nur das Anzeigeformat variiert. Oder wie es im Handbuch heißt :

Alle für die Zeitzone relevanten Datums- und Uhrzeitangaben werden intern in UTC gespeichert. Sie werden in der durch den Konfigurationsparameter TimeZone angegebenen Zone in die Ortszeit konvertiert, bevor sie dem Client angezeigt werden.

Betrachten Sie dieses einfache Beispiel (in psql):

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

Meine kühne Betonung. Was ist hier passiert?
Ich habe einen beliebigen Zeitzonen-Offset +3 für das Eingabeliteral gewählt. 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 ist angezeigt für die aktuelle Zeitzoneneinstellung Wien/Österreich in meinem Test, der einen Versatz +1 während hat Winter und +2 während der Sommerzeit: 2012-03-05 18:00:00+01, weil es in die Winterzeit fällt.

Postgres hat bereits vergessen, wie dieser Wert eingegeben wurde. Er merkt sich lediglich den Wert und den Datentyp. Genau wie bei einer Dezimalzahl. numeric '003.4', numeric '3.40' oder numeric '+3.4' - alle führen zu genau demselben internen Wert.

AT TIME ZONE

Sobald Sie diese Logik verstanden haben, können Sie alles tun, was Sie wollen. Jetzt fehlt nur noch ein Tool zum Interpretieren oder Darstellen von Zeitstempel-Literalen nach einer bestimmten Zeitzone. Hier kommt das Konstrukt AT TIME ZONE ins Spiel. Es gibt zwei verschiedene Anwendungsfälle. timestamptz wird in timestamp konvertiert und umgekehrt.

Um den UTC timestamptz2012-03-05 17:00:00+0 einzugeben:

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'

So zeigen Sie denselben Zeitpunkt wie EST timestamp (Eastern Standard Time) an:

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

Das ist richtig, AT TIME ZONE 'UTC' zweimal . Der erste interpretiert den Wert timestamp als (angegebenen) UTC-Zeitstempel, der den Typ timestamptz zurückgibt. Die zweite konvertiert die timestamptz in die timestamp in der angegebenen Zeitzone 'EST' - was für eine Uhr in der Zeitzone EST zu diesem einzigartigen Zeitpunkt angezeigt wird.

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 Zeilen mit einer Timestamptz-Spalte zurück, die denselben UTC-Zeitstempel 2012-03-05 17:00:00 enthält. Die 9. Reihe funktioniert zwar in meiner Zeitzone, ist aber eine böse Falle. Siehe unten.

① Zeilen 6 - 8 mit Zeitzone Name und Zeitzone Abkürzung für Hawaii-Zeit unterliegen der Sommerzeit (DST) und können abweichen, obwohl dies derzeit nicht der Fall ist. Ein Zeitzonenname wie 'US/Hawaii' kennt die DST-Regeln und alle historischen Verschiebungen automatisch, während eine Abkürzung wie HST nur ein dummer Code für einen festen Versatz ist. Möglicherweise müssen Sie für die Sommer-/Standardzeit eine andere Abkürzung anhängen. Der name interpretiert den any Zeitstempel in der angegebenen Zeitzone korrekt. Ein Abkürzung ist billig, muss aber für den angegebenen Zeitstempel der richtige sein:

Die Sommerzeit ist nicht die hellste Idee, die die Menschheit jemals hatte.

② Zeile 9, markiert als geladene Fußwaffe funktioniert für mich , aber nur zufällig. Wenn Sie ein Literal explizit in timestamp [without time zone] umwandeln, wird jeder Zeitzonenversatz wird ignoriert ! Es wird nur der reine Zeitstempel verwendet. Der Wert wird dann automatisch auf timestamptz im Beispiel erzwungen, um dem Spaltentyp zu entsprechen. Für diesen Schritt wird die Einstellung timezone der aktuellen Sitzung angenommen, die in meinem Fall dieselbe Zeitzone +1 ist (Europa/Wien). Aber wahrscheinlich nicht in Ihrem Fall - was zu einem anderen Wert führt. Kurz gesagt: Wandeln Sie timestamptz-Literale nicht in timestamp um, da sonst der Zeitzonenoffset verloren geht.

Deine Fragen

Der Benutzer speichert eine Uhrzeit, z. B. am 17. März 2012, um 19 Uhr. Ich möchte nicht, dass Zeitzonen-Konvertierungen oder die Zeitzone gespeichert werden.

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

Ich verwende nur die vom Benutzer angegebene Zeitzone, um Datensätze zu erhalten, die der aktuellen Zeit in der lokalen Zeitzone des Benutzers "vor" oder "nach" liegen.

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

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

Für die Zeit nach der lokalen Uhr:

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

Noch nicht genug von Hintergrundinformationen? Es gibt mehr im Handbuch.

328

Wenn Sie standardmäßig in UTC handeln möchten:

Fügen Sie in config/application.rb Folgendes hinzu:

config.time_zone = 'UTC'

Wenn Sie den aktuellen Namen der Benutzerzeitzone speichern, ist current_user.timezone, können Sie sagen.

post.created_at.in_time_zone(current_user.timezone)

current_user.timezone sollte ein gültiger Name der Zeitzone sein, andernfalls erhalten Sie ArgumentError: Invalid Timezone, siehe vollständige Liste .

0
Dorian