29

Ho riscontrato un problema durante la selezione delle date correttamente da Postgres: vengono archiviate in UTC, ma non viene convertito correttamente con la funzione Date().Data PostgreSQL() con fuso orario

Convertire la data/ora in una data mi dà la data sbagliata se è passato alle 16:00 PST.

2012-06-21 in questo caso deve essere 2012-06-20.

Il tipo di dati della colonna starts_at è timestamp without time zone. Qui sono le mie domande:

senza convertire a PST fuso orario:

Select starts_at from schedules where id = 40; 

     starts_at  
--------------------- 
2012-06-21 01:00:00 

Conversione dà questo:

Select (starts_at at time zone 'pst') from schedules where id = 40; 
     timezone   
------------------------ 
2012-06-21 02:00:00-07 

Ma né convertire la data corretta nel fuso orario.

risposta

22

Non vedo il esatto tipo di starts_at nella tua domanda. Dovresti davvero includere queste informazioni, è la chiave della soluzione. Dovrò indovinare.

Fondamentalmente, PostgreSQL sempre memorizza internamente il valore di ora UTC per il tipo timestamp with time zone. Solo il display varia a seconda dell'impostazione attuale di timezone. Anche l'effetto del costrutto AT TIME ZONE cambia con il tipo di dati sottostante. Maggiori dettagli in this related answer.

Se si estrae un date dal tipo timestamp [without time zone], si ottiene la data per il fuso orario corrente. Il giorno nell'output sarà uguale a quello del display del valore timestamp.

Se si estrae un date dal tipo timestamp with time zone (timestamptz in breve), l'offset del fuso orario viene "applicato" per primo. Hai ancora la data per il fuso orario corrente, che è d'accordo con il display del timestamp. Lo stesso punto nel tempo si traduce in un giorno successivo in alcune parti dell'Europa, quando sono passate le 4 del pomeriggio. per esempio in California. Per ottenere la data per un determinato fuso orario, applicare prima AT TIME ZONE.

Pertanto, ciò che descrivi in ​​cima alla domanda contraddice il tuo esempio.

Dato che starts_at è un timestamp [without time zone] e l'ora del server è impostato per l'ora locale. Test con:

SELECT now(); 

Esiste lo stesso tempo di un orologio sul muro? In caso affermativo, l'impostazione timezone della sessione corrente concorda con il fuso orario locale. In caso contrario, è possibile visitare l'impostazione di timezone nel proprio postgresql.conf. O forse il tuo cliente fa qualcosa per l'impostazione? (Può essere impostato per sessione.) Details in the manual.

Per ottenere la data locale da starts_at solo

SELECT starts_at::date 

equivale a:

SELECT date(starts_at) 

BTW, l'ora locale è a UTC-7 in questo momento, non UTC-8, a causa è in vigore l'ora legale (non tra le idee più luminose della razza umana).

Pacific Standard TIME (PST) è normalmente 8 ore prima di UTC (fuso orario universale), ma durante i periodi di ora legale (come ora) sono 7 ore. Ecco perché timestamptz viene visualizzato come 2012-06-21 02:00:00-07 nell'esempio. Il costrutto AT TIME ZONE 'PST' tiene conto dell'ora legale. Queste due espressioni producono risultati diversi (una in inverno, uno in estate) e può causare date diverse, quando Cast:

SELECT '2012-06-21 01:00:00'::timestamp AT TIME ZONE 'PST' 
     ,'2012-12-21 01:00:00'::timestamp AT TIME ZONE 'PST' 
+0

Questo è un po 'corretto -' PST 'non tiene conto dell'ora legale, vedere la risposta di John Rennpferd sull'uso di "US/Pacific" vs "PST" o "PDT" –

6

So che questo è un vecchio ma si consiglia di considerare l'utilizzo AT TIME ZONE "US/Pacific "durante la trasmissione per evitare problemi PST/PDT. Quindi

SELECT starts_at :: TIMESTAMPTZ AL FUSO ORARIO "USA/Pacifico" DALLE STANZE WHERE ID = '40';

+0

Questo è assolutamente corretto se avete bisogno di date storiche per rappresentare il tempo reale in cui sono accaduti. –

19

