Postgres har to forskellige tidsstempeldatatyper:
timestamp with time zone
, kort navn:timestamptz
timestamp without time zone
, kort navn:timestamp
timestamptz
er den foretrukne skriv dato/klokkeslæt-familien bogstaveligt talt. Den har typispreferred
indstillet i pg_type
, hvilket kan være relevant:
- Generering af tidsserier mellem to datoer i PostgreSQL
Intern opbevaring og epoke
Internt optager tidsstempler 8 bytes lagerplads på disk og i RAM. Det er en heltalsværdi, der repræsenterer antallet af mikrosekunder fra Postgres-epoken, 2000-01-01 00:00:00 UTC.
Postgres har også indbygget viden om den almindeligt anvendte UNIX-tidstælling sekunder fra UNIX-epoken, 1970-01-01 00:00:00 UTC, og bruger det i funktionerne to_timestamp(double precision)
eller EXTRACT(EPOCH FROM timestamptz)
.
Kildekoden:
* 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.)
Og:
/* 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) */
Mikrosekundsopløsningen oversættes til maksimalt 6 brøkcifre i sekunder.
timestamp
For timestamp
ingen tidszone er angivet eksplicit. Postgres ignorerer enhver tidszonemodifikator tilføjet til input literal ved en fejltagelse!
Ingen timer flyttes til visning. Med alt, der sker i samme tidszone, er det fint. For en anden tidszone betydningen ændringer, men værdi og visning forbliv det samme.
timestamptz
Håndtering af timestamptz
er subtilt anderledes. Jeg citerer manualen her:
For timestamp with time zone
, er den internt lagrede værdi altid i UTC (Universal koordineret tid ...)
Fed fremhævelse min. Selve tidszonen gemmes aldrig . Det er en input-modifikator, der bruges til at beregne det tilsvarende UTC-tidsstempel, som er lagret - eller og output-dekorator bruges til at beregne den lokale tid til visning - med tilføjet tidszoneforskydning. Hvis du ikke tilføjer en offset for timestamptz
ved input antages den aktuelle tidszoneindstilling for sessionen. Alle beregninger udføres med UTC-tidsstempelværdier. Hvis du (måske) skal håndtere mere end én tidszone, skal du bruge timestamptz
. Med andre ord:Hvis der kan være tvivl eller misforståelser om den antagne tidszone, så gå med timestamptz
. Gælder i de fleste tilfælde.
Klienter som psql eller pgAdmin eller enhver applikation, der kommunikerer via libpq (som Ruby med pg gem) præsenteres med tidsstemplet plus offset for den aktuelle tidszone eller i henhold til en anmodet tidszone (se nedenfor). Det er altid det samme tidspunkt , kun visningsformatet varierer. Eller, som manualen udtrykker det:
Alle tidszone-bevidste datoer og tidspunkter gemmes internt i UTC. De konverteres til lokal tid i den zone, der er angivet af TimeZone
konfigurationsparameter, før den vises til klienten.
Eksempel i psql:
db=# SELECT timestamptz '2012-03-05 20:00+03';
timestamptz
------------------------
2012-03-05 18:00:00+01
Hvad skete der her?
Jeg valgte en vilkårlig tidszoneforskydning +3
for det bogstavelige input. For Postgres er dette blot en af mange måder at indtaste UTC-tidsstemplet 2012-03-05 17:00:00
. Resultatet af forespørgslen vises for den aktuelle tidszoneindstilling Wien/Østrig i min test, som har en offset +1
om vinteren og +2
i sommertid ("sommertid", sommertid). Så 2012-03-05 18:00:00+01
da sommertid først starter senere.
Postgres glemmer det bogstavelige input med det samme. Det eneste, den husker, er værdien for datatypen. Ligesom med et decimaltal. numeric '003.4'
eller numeric '+3.4'
- begge resulterer i nøjagtig samme interne værdi.
AT TIME ZONE
Alt, der mangler nu, er et værktøj til at fortolke eller repræsentere tidsstempler i henhold til en specifik tidszone. Det er her AT TIME ZONE
konstruktion kommer ind. Der er to forskellige use cases. timestamptz
er konverteret til timestamp
og omvendt.
For at indtaste UTC timestamptz
2012-03-05 17:00:00+0
:
SELECT timestamp '2012-03-05 17:00:00' AT TIME ZONE 'UTC'
... hvilket svarer til:
SELECT timestamptz '2012-03-05 17:00:00 UTC'
For at vise det samme tidspunkt som EST timestamp
(Eastern Standard Time):
SELECT timestamp '2012-03-05 17:00:00' AT TIME ZONE 'UTC' AT TIME ZONE 'EST'
Det er rigtigt, AT TIME ZONE 'UTC'
to gange . Den første fortolker timestamp
værdi som (givet) UTC-tidsstempel, der returnerer typen timestamptz
. Den anden konverterer timestamptz
til timestamp
i den givne tidszone 'EST' - hvad et vægur viser i tidszonen EST på dette tidspunkt.
Eksempler
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);
Returnerer 8 (eller 9) identiske rækker med en timestamptz-kolonner med det samme UTC-tidsstempel 2012-03-05 17:00:00
. Den 9. række virker tilfældigvis i min tidszone, men er en ond fælde. Se nedenfor.
① Række 6 - 8 med tidszone navn og tidszone forkortelse for Hawaii er tid underlagt sommertid (sommertid) og kan variere, dog ikke i øjeblikket. Et tidszonenavn som 'US/Hawaii'
er opmærksom på sommertidsregler og alle historiske skift automatisk, mens en forkortelse som HST
er bare en dum kode for en fast offset. Du skal muligvis tilføje en anden forkortelse for sommer-/standardtid. navnet fortolker enhver korrekt tidsstempel i den givne tidszone. En forkortelse er billig, men skal være den rigtige til det givne tidsstempel:
- Tidszonenavne med identiske egenskaber giver forskellige resultater, når de anvendes på tidsstemplet
Sommertid er ikke blandt de smarteste ideer, menneskeheden nogensinde har fundet på.
② Række 9, markeret som ladet fodgevær virker for mig , men kun ved en tilfældighed. Hvis du eksplicit caster et bogstav til timestamp [without time zone]
, enhver tidszoneforskydning ignoreres! Kun det nøgne tidsstempel bruges. Værdien tvinges derefter automatisk til timestamptz
i eksemplet for at matche kolonnetypen. For dette trin, timezone
indstillingen af den aktuelle session antages, hvilket tilfældigvis er den samme tidszone +1
i mit tilfælde (Europa/Wien). Men sandsynligvis ikke i dit tilfælde - hvilket vil resultere i en anden værdi. Kort sagt:Lad være med at caste timestamptz
bogstaver til timestamp
eller du mister tidszoneforskydningen.
Dine spørgsmål
Brugeren gemmer et tidspunkt, f.eks. 17. marts 2012, kl. 19.00. Jeg ønsker ikke, at tidszonekonverteringer eller tidszonen skal gemmes.
Selve tidszonen gemmes aldrig. Brug en af metoderne ovenfor til at indtaste et UTC-tidsstempel.
Jeg bruger kun brugernes angivne tidszone til at få optegnelser 'før' eller 'efter' den aktuelle tid i brugerens lokale tidszone.
Du kan bruge én forespørgsel for alle klienter i forskellige tidszoner.
For absolut global tid:
SELECT * FROM tbl WHERE time_col > (now() AT TIME ZONE 'UTC')::time
For tid ifølge det lokale ur:
SELECT * FROM tbl WHERE time_col > now()::time
Er du ikke træt af baggrundsinformation, endnu? Der er mere i manualen.