Ten eerste is de tijdafhandeling en rekenkunde van PostgreSQL fantastisch en is optie 3 in het algemeen prima. Het is echter een onvolledige weergave van tijd en tijdzones en kan worden aangevuld:
- Sla de naam van de tijdzone van een gebruiker op als gebruikersvoorkeur (bijv.
America/Los_Angeles
, niet-0700
). - Laat gebruikersgebeurtenissen/tijdgegevens lokaal indienen bij hun referentiekader (hoogstwaarschijnlijk een offset van UTC, zoals
-0700
). - Converteer in de toepassing de tijd naar
UTC
en opgeslagen met behulp van eenTIMESTAMP WITH TIME ZONE
kolom. - Retourtijdverzoeken lokaal naar de tijdzone van een gebruiker (d.w.z. converteren van
UTC
naarAmerica/Los_Angeles
). - Stel de
timezone
van uw database in naarUTC
.
Deze optie werkt niet altijd omdat het moeilijk kan zijn om de tijdzone van een gebruiker te achterhalen en vandaar het hedge-advies om TIMESTAMP WITH TIME ZONE
te gebruiken. voor lichtgewicht toepassingen. Dat gezegd hebbende, laat me wat achtergrondaspecten van deze optie 4 nader toelichten.
Net als optie 3, de reden voor de WITH TIME ZONE
is omdat het tijdstip waarop iets gebeurde een absoluut . is moment in de tijd. WITHOUT TIME ZONE
levert een relatief op tijdzone. Meng nooit, maar dan ook nooit absolute en relatieve TIMESTAMPs.
Zorg er vanuit een programmatisch en consistentieperspectief voor dat alle berekeningen worden gemaakt met UTC als tijdzone. Dit is geen PostgreSQL-vereiste, maar het helpt bij de integratie met andere programmeertalen of -omgevingen. Een CHECK
instellen op de kolom om er zeker van te zijn dat het schrijven naar de tijdstempelkolom een tijdzone-offset heeft van 0
is een defensieve positie die een paar klassen bugs voorkomt (bijvoorbeeld een script dumpt gegevens naar een bestand en iets anders sorteert de tijdgegevens met behulp van een lexicale sortering). Nogmaals, PostgreSQL heeft dit niet nodig om datumberekeningen correct uit te voeren of om tussen tijdzones te converteren (d.w.z. PostgreSQL is zeer bedreven in het converteren van tijden tussen twee willekeurige tijdzones). Om ervoor te zorgen dat gegevens die naar de database gaan, worden opgeslagen met een offset van nul:
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
Het is niet 100% perfect, maar het biedt een sterk genoeg anti-footshooting-maatregel die ervoor zorgt dat de gegevens al worden geconverteerd naar UTC. Er zijn veel meningen over hoe dit te doen, maar dit lijkt in de praktijk het beste te zijn vanuit mijn ervaring.
Kritiek op het afhandelen van de tijdzone van de database is grotendeels terecht (er zijn tal van databases die dit met grote incompetentie behandelen), maar PostgreSQL's verwerking van tijdstempels en tijdzones is behoorlijk geweldig (ondanks een paar "functies" hier en daar). Een voorbeeld van zo'n functie:
-- 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)
Merk op dat AT TIME ZONE 'UTC'
stript tijdzone-info en maakt een relatieve TIMESTAMP WITHOUT TIME ZONE
met behulp van het referentiekader van uw doelwit (UTC
).
Bij het converteren van een onvolledige TIMESTAMP WITHOUT TIME ZONE
naar een TIMESTAMP WITH TIME ZONE
, de ontbrekende tijdzone is overgenomen van uw verbinding:
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)
Het komt erop neer:
- de tijdzone van een gebruiker opslaan als een benoemd label (bijv.
America/Los_Angeles
) en geen offset van UTC (bijv.-0700
) - gebruik UTC voor alles, tenzij er een dwingende reden is om een offset die niet nul is op te slaan
- behandel alle UTC-tijden die niet nul zijn als invoerfout
- mix en match nooit relatieve en absolute tijdstempels
- gebruik ook
UTC
als detimezone
indien mogelijk in de database
Willekeurige programmeertaal opmerking:Python's datetime
datatype is erg goed in het handhaven van het onderscheid tussen absolute versus relatieve tijden (hoewel in het begin frustrerend totdat je het aanvult met een bibliotheek zoals PyTZ).
BEWERKEN
Laat me het verschil tussen relatief en absoluut wat meer uitleggen.
Absolute tijd wordt gebruikt om een gebeurtenis vast te leggen. Voorbeelden:"Gebruiker 123 ingelogd" of "een diploma-uitreiking begint op 28-05-2011 14:00 uur PST." Ongeacht je lokale tijdzone, als je zou kunnen teleporteren naar waar de gebeurtenis plaatsvond, zou je getuige kunnen zijn van de gebeurtenis. De meeste tijdgegevens in een database zijn absoluut (en zouden daarom TIMESTAMP WITH TIME ZONE
moeten zijn , idealiter met een +0 offset en een tekstlabel dat de regels voor de specifieke tijdzone aangeeft - geen offset).
Een relatieve gebeurtenis zou zijn om de tijd van iets vast te leggen of te plannen vanuit het perspectief van een nog nader te bepalen tijdzone. Voorbeelden:"de deuren van ons bedrijf openen om 8.00 uur en sluiten om 21.00 uur", "laten we elkaar elke maandag om 7.00 uur ontmoeten voor een wekelijkse ontbijtbijeenkomst" of "elke Halloween om 20.00 uur." Over het algemeen wordt relatieve tijd gebruikt in een sjabloon of fabriek voor evenementen, en absolute tijd wordt gebruikt voor bijna al het andere. Er is één zeldzame uitzondering die het vermelden waard is en die de waarde van relatieve tijden zou moeten illustreren. Gebruik een relatieve tijdstempel voor toekomstige gebeurtenissen die ver genoeg in de toekomst liggen en waar er onzekerheid kan bestaan over het absolute tijdstip waarop iets zou kunnen gebeuren. Hier is een voorbeeld uit de praktijk:
Stel dat het het jaar 2004 is en u een levering moet plannen op 31 oktober 2008 om 13.00 uur aan de westkust van de VS (d.w.z. America/Los_Angeles
/PST8PDT
). Als je dat hebt opgeslagen met absolute tijd met behulp van ’2008-10-31 21:00:00.000000+00’::TIMESTAMP WITH TIME ZONE
, zou de levering om 14.00 uur zijn gekomen omdat de Amerikaanse regering de Energy Policy Act van 2005 heeft aangenomen die de regels voor zomertijd heeft gewijzigd. In 2004 toen de levering was gepland, de datum 10-31-2008
zou Pacific Standard Time zijn geweest (+8000
), maar vanaf het jaar 2005+ herkenden tijdzonedatabases dat 10-31-2008
zou Pacific Daylight Savings-tijd zijn geweest (+0700
). Het opslaan van een relatief tijdstempel met de tijdzone zou hebben geleid tot een correct leveringsschema, omdat een relatief tijdstempel immuun is voor slecht geïnformeerde manipulatie door het Congres. Waar de grens tussen het gebruik van relatieve versus absolute tijden voor het plannen van dingen is, is een vage lijn, maar mijn vuistregel is dat planning voor alles in de toekomst verder dan 3-6 maanden gebruik moet maken van relatieve tijdstempels (gepland =absoluut versus gepland =relatief ???).
Het andere/laatste type relatieve tijd is de INTERVAL
. Voorbeeld:"de sessie verloopt 20 minuten nadat een gebruiker zich heeft aangemeld". Een INTERVAL
kan correct worden gebruikt met ofwel absolute tijdstempels (TIMESTAMP WITH TIME ZONE
) of relatieve tijdstempels (TIMESTAMP WITHOUT TIME ZONE
). Het is evengoed correct om te zeggen:"een gebruikerssessie verloopt 20 minuten na een succesvolle login (login_utc + session_duration)" of "onze ochtendontbijtvergadering kan slechts 60 minuten duren (recurring_start_time + meeting_length)".
Laatste stukjes verwarring:DATE
, TIME
, TIME WITHOUT TIME ZONE
en TIME WITH TIME ZONE
zijn allemaal relatieve gegevenstypen. Bijvoorbeeld:'2011-05-28'::DATE
staat voor een relatieve datum aangezien u geen tijdzone-informatie heeft die kan worden gebruikt om middernacht te identificeren. Evenzo '23:23:59'::TIME
is relatief omdat je de tijdzone of de DATE
. niet kent vertegenwoordigd door de tijd. Zelfs met '23:59:59-07'::TIME WITH TIME ZONE
, je weet niet wat de DATE
zou zijn. En tot slot, DATE
met een tijdzone is in feite geen DATE
, het is een 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)
Datums en tijdzones in databases plaatsen is een goede zaak, maar het is gemakkelijk om subtiel onjuiste resultaten te krijgen. Er is minimale extra inspanning nodig om tijdinformatie correct en volledig op te slaan, maar dat betekent niet dat de extra inspanning altijd nodig is.