sql >> Database >  >> RDS >> PostgreSQL

Tijdzones volledig negeren in Rails en PostgreSQL

Postgres heeft twee verschillende soorten tijdstempelgegevens:

  • timestamp with time zone , korte naam:timestamptz
  • timestamp without time zone , korte naam:timestamp

timestamptz is de voorkeur typ de datum/tijd-familie letterlijk in. Het heeft typispreferred ingesteld in pg_type , wat relevant kan zijn:

  • Tijdreeksen genereren tussen twee datums in PostgreSQL

Interne opslag en tijdperk

Intern bezetten tijdstempels 8 bytes van opslag op schijf en in RAM. Het is een geheel getal dat het aantal microseconden vertegenwoordigt vanaf het Postgres-tijdperk, 2000-01-01 00:00:00 UTC.

Postgres heeft ook ingebouwde kennis van de veelgebruikte UNIX-tijdtellingsseconden uit het UNIX-tijdperk, 1970-01-01 00:00:00 UTC, en gebruikt die in functies to_timestamp(double precision) of EXTRACT(EPOCH FROM timestamptz) .

De broncode:

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

En:

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

De resolutie van microseconden vertaalt zich naar maximaal 6 fractionele cijfers voor seconden.

timestamp

Voor timestamp er wordt niet expliciet een tijdzone opgegeven. Postgres negert elke tijdzone-modifier per ongeluk aan de invoer toegevoegd!

Er worden geen uren verschoven voor weergave. Met alles in dezelfde tijdzone is dit prima. Voor een andere tijdzone de betekenis veranderingen, maar waarde en weergeven blijf hetzelfde.

timestamptz

Afhandeling van timestamptz is subtiel anders. Ik citeer hier de handleiding:

Voor timestamp with time zone , de intern opgeslagen waarde is altijd in UTC (Universele gecoördineerde tijd ...)

Vetgedrukte nadruk van mij. De tijdzone zelf wordt nooit opgeslagen . Het is een invoermodificator die wordt gebruikt om de overeenkomstige UTC-tijdstempel te berekenen, die wordt opgeslagen - of een uitvoerdecorator die wordt gebruikt om de lokale tijd voor weergave te berekenen - met een toegevoegde tijdzone-offset. Als u geen offset toevoegt voor timestamptz bij invoer wordt uitgegaan van de huidige tijdzone-instelling van de sessie. Alle berekeningen worden gedaan met UTC-tijdstempelwaarden. Als u (mogelijk) te maken krijgt met meer dan één tijdzone, gebruik dan timestamptz . Met andere woorden:als er enige twijfel of misverstand kan bestaan ​​over de veronderstelde tijdzone, gebruik dan timestamptz . Geldt in de meeste gevallen.

Clients zoals psql of pgAdmin of een applicatie die communiceert via libpq (zoals Ruby met de pg gem) krijgen de tijdstempel plus offset voor de huidige tijdzone te zien. of volgens een gevraagde tijdzone (zie hieronder). Het is altijd hetzelfde tijdstip , alleen het weergaveformaat varieert. Of, zoals de handleiding het stelt:

Alle tijdzonebewuste datums en tijden worden intern in UTC opgeslagen. Ze worden geconverteerd naar lokale tijd in de zone gespecificeerd door de TimeZone configuratieparameter voordat deze aan de klant wordt weergegeven.

Voorbeeld in psql:

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

Wat is hier gebeurd?
Ik heb een willekeurige tijdzone gekozen +3 voor de invoer letterlijk. Voor Postgres is dit slechts een van de vele manieren om het UTC-tijdstempel in te voeren 2012-03-05 17:00:00 . Het resultaat van de zoekopdracht wordt weergegeven voor de huidige tijdzone-instelling Wenen/Oostenrijk in mijn test, die een offset heeft +1 tijdens de winter en +2 tijdens de zomertijd ("zomertijd", DST). Dus 2012-03-05 18:00:00+01 aangezien de zomertijd pas later begint.

