PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Ignorando del tutto i fusi orari in Rails e PostgreSQL

Postgres ha due diversi tipi di dati di timestamp:

  • timestamp with time zone , nome breve:timestamptz
  • timestamp without time zone , nome breve:timestamp

timestamptz è il preferito digitare la famiglia di data/ora, letteralmente. Ha typispreferred impostato in pg_type , che può essere rilevante:

  • Generazione di serie temporali tra due date in PostgreSQL

Memoria interna ed epoca

Internamente, i timestamp occupano 8 byte di memoria su disco e nella RAM. È un valore intero che rappresenta il conteggio dei microsecondi dall'epoca di Postgres, 2000-01-01 00:00:00 UTC.

Postgres ha anche una conoscenza integrata del tempo UNIX comunemente usato che conta i secondi dall'epoca UNIX, 1970-01-01 00:00:00 UTC, e lo usa nelle funzioni to_timestamp(double precision) o EXTRACT(EPOCH FROM timestamptz) .

Il codice sorgente:

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

E:

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

La risoluzione in microsecondi si traduce in un massimo di 6 cifre frazionarie per secondi.

timestamp

Per timestamp nessun fuso orario è fornito in modo esplicito. Postgres ignora qualsiasi modificatore di fuso orario aggiunto per errore al valore letterale di input!

Nessuna ora viene spostata per la visualizzazione. Con tutto ciò che accade nello stesso fuso orario, va bene. Per un fuso orario diverso il significato cambia, ma valore e visualizza rimani lo stesso.

timestamptz

Gestione di timestamptz è sottilmente diverso. Cito il manuale qui:

Per timestamp with time zone , il valore memorizzato internamente è sempre in UTC (Tempo coordinato universale...)

Enfasi in grassetto mio. Il fuso orario stesso non viene mai memorizzato . È un modificatore di input utilizzato per calcolare il timestamp UTC corrispondente, che viene memorizzato - o e il decoratore di output utilizzato per calcolare l'ora locale per la visualizzazione - con l'offset del fuso orario aggiunto. Se non aggiungi un offset per timestamptz all'input si assume l'impostazione del fuso orario corrente della sessione. Tutti i calcoli vengono eseguiti con valori di timestamp UTC. Se (potresti) avere a che fare con più di un fuso orario, usa timestamptz . In altre parole:se possono esserci dubbi o incomprensioni sul fuso orario presunto, vai con timestamptz . Si applica nella maggior parte dei casi d'uso.

I client come psql o pgAdmin o qualsiasi applicazione che comunica tramite libpq (come Ruby con pg gem) vengono presentati con il timestamp più l'offset per il fuso orario corrente o secondo una richiesto fuso orario (vedi sotto). È sempre lo stesso momento , cambia solo il formato di visualizzazione. Oppure, come dice il manuale:

Tutte le date e gli orari in base al fuso orario vengono archiviati internamente in UTC. Vengono convertiti nell'ora locale nella zona specificata da TimeZone parametro di configurazione prima di essere mostrato al client.

Esempio in psql:

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

Che cosa è successo qui?
Ho scelto un fuso orario arbitrario +3 per il letterale di input. Per Postgres, questo è solo uno dei tanti modi per inserire il timestamp UTC 2012-03-05 17:00:00 . Il risultato della query viene visualizzato per l'impostazione del fuso orario corrente Vienna/Austria nel mio test, che ha un offset +1 durante l'inverno e +2 durante l'ora legale ("ora legale", DST). Quindi 2012-03-05 18:00:00+01 poiché l'ora legale entra in vigore solo più tardi.

Postgres dimentica immediatamente il letterale di input. Tutto ciò che ricorda è il valore per il tipo di dati. Proprio come con un numero decimale. numeric '003.4' o numeric '+3.4' - entrambi danno lo stesso identico valore interno.

AT TIME ZONE

Tutto ciò che manca ora è uno strumento per interpretare o rappresentare valori letterali di timestamp in base a un fuso orario specifico. Ecco dove si trova il AT TIME ZONE entra in gioco costrutto. Esistono due diversi casi d'uso. timestamptz viene convertito in timestamp e viceversa.

Per inserire il timestamptz UTC 2012-03-05 17:00:00+0 :

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

... che equivale a:

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

Per visualizzare lo stesso momento di EST timestamp (ora solare orientale):

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

Esatto, AT TIME ZONE 'UTC' due volte . Il primo interpreta il timestamp valore come (dato) timestamp UTC che restituisce il tipo timestamptz . Il secondo converte il timestamptz al timestamp nel fuso orario 'EST' specificato - cosa mostra un orologio da parete nel fuso orario EST in questo momento.

Esempi

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

Restituisce 8 (o 9) identico righe con un timestamptz colonne che contengono lo stesso timestamp UTC 2012-03-05 17:00:00 . La nona fila sembra funzionare nel mio fuso orario, ma è una trappola malvagia. Vedi sotto.

① Righe 6 - 8 con fuso orario nome e fuso orario abbreviazione per le Hawaii l'ora è soggetta all'ora legale (ora legale) e potrebbe differire, sebbene non attualmente. Un nome di fuso orario come 'US/Hawaii' è a conoscenza delle regole dell'ora legale e di tutti i turni storici automaticamente, mentre un'abbreviazione come HST è solo un codice stupido per un offset fisso. Potrebbe essere necessario aggiungere un'abbreviazione diversa per l'ora legale/solare. Il nome interpreta correttamente qualsiasi timestamp nel fuso orario specificato. Un'abbreviazione è economico, ma deve essere quello giusto per il timestamp specificato:

  • I nomi di fuso orario con proprietà identiche producono risultati diversi se applicati al timestamp

L'ora legale non è tra le idee più brillanti che l'umanità abbia mai avuto.

② Riga 9, contrassegnata come pistola carica funziona per me , ma solo per caso. Se trasmetti in modo esplicito un valore letterale a timestamp [without time zone] , qualsiasi differenza di fuso orario viene ignorata! Viene utilizzato solo il timestamp nudo. Il valore viene quindi forzato automaticamente a timestamptz nell'esempio in modo che corrisponda al tipo di colonna. Per questo passaggio, il timezone si presuppone l'impostazione della sessione corrente, che risulta essere lo stesso fuso orario +1 nel mio caso (Europa/Vienna). Ma probabilmente non nel tuo caso, il che risulterà in un valore diverso. In breve:non trasmettere timestamptz letterali a timestamp o perdi la differenza di fuso orario.

Le tue domande

L'utente memorizza un'ora, ad esempio il 17 marzo 2012, alle 19:00. Non voglio che le conversioni del fuso orario o il fuso orario vengano archiviati.

Il fuso orario stesso non viene mai memorizzato. Utilizza uno dei metodi precedenti per inserire un timestamp UTC.

Uso solo il fuso orario specificato dall'utente per ottenere record "prima" o "dopo" l'ora corrente nel fuso orario locale dell'utente.

Puoi utilizzare una query per tutti i client in diversi fusi orari.
Per l'ora globale assoluta:

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

Per l'ora in base all'orologio locale:

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

Non sei ancora stanco delle informazioni di base? C'è altro nel manuale.