sql >> Databasteknik >  >> RDS >> PostgreSQL

Ignorerar tidszoner helt och hållet i Rails och PostgreSQL

Postgres har två olika tidsstämpeldatatyper:

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

timestamptz är den föredragna skriv in datum/tid-familjen, bokstavligen. Den har typispreferred ställs in i pg_type , vilket kan vara relevant:

  • Genererar tidsserier mellan två datum i PostgreSQL

Intern lagring och epok

Internt upptar tidsstämplar 8 byte lagringsutrymme på disk och i RAM. Det är ett heltalsvärde som representerar antalet mikrosekunder från Postgres-epoken, 2000-01-01 00:00:00 UTC.

Postgres har också inbyggd kunskap om den vanliga UNIX-tidsräkningssekunderna från UNIX-epoken, 1970-01-01 00:00:00 UTC, och använder det i funktionerna to_timestamp(double precision) eller EXTRACT(EPOCH FROM timestamptz) .

Källkoden:

* 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.)

Och:

/* 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) */  

Mikrosekundsupplösningen översätts till maximalt 6 bråksiffror i sekunder.

timestamp

För timestamp ingen tidszon anges uttryckligen. Postgres ignorerar någon tidszonsmodifierare som av misstag har lagts till i inmatningen!

Inga timmar förskjuts för visning. Med allt som händer i samma tidszon är det bra. För en annan tidszon innebörden ändringar, men värde och visa förbli densamma.

timestamptz

Hantering av timestamptz är subtilt annorlunda. Jag citerar manualen här:

För timestamp with time zone , är det internt lagrade värdet alltid i UTC (Universal Coordinated Time ...)

Djärv betoning min. Själva tidszonen lagras aldrig . Det är en ingångsmodifierare som används för att beräkna enligt UTC-tidsstämpeln, som lagras - eller och utgångsdekoratorn används för att beräkna den lokala tiden för visning - med bifogad tidszonförskjutning. Om du inte lägger till en offset för timestamptz vid inmatning antas den aktuella tidszonsinställningen för sessionen. Alla beräkningar görs med UTC-tidsstämpelvärden. Om du (kan) behöva hantera mer än en tidszon, använd timestamptz . Med andra ord:Om det kan finnas några tvivel eller missförstånd om den antagna tidszonen, gå till timestamptz . Gäller i de flesta användningsfall.

Klienter som psql eller pgAdmin eller andra program som kommunicerar via libpq (som Ruby med pg gem) presenteras med tidsstämpeln plus offset för aktuell tidszon eller enligt en begäran tidszon (se nedan). Det är alltid samma tidpunkt , endast visningsformatet varierar. Eller, som bruksanvisningen uttrycker det:

Alla tidszonsmedvetna datum och tider lagras internt i UTC. De konverteras till lokal tid i zonen som anges av TimeZone konfigurationsparameter innan den visas för klienten.

Exempel i psql:

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

Vad hände här?
Jag valde en godtycklig tidszonförskjutning +3 för ingången bokstavlig. För Postgres är detta bara ett av många sätt att mata in UTC-tidsstämpeln 2012-03-05 17:00:00 . Resultatet av frågan visas för den aktuella tidszonsinställningen Wien/Österrike i mitt test, som har en offset +1 under vintern och +2 under sommartid ("sommartid", sommartid). Så 2012-03-05 18:00:00+01 eftersom sommartid kommer in först senare.

Postgres glömmer ingången bokstavligt omedelbart. Allt den kommer ihåg är värdet för datatypen. Precis som med ett decimaltal. numeric '003.4' eller numeric '+3.4' - båda resulterar i exakt samma interna värde.

AT TIME ZONE

Allt som saknas nu är ett verktyg för att tolka eller representera tidsstämpelns bokstavliga ord enligt en specifik tidszon. Det är där AT TIME ZONE konstruktionen kommer in. Det finns två olika användningsfall. timestamptz konverteras till timestamp och vice versa.

För att ange UTC timestamptz 2012-03-05 17:00:00+0 :

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

... vilket motsvarar:

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

För att visa samma tidpunkt som EST timestamp (Eastern Standard Time):

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

Det stämmer, AT TIME ZONE 'UTC' två gånger . Den första tolkar timestamp värde som (givet) UTC-tidsstämpel som returnerar typen timestamptz . Den andra konverterar timestamptz till timestamp i den givna tidszonen 'EST' - vad en väggklocka visar i tidszonen EST vid denna tidpunkt.

Exempel

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);

Returnerar 8 (eller 9) identiska rader med en tidsstämpeltz-kolumn som innehåller samma UTC-tidsstämpel 2012-03-05 17:00:00 . Den nionde raden råkar fungera i min tidszon, men är en ond fälla. Se nedan.

① Rad 6 - 8 med tidszon namn och tidszon förkortning för Hawaii gäller tid sommartid (sommartid) och kan skilja sig åt, men inte för närvarande. Ett tidszonsnamn som 'US/Hawaii' är medveten om sommartidsregler och alla historiska skift automatiskt, medan en förkortning som HST är bara en dum kod för en fast offset. Du kan behöva lägga till en annan förkortning för sommar-/normaltid. namnet tolkar alla korrekt tidsstämpel vid den givna tidszonen. En förkortning är billig, men måste vara den rätta för den givna tidsstämpeln:

  • Tidszonsnamn med identiska egenskaper ger olika resultat när de tillämpas på tidsstämpel

Sommartid är inte bland de smartaste idéer som mänskligheten någonsin kommit på.

② Rad 9, markerad som laddat fotgevär fungerar för mig , men bara av en slump. Om du uttryckligen castar en bokstavlig till timestamp [without time zone] , alla tidszonförskjutningar ignoreras! Endast den blotta tidsstämpeln används. Värdet tvingas sedan automatiskt till timestamptz i exemplet för att matcha kolumntypen. För detta steg, timezone inställningen för den aktuella sessionen antas, vilket råkar vara samma tidszon +1 i mitt fall (Europa/Wien). Men förmodligen inte i ditt fall - vilket kommer att resultera i ett annat värde. Kort sagt:Casta inte timestamptz bokstaver till timestamp eller så förlorar du tidszonförskjutningen.

Dina frågor

Användaren lagrar en tid, t.ex. 17 mars 2012, kl. Jag vill inte att tidszonomvandlingar eller tidszonen ska lagras.

Själva tidszonen lagras aldrig. Använd en av metoderna ovan för att ange en UTC-tidsstämpel.

Jag använder bara användarens angivna tidszon för att få poster "före" eller "efter" den aktuella tiden i användarens lokala tidszon.

Du kan använda en fråga för alla klienter i olika tidszoner.
För absolut global tid:

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

För tid enligt den lokala klockan:

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

Har du inte tröttnat på bakgrundsinformation ännu? Det finns mer i manualen.



  1. SQL Server 2016:Frågedesigner

  2. SQL där joined set måste innehålla alla värden men kan innehålla fler

  3. Hur säkerhetskopierar och återställer man en databas som en kopia på samma server?

  4. Hur man beställer efter datum i SQLite