In sostanza ciò che si vuole è:

$ select starts_at AT TIME ZONE 'UTC' AT TIME ZONE 'US/Pacific' from schedules where id = 40 

ho avuto la soluzione da questo articolo è al di sotto, che è ORO dritto !!! Spiega questo problema non banale molto chiaramente, dargli una lettura se desideri comprendere meglio la gestione di TST pstgrsql.

Expressing PostgreSQL timestamps without zones in local time

Ecco che cosa sta succedendo. Per prima cosa dovresti sapere che "il fuso orario PST è di 8 ore rispetto al fuso orario UTC, quindi ad esempio 1 gen 2014, 16:30 PST (mer, 01 gen 2014 16:00:30 -0800) equivale al 2 gen 2014, 00:30 AM UTC (Gio, 02 gen 2014 00:00:30 +0000). In qualsiasi momento dopo le 16:00 nel PST si passa al giorno successivo, interpretato come UTC.

Inoltre, come menzionato sopra Erwin Brandstetter, postresql ha due tipi di dati di tipo data/ora, uno con un fuso orario e uno senza. Se i timestamp include un fuso orario, poi un semplice:

$ select starts_at AT TIME ZONE 'US/Pacific' from schedules where id = 40 

funzionerà. Tuttavia se il tuo timestamp è timezoneless, l'esecuzione del comando precedente non funzionerà, e devi IN PRIMO LUOGO convertire il timestamp timezoneless in un timestamp con un fuso orario, cioè un fuso orario UTC, e SOLO ALLORA convertirlo nel PST desiderato o in US/Pacific '(che sono gli stessi per alcuni problemi di ora legale. Penso che dovresti stare bene con entrambi).

Consentitemi di dimostrare con un esempio in cui creo un timestamp timezoneless. Supponiamo per comodità che il nostro fuso orario locale sia effettivamente 'PST' (se non lo fosse, diventa un po 'più complicato che non è necessario ai fini di questa spiegazione).

Dire che ho:

$ select timestamp '2014-01-2 00:30:00' AS a, timestamp '2014-01-2 00:30:00' AT TIME ZONE 'UTC' AS b, timestamp '2014-01-2 00:30:00' AT TIME ZONE 'UTC' AT TIME ZONE 'PST' AS c, timestamp '2014-01-2 00:30:00' AT TIME ZONE 'PST' AS d 

Questo produrrà:

"a"=>"2014-01-02 00:30:00" (This is the timezoneless timestamp) 
"b"=>"2014-01-02 00:30:00+00" (This is the UTC TZ timestamp, note that up to a timezone, it is equivalent to the timezoneless one) 
"c"=>"2014-01-01 16:30:00" (This is the correct 'PST' TZ conversion of the UTC timezone, if you read the documentation postgresql will not print the actual TZ for this conversion) 
"d"=>"2014-01-02 08:30:00+00" 

L'ultimo timestamp è la ragione di tutto la confusione per quanto riguarda la conversione timezoneless timestamp dal UTC a 'PST' in PostgreSQL. Quando scriviamo:

timestamp '2014-01-2 00:30:00' AT TIME ZONE 'PST' AS d 

stiamo facendo un timestamp timezoneless e cercare di convertirlo in 'PST TZ (abbiamo indirettamente supporre che PostgreSQL capirà che vogliamo convertire il timestamp da un'UTC TZ, ma PostreSQL ha piani a sé!). In pratica, ciò che postgresql fa è che prende il timestamp timezoneless ('2014-01-2 00:30:00) e lo tratta come se fosse GIÀ un timestamp' PST 'TZ (es .: 2014-01-2 00:30 : 00 -0800) e lo converte in fuso orario UTC !!! Quindi in realtà lo spinge 8 ore prima anziché indietro! Così otteniamo (2014-01-02 08: 30: 00 + 00).

In ogni caso, questo ultimo (non intuitivo) comportamento è la causa di ogni confusione. Leggi l'articolo se vuoi una spiegazione più approfondita, in realtà ho ottenuto risultati un po 'diversi da quelli relativi a quest'ultima parte, ma l'idea generale è la stessa.

+0

Grazie per questa spiegazione dettagliata. Ero in perdita totale prima di leggere questo! –