web-dev-qa-db-fra.com

Différence entre les horodatages avec / sans fuseau horaire dans PostgreSQL

Les valeurs d'horodatage sont-elles stockées différemment dans PostgreSQL lorsque le type de données est WITH TIME ZONE par rapport à WITHOUT TIME ZONE? Les différences peuvent-elles être illustrées avec des cas de test simples?

155
Larsenal

Les différences sont couvertes à documentation de PostgreSQL pour les types date/heure . Oui, le traitement de TIME ou TIMESTAMP diffère d'un WITH TIME ZONE ou WITHOUT TIME ZONE. Cela n'affecte pas la façon dont les valeurs sont stockées; cela affecte leur interprétation.

Les effets des fuseaux horaires sur ces types de données sont couverts spécifiquement dans la documentation. La différence provient de ce que le système peut raisonnablement savoir sur la valeur:

  • Avec un fuseau horaire faisant partie de la valeur, la valeur peut être restituée sous forme d’heure locale dans le client.

  • Sans fuseau horaire dans la valeur, le fuseau horaire par défaut évident est UTC. Il est donc rendu pour ce fuseau horaire.

Le comportement diffère selon au moins trois facteurs:

  • Le réglage du fuseau horaire dans le client.
  • Le type de données (c'est-à-dire WITH TIME ZONE ou WITHOUT TIME ZONE) de la valeur.
  • Si la valeur est spécifiée avec un fuseau horaire particulier.

Voici des exemples couvrant les combinaisons de ces facteurs:

foo=> SET TIMEZONE TO 'Japan';
SET
foo=> SELECT '2011-01-01 00:00:00'::TIMESTAMP;
      timestamp      
---------------------
 2011-01-01 00:00:00
(1 row)

foo=> SELECT '2011-01-01 00:00:00'::TIMESTAMP WITH TIME ZONE;
      timestamptz       
------------------------
 2011-01-01 00:00:00+09
(1 row)

foo=> SELECT '2011-01-01 00:00:00+03'::TIMESTAMP;
      timestamp      
---------------------
 2011-01-01 00:00:00
(1 row)

foo=> SELECT '2011-01-01 00:00:00+03'::TIMESTAMP WITH TIME ZONE;
      timestamptz       
------------------------
 2011-01-01 06:00:00+09
(1 row)

foo=> SET TIMEZONE TO 'Australia/Melbourne';
SET
foo=> SELECT '2011-01-01 00:00:00'::TIMESTAMP;
      timestamp      
---------------------
 2011-01-01 00:00:00
(1 row)

foo=> SELECT '2011-01-01 00:00:00'::TIMESTAMP WITH TIME ZONE;
      timestamptz       
------------------------
 2011-01-01 00:00:00+11
(1 row)

foo=> SELECT '2011-01-01 00:00:00+03'::TIMESTAMP;
      timestamp      
---------------------
 2011-01-01 00:00:00
(1 row)

foo=> SELECT '2011-01-01 00:00:00+03'::TIMESTAMP WITH TIME ZONE;
      timestamptz       
------------------------
 2011-01-01 08:00:00+11
(1 row)
130
bignose

J'essaie de l'expliquer de manière plus compréhensible que la documentation PostgreSQL mentionnée.

Ni les variantes TIMESTAMP ne stockent un fuseau horaire (ou un décalage), malgré ce que les noms suggèrent. La différence réside dans l'interprétation des données stockées (et dans l'application envisagée) et non dans le format de stockage lui-même:

  • TIMESTAMP WITHOUT TIME ZONE stocke local date-heure (aussi appelée date du calendrier mural et heure de l'horloge murale). Son fuseau horaire est indéterminé pour autant que PostgreSQL puisse le savoir (bien que votre application sache ce que c'est). Par conséquent, PostgreSQL ne fait aucune conversion liée à un fuseau horaire en entrée ou en sortie. Si la valeur a été entrée dans la base de données sous la forme '2011-07-01 06:30:30', alors peu importe le fuseau horaire dans lequel vous l'afficherez plus tard, il sera toujours indiqué que l'année 2011, le mois 07, le jour 01, 06 heures, 30 minutes et 30 secondes ( dans un certain format). De même, tout décalage ou fuseau horaire spécifié dans l'entrée est ignoré par PostgreSQL. Par conséquent, '2011-07-01 06:30:30+00' et '2011-07-01 06:30:30+05' sont identiques à '2011-07-01 06:30:30'. Pour les développeurs Java: c'est analogue à Java.time.LocalDateTime.

  • TIMESTAMP WITH TIME ZONE enregistre un point sur la ligne horaire UTC. Son apparence (combien d'heures, de minutes, etc.) dépend de votre fuseau horaire, mais elle fait toujours référence au même instant "physique" (comme le moment d'un événement physique réel). L'entrée est convertie en interne en UTC, et c'est ainsi qu'elle est stockée. Pour cela, le décalage de l'entrée doit être connu. Ainsi, lorsque l'entrée ne contient pas de décalage explicite ni de fuseau horaire (comme '2011-07-01 06:30:30'), elle est supposée se trouver dans le fuseau horaire actuel de la session PostgreSQL, sinon le décalage explicitement spécifié ou le fuseau horaire est utilisé (comme dans '2011-07-01 06:30:30+05'). La sortie affichée est convertie dans le fuseau horaire actuel de la session PostgreSQL. Pour les développeurs Java: cela ressemble à Java.time.Instant (avec une résolution inférieure cependant), mais avec JDBC et JPA 2.2, vous êtes censé l'associer à Java.time.OffsetDateTime (ou à Java.util.Date ou Java.sql.Timestamp bien sûr).

Certains disent que les deux variantes TIMESTAMP stockent la date et l'heure UTC. En quelque sorte, mais à mon avis, c'est déroutant de le dire ainsi. TIMESTAMP WITHOUT TIME ZONE est stocké comme un TIMESTAMP WITH TIME ZONE, qui est rendu avec le fuseau horaire UTC et indique les mêmes années, mois, jour, heures, minutes, secondes et microsecondes, comme dans la date et l'heure locales. Mais ce n'est pas censé représenter le point sur la ligne de temps mentionné dans l'interprétation UTC, c'est simplement la manière dont les champs de date-heure locaux sont codés. (Il s'agit d'une grappe de points sur la ligne de temps, car le fuseau horaire réel n'est pas UTC; nous ne savons pas ce que c'est.)

18
ddekany

Voici un exemple qui devrait aider. Si vous avez un horodatage avec un fuseau horaire, vous pouvez convertir cet horodatage en un autre fuseau horaire. Si vous n'avez pas de fuseau horaire de base, il ne sera pas converti correctement.

SELECT now(),
   now()::timestamp,
   now() AT TIME ZONE 'CST',
   now()::timestamp AT TIME ZONE 'CST'

Sortie:

-[ RECORD 1 ]---------------------------
now      | 2018-09-15 17:01:36.399357+03
now      | 2018-09-15 17:01:36.399357
timezone | 2018-09-15 08:01:36.399357
timezone | 2018-09-16 02:01:36.399357+03
9
serby