Postgres vergeet de letterlijke invoer onmiddellijk. Het enige dat het onthoudt, is de waarde voor het gegevenstype. Net als bij een decimaal getal. numeric '003.4' of numeric '+3.4' - beide resulteren in exact dezelfde interne waarde.

AT TIME ZONE

Het enige dat nu nog ontbreekt, is een hulpmiddel om letterlijke tijdstempels te interpreteren of weer te geven volgens een specifieke tijdzone. Dat is waar de AT TIME ZONE construct komt binnen. Er zijn twee verschillende use-cases. timestamptz wordt geconverteerd naar timestamp en vice versa.

Om de UTC timestamptz in te voeren 2012-03-05 17:00:00+0 :

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

... wat gelijk is aan:

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

Om hetzelfde tijdstip weer te geven als EST timestamp (Eastern Standard Time):

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

Dat klopt, AT TIME ZONE 'UTC' tweemaal . De eerste interpreteert de timestamp waarde als (gegeven) UTC-tijdstempel en retourneert het type timestamptz . De tweede converteert de timestamptz naar de timestamp in de gegeven tijdzone 'EST' - wat een wandklok op dit moment in de tijdzone EST weergeeft.

Voorbeelden

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

Retourneert 8 (of 9) identiek rijen met een timestamptz kolommen met hetzelfde UTC-tijdstempel 2012-03-05 17:00:00 . De 9e rij werkt toevallig in mijn tijdzone, maar is een slechte valstrik. Zie hieronder.

① Rijen 6 - 8 met tijdzone naam en tijdzone afkorting voor Hawaï is de tijd onderhevig aan DST (zomertijd) en kan verschillen, maar momenteel niet. Een tijdzonenaam zoals 'US/Hawaii' is automatisch op de hoogte van DST-regels en alle historische verschuivingen, terwijl een afkorting zoals HST is gewoon een domme code voor een vaste offset. Het kan zijn dat u een andere afkorting voor zomer-/standaardtijd moet toevoegen. De naam interpreteert elke correct tijdstempel in de opgegeven tijdzone. Een afkorting is goedkoop, maar moet de juiste zijn voor de gegeven tijdstempel:

  • Tijdzonenamen met identieke eigenschappen leveren een ander resultaat op wanneer toegepast op tijdstempel

Zomertijd is niet een van de slimste ideeën die de mensheid ooit heeft bedacht.

② Rij 9, gemarkeerd als geladen footgun werkt voor mij , maar alleen bij toeval. Als je expliciet een letterlijke cast naar timestamp [without time zone] , wordt elke tijdzone-offset genegeerd! Alleen de kale tijdstempel wordt gebruikt. De waarde wordt dan automatisch gedwongen tot timestamptz in het voorbeeld om overeen te komen met het kolomtype. Voor deze stap is de timezone instelling van de huidige sessie wordt aangenomen, wat toevallig dezelfde tijdzone is +1 in mijn geval (Europa/Wenen). Maar waarschijnlijk niet in uw geval - wat resulteert in een andere waarde. Kortom:gebruik geen timestamptz letterlijke naar timestamp of je verliest de tijdzone-offset.

Uw vragen

Gebruiker slaat een tijd op, bijvoorbeeld 17 maart 2012, 19:00 uur. Ik wil niet dat tijdzoneconversies of de tijdzone worden opgeslagen.

Tijdzone zelf wordt nooit opgeslagen. Gebruik een van de bovenstaande methoden om een ​​UTC-tijdstempel in te voeren.

Ik gebruik alleen de door de gebruiker opgegeven tijdzone om records 'voor' of 'na' de huidige tijd in de lokale tijdzone van de gebruiker te krijgen.

U kunt één zoekopdracht gebruiken voor alle klanten in verschillende tijdzones.
Voor absolute globale tijd:

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

Voor tijd volgens de lokale klok:

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

Ben je de achtergrondinformatie nog niet beu? Er staat meer in de handleiding.



  1. ADDDATE() Voorbeelden – MySQL

  2. MySQL COS() Functie – Retourneer de cosinus van een getal in MySQL

  3. goede postgresql-client voor Windows?

  4. bulksgewijs invoegen van Java in Oracle