web-dev-qa-db-fra.com

PostgreSQL ne convertit pas correctement l'horodatage sans fuseau horaire en horodatage avec fuseau horaire

J'ai fait face au problème suivant ce matin:

select '2011-12-30 00:30:00'::timestamp without time zone AT TIME ZONE 'EST5EDT';

me renvoie 2011-12-30 05:30:00+00 la sorcière a tort.

Mais les prochaines requêtes ci-dessous:

select '2011-12-30 00:30:00'::timestamp without time zone AT TIME ZONE 'UTC-5';
select '2011-12-30 00:30:00' AT TIME ZONE 'EST5EDT';

je vois la bonne date 2011-12-29 19:30:00

Empêcher votre question sur mon fuseau horaire local:

SELECT  current_setting('TIMEZONE');
current_setting
-----------------
     UTC
(1 row)

Quelqu'un a-t-il répondu pourquoi postgresql convertit timestamp without time zone d'une manière étrange et en prenant à la place 5 heures, il ajoute à la place?

47
saicheg

Éléments clés à comprendre

timestamp without time zone AT TIME ZONE réinterprète un timestamp comme étant dans ce fuseau horaire pour le but de le convertir en UTC .

timestamp with time zone AT TIME ZONE convertit un timestamptz en timestamp au fuseau horaire spécifié.

PostgreSQL utilise des fuseaux horaires ISO-8601, qui spécifient que l'est de Greenwich est positif ... sauf si vous utilisez un spécificateur de fuseau horaire POSIX, auquel cas il suit POSIX. La folie s'ensuit.

Pourquoi le premier produit un résultat inattendu

Les horodatages et les fuseaux horaires dans SQL sont horribles. Cette:

select '2011-12-30 00:30:00'::timestamp without time zone AT TIME ZONE 'EST5EDT';

interprète le littéral de type inconnu '2011-12-30 00:30:00' comme timestamp without time zone, ce que Pg suppose être dans la TimeZone locale, sauf indication contraire. Lorsque vous utilisez AT TIME ZONE, Il est (selon la spécification) réinterprété en tant que timestamp with time zone Dans le fuseau horaire EST5EDT Puis stocké en temps absolu en UTC - il est donc converti à partir de EST5EDT à UTC, c'est-à-dire que le décalage de fuseau horaire est soustrait . x - (-5) est x + 5.

Cet horodatage, ajusté au stockage UTC, est ensuite ajusté pour le paramètre TimeZone de votre serveur pour l'affichage afin qu'il s'affiche à l'heure locale.

Si vous souhaitez plutôt dire "J'ai cet horodatage en heure UTC et souhaitez voir l'heure locale équivalente dans EST5EDT", si vous voulez être indépendant du paramètre TimeZone du serveur, vous devez écrire quelque chose comme:

select TIMESTAMP '2011-12-30 00:30:00' AT TIME ZONE 'UTC'
       AT TIME ZONE 'EST5EDT';

Cela dit "Étant donné l'horodatage 2011-12-30 00:30:00, traitez-le comme un horodatage en UTC lors de la conversion en timestamptz, puis convertissez cet horodatage en heure locale dans EST5EDT".

Horrible, non? Je veux donner une entreprise à celui qui a décidé de la sémantique folle de AT TIME ZONE - ça devrait vraiment être quelque chose comme timestamp CONVERT FROM TIME ZONE '-5' et timestamptz CONVERT TO TIME ZONE '+5'. De plus, timestamp with time zone Devrait en fait porter son fuseau horaire avec lui, ne pas être stocké en UTC et converti automatiquement en heure locale.

Pourquoi le second fonctionne (tant que TimeZone = UTC)

Votre version originale "travaux":

select '2011-12-30 00:30:00' AT TIME ZONE 'EST5EDT';

ne sera correct que si TimeZone est défini sur UTC, car la conversion text-to-timestamptz suppose TimeZone lorsque celle-ci n'est pas spécifiée.

Pourquoi le troisième fonctionne

Deux problèmes s'annulent.

L'autre version qui semble fonctionner est indépendante de TimeZone, mais elle ne fonctionne que parce que deux problèmes s'annulent. Tout d'abord, comme expliqué ci-dessus, timestamp without time zone AT TIME ZONE réinterprète l'horodatage comme étant dans ce fuseau horaire pour la conversion en un horodatage UTC; cela soustrait effectivement le décalage du fuseau horaire.

Cependant, pour des raisons que je dépasse mon ken, PostgreSQL utilise des horodatages avec le signe inverse de ce que j'ai l'habitude de voir la plupart des endroits. Voir la documentation :

Un autre problème à garder à l'esprit est que dans les noms de fuseau horaire POSIX, des décalages positifs sont utilisés pour les emplacements à l'ouest de Greenwich. Partout ailleurs, PostgreSQL suit la convention ISO-8601 selon laquelle les décalages de fuseau horaire positifs se trouvent à l'est de Greenwich.

Cela signifie que EST5EDT Est identique à +5, Et non -5. C'est pourquoi cela fonctionne: parce que vous soustrayez le décalage tz sans l'ajouter, mais vous soustrayez un décalage négatif!

Ce dont vous auriez besoin pour le corriger est à la place:

select TIMESTAMP '2011-12-30 00:30:00' AT TIME ZONE 'UTC'
       AT TIME ZONE '+5';
121
Craig Ringer