web-dev-qa-db-fra.com

Date () de PostgreSQL avec fuseau horaire

J'ai un problème pour sélectionner correctement les dates dans Postgres - elles sont stockées en UTC, mais ne sont pas converties correctement avec la fonction Date ().

La conversion de l'horodatage en une date me donne une mauvaise date s'il est 16 heures PST.

2012-06-21 devrait être 2012-06-20 dans ce cas.

Le starts_at le type de données de la colonne est timestamp without time zone. Voici mes requêtes:

Sans conversion en fuseau horaire PST:

Select starts_at from schedules where id = 40;

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

La conversion donne ceci:

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

Mais ni convertir à la date correcte dans le fuseau horaire.

42
Matthew Lehner

Je ne vois pas le type exact de starts_at dans votre question. Vous devez vraiment inclure ces informations, c'est la clé de la solution. Je vais devoir deviner.

Fondamentalement, PostgreSQL toujours stocke la valeur de temps UTC pour le type timestamp with time zone en interne. Seul l'affichage varie en fonction de votre paramètre timezone actuel. L'effet de AT TIME ZONE la construction change également avec le type de données sous-jacent. Plus de détails:

Si vous extrayez un date du type timestamp [without time zone] , vous obtenez la date du fuseau horaire actuel. Le jour dans la sortie sera le même que dans l'affichage de la valeur timestamp.

Si vous extrayez un date du type timestamp with time zone (timestamptz pour faire court), le décalage de fuseau horaire est "appliqué" en premier. Vous obtenez toujours la date du fuseau horaire actuel, ce qui correspond à affichage de l'horodatage. Le même moment se traduit au lendemain dans certaines parties de l'Europe, quand il est 16 heures passées. en Californie par exemple. Pour obtenir la date d'un certain fuseau horaire, appliquez AT TIME ZONE première.

Par conséquent, ce que vous décrivez en haut de la question contredit votre exemple.

Étant donné que starts_at est un timestamp [without time zone] et l'heure sur votre serveur est réglée sur l'heure locale. Testez avec:

SELECT now();

Affiche-t-elle la même heure qu'une horloge sur votre mur? Si oui (et que le serveur db fonctionne avec l'heure correcte), le paramètre timezone de votre session en cours correspond à votre fuseau horaire local. Si non, vous souhaiterez peut-être visiter le paramètre de timezone dans votre postgresql.conf ou votre client pour la session. Détails dans le manuel.

Sachez que l'offset timezone utilise le signe opposé de ce qui est affiché dans les littéraux d'horodatage. Voir:

Pour obtenir votre date locale à partir de starts_at juste

SELECT starts_at::date

Équivaut à:

SELECT date(starts_at)

BTW, votre heure locale est en UTC-7 en ce moment, pas en UTC-8, car l'heure d'été est en vigueur (ne fait pas partie des idées les plus brillantes de la race humaine).

Pacific Standard TIME (PST) est normalement 8 heures plus tôt (valeur timestamp plus grande) que UTC (Universal Time Zone), mais pendant les périodes d'heure d'été (comme maintenant), elle peut être de 7 heures. C'est pourquoi timestamptz est affiché comme 2012-06-21 02:00:00-07 dans votre exemple. La construction AT TIME ZONE 'PST' prend en compte l'heure d'été. Ces deux expressions donnent des résultats différents (un en hiver, un en été) et peuvent entraîner des dates différentes lors de la distribution:

SELECT '2012-06-21 01:00:00'::timestamp AT TIME ZONE 'PST'
     , '2012-12-21 01:00:00'::timestamp AT TIME ZONE 'PST'
31
Erwin Brandstetter

Fondamentalement, ce que vous voulez, c'est:

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

J'ai obtenu la solution de cet article ci-dessous, qui est tout simplement GOLD !!! Il explique très clairement ce problème non trivial, lisez-le si vous souhaitez mieux comprendre la gestion de pstgrsql TZ.

Exprimer les horodatages PostgreSQL sans zones dans l'heure locale

Voici ce qui se passe. Tout d'abord, vous devez savoir que le fuseau horaire PST a 8 heures de retard sur le fuseau horaire UTC. Par exemple, le 1er janvier 2014, 4:30 PM PST (mer. 01 janv. 2014 16:00:30 -0800) est équivalent au 2 janvier 2014, 00:30 UTC (jeu., 02 janv. 2014 00:00:30 +0000) .Tout moment après 16h00 PST glisse jusqu'au lendemain, interprété comme UTC.

En outre, comme Erwin Brandstetter l'a mentionné ci-dessus, postresql a deux types de type de données d'horodatage, l'un avec un fuseau horaire et l'autre sans. Si vos horodatages incluent un fuseau horaire, alors un simple:

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

marchera. Cependant, si votre horodatage est sans fuseau horaire, l'exécution de la commande ci-dessus ne fonctionnera pas, et vous devez d'abord convertir votre horodatage sans fuseau horaire en horodatage avec un fuseau horaire, à savoir un fuseau horaire UTC, et SEULEMENT ALORS le convertir en votre 'PST' ou 'US/Pacific '(qui sont les mêmes jusqu'à certains problèmes d'heure d'été. Je pense que vous devriez être bien avec l'un ou l'autre).

Permettez-moi de démontrer avec un exemple où je crée un horodatage sans fuseau horaire. Supposons par commodité que notre fuseau horaire local est bien "PST" (s'il ne l'était pas, il devient un peu plus compliqué, ce qui n'est pas nécessaire dans le cadre de cette explication).

Dis que j'ai:

$ 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

Cela donnera:

"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"

Le dernier horodatage est la raison de toute la confusion concernant la conversion de l'horodatage sans fuseau horaire UTC en 'PST' dans postgresql. Quand on écrit:

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

Nous prenons un horodatage sans fuseau horaire et essayons de le convertir en 'PST TZ (nous supposons indirectement que postgresql comprendra que nous voulons qu'il convertisse l'horodatage à partir d'un UTC TZ, mais postresql a ses propres plans!). En pratique, ce que postgresql fait, c'est qu'il prend l'horodatage sans fuseau horaire ('2014-01-2 00:30:00) et le traite comme s'il était DÉJÀ un horodatage TZ' PST '(c'est-à-dire: 2014-01-2 00:30 : 00 -0800) et le convertit en fuseau horaire UTC !!! Donc, ça pousse 8 heures en avant plutôt qu'en arrière! Ainsi nous obtenons (2014-01-02 08: 30: 00 + 00).

Quoi qu'il en soit, ce dernier comportement (non intuitif) est à l'origine de toute confusion. Lisez l'article si vous voulez une explication plus approfondie, j'ai en fait obtenu des résultats qui sont un peu différents de leur sur cette dernière partie, mais l'idée générale est la même.

31
AmitF

Je sais que c'est un ancien mais vous voudrez peut-être envisager d'utiliser AT TIME ZONE "US/Pacific" lors de la diffusion pour éviter tout problème PST/PDT. Donc

SELECT starts_at :: TIMESTAMPTZ AT FUSEAU HORAIRE "US/Pacific" FROM horaires WHERE ID = '40';

7
John Rennpferd
cast(master.Stamp5DateTime as date) >= '05-05-2019' AND 

cast(master.Stamp5DateTime as date) <= '05-05-2019' 
0
Bhushan Shimpi