sql >> Databáze >  >> RDS >> PostgreSQL

Úplné ignorování časových pásem v Rails a PostgreSQL

Postgres má dva různé typy dat časových razítek:

  • timestamp with time zone , krátký název:timestamptz
  • timestamp without time zone , krátký název:timestamp

timestamptz je preferováno zadejte rodinu data/času, doslova. Má typispreferred nastavit v pg_type , které mohou být relevantní:

  • Generování časových řad mezi dvěma daty v PostgreSQL

Interní úložiště a epocha

Interně časová razítka zabírají 8 bajtů úložiště na disku a v RAM. Je to celočíselná hodnota představující počet mikrosekund z epochy Postgres, 2000-01-01 00:00:00 UTC.

Postgres má také zabudované znalosti o běžně používaném UNIXovém počítání sekund z epochy UNIX, 1970-01-01 00:00:00 UTC, a používá je ve funkcích to_timestamp(double precision) nebo EXTRACT(EPOCH FROM timestamptz) .

Zdrojový kód:

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

A:

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

Mikrosekundové rozlišení se převádí na maximálně 6 desetinných číslic po dobu sekund.

timestamp

Pro timestamp není výslovně uvedeno žádné časové pásmo. Postgres ignoruje jakýkoli modifikátor časového pásma přidaný do vstupního literálu omylem!

Pro zobrazení nejsou posunuty žádné hodiny. Když se vše děje ve stejném časovém pásmu, je to v pořádku. Pro jiné časové pásmo význam změny, ale hodnotu a zobrazit zůstaňte stejní.

timestamptz

Zpracování timestamptz je jemně odlišná. Zde cituji manuál:

Pro timestamp with time zone , interně uložená hodnota je vždy v UTC (Univerzální koordinovaný čas ...)

Odvážný důraz můj. Časové pásmo samotné se nikdy neukládá . Je to vstupní modifikátor používaný k výpočtu příslušné časové značky UTC, která je uložena - nebo a výstupní dekorátor používaný k výpočtu místního času pro zobrazení - s připojeným posunem časového pásma. Pokud nepřipojíte offset pro timestamptz při vstupu se předpokládá aktuální nastavení časového pásma relace. Všechny výpočty se provádějí s hodnotami časového razítka UTC. Pokud (možná) budete muset pracovat s více než jedním časovým pásmem, použijte timestamptz . Jinými slovy:Pokud existují nějaké pochybnosti nebo nedorozumění ohledně předpokládaného časového pásma, použijte timestamptz . Platí ve většině případů použití.

Klientům jako psql nebo pgAdmin nebo jakékoli aplikaci komunikující přes libpq (jako Ruby s drahokamem pg) se zobrazí časové razítko plus posun pro aktuální časové pásmo nebo podle požadavku časové pásmo (viz níže). Je to vždy stejný časový okamžik , liší se pouze formát zobrazení. Nebo, jak to uvádí manuál:

Všechna data a časy s ohledem na časové pásmo jsou interně uloženy v UTC. Jsou převedeny na místní čas v zóně určené TimeZone konfigurační parametr před zobrazením klientovi.

Příklad v psql:

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

Co se tu stalo?
Zvolil jsem libovolné posunutí časového pásma +3 pro vstupní literál. Pro Postgres je to jen jeden z mnoha způsobů, jak zadat časové razítko UTC 2012-03-05 17:00:00 . Výsledek dotazu se zobrazí pro aktuální nastavení časového pásma Vídeň/Rakousko v mém testu, který má offset +1 během zimy a +2 během letního času („letní čas“, DST). Takže 2012-03-05 18:00:00+01 protože DST se spustí až později.

Postgres okamžitě zapomene vstupní literál. Jediné, co si pamatuje, je hodnota pro datový typ. Stejně jako u desetinného čísla. numeric '003.4' nebo numeric '+3.4' - oba mají za následek přesně stejnou vnitřní hodnotu.

AT TIME ZONE

Vše, co nyní chybí, je nástroj pro interpretaci nebo reprezentaci literálů časových razítek podle konkrétního časového pásma. To je místo AT TIME ZONE přichází konstrukt. Existují dva různé případy použití. timestamptz se převede na timestamp a naopak.

Chcete-li zadat UTC timestamptz 2012-03-05 17:00:00+0 :

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

... což je ekvivalentní:

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

Chcete-li zobrazit stejný bod v čase jako EST timestamp (Východní standardní čas):

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

Správně, AT TIME ZONE 'UTC' dvakrát . První interpretuje timestamp hodnotu jako (dané) časové razítko UTC vracející typ timestamptz . Druhý převede timestamptz na timestamp v daném časovém pásmu 'EST' - to, co nástěnné hodiny zobrazují v časovém pásmu EST v tomto okamžiku.

Příklady

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

Vrátí 8 (nebo 9) identické řádky s časovým razítkem sloupce se stejným časovým razítkem UTC 2012-03-05 17:00:00 . 9. řada náhodou funguje v mém časovém pásmu, ale je to zlá past. Viz níže.

① Řádky 6–8 s názvem časového pásma a časové pásmo zkratka pro havajský čas podléhají DST (letní čas) a mohou se lišit, i když ne v současnosti. Název časového pásma jako 'US/Hawaii' zná pravidla DST a všechny historické posuny automaticky, zatímco zkratka jako HST je jen hloupý kód pro pevný offset. Možná budete muset připojit jinou zkratku pro letní / standardní čas. jméno správně interpretuje jakýkoli časové razítko v daném časovém pásmu. zkratka je levný, ale musí být správný pro dané časové razítko:

  • Názvy časových pásem se stejnými vlastnostmi dávají při použití na časové razítko jiný výsledek

Letní čas nepatří mezi ty nejchytřejší nápady, se kterými lidstvo kdy přišlo.

② Řádek 9, označený jako loaded footgun funguje pro mě , ale jen shodou okolností. Pokud do timestamp [without time zone] explicitně přenesete literál , jakýkoli posun časového pásma je ignorován! Používá se pouze holé časové razítko. Hodnota je poté automaticky převedena na timestamptz v příkladu, aby odpovídal typu sloupce. Pro tento krok timezone předpokládá se nastavení aktuální relace, což je shodou okolností stejné časové pásmo +1 v mém případě (Evropa/Vídeň). Ale pravděpodobně ne ve vašem případě - což bude mít za následek jinou hodnotu. Stručně řečeno:Nevysílejte timestamptz literály na timestamp nebo ztratíte posun časového pásma.

Vaše otázky

Uživatel uloží čas, například 17. března 2012, 19:00. Nechci, aby se ukládaly konverze časového pásma nebo časové pásmo.

Samotné časové pásmo se nikdy neukládá. K zadání časového razítka UTC použijte jednu z výše uvedených metod.

K získávání záznamů „před“ nebo „po“ aktuálním čase v místním časovém pásmu uživatele používám pouze uživatele zadané časové pásmo.

Můžete použít jeden dotaz pro všechny klienty v různých časových pásmech.
Pro absolutní globální čas:

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

Pro čas podle místních hodin:

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

Ještě vás nebaví základní informace? Více je v návodu.



  1. Jak použít typ tabulky v příkazu SELECT FROM?

  2. Kontrolní omezení překrývajícího se období

  3. Referenční alias (vypočítaný v SELECT) v klauzuli WHERE

  4. Co je testování databáze a jak jej provádět?