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

199

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

Larsenal
la source
3
Cette réponse connexe peut être utile.
Erwin Brandstetter

Réponses:

170

Les différences sont couvertes dans la documentation PostgreSQL pour les types date / heure . Oui, le traitement de TIMEou TIMESTAMPdiffère entre l'un WITH TIME ZONEou l'autre WITHOUT TIME ZONE. Cela n'affecte pas la façon dont les valeurs sont stockées; cela affecte la manière dont ils sont interprétés.

Les effets des fuseaux horaires sur ces types de données sont traités 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 dans le cadre de la valeur, la valeur peut être rendue comme une heure locale dans le client.

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

Le comportement diffère en fonction d'au moins trois facteurs:

  • Le paramètre de fuseau horaire dans le client.
  • Le type de données (c'est WITH TIME ZONE-à- dire ou WITHOUT TIME ZONE) de la valeur.
  • Indique 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)
gros nez
la source
92
Corriger uniquement si vous vous référez au processus d'insertion / récupération de valeurs. Mais les lecteurs doivent comprendre que les deux types de données, timestamp with time zoneet timestamp without time zone, dans Postgres, ne stockent pas réellement les informations de fuseau horaire. Vous pouvez le confirmer en jetant un coup d'œil à la page de documentation du type de données: les deux types occupent le même nombre d'octets et ont la plage de valeurs de sauvegarde, donc pas de place pour stocker les informations de fuseau horaire. Le texte de la page le confirme. Quelque chose d'un abus de langage: "sans tz" signifie "ignorer le décalage lors de l'insertion de données" et "avec tz" signifie "utiliser le décalage pour s'ajuster à UTC".
Basil Bourque
47
Les types de données sont un abus de langage d'une seconde manière: ils disent "fuseau horaire" mais en fait nous parlons de décalage par rapport à UTC / GMT. Un fuseau horaire est en fait un décalage ainsi que des règles / historique sur l'heure d'été (DST) et d'autres anomalies.
Basil Bourque
4
Je dirais plutôt qu'un décalage est un fuseau horaire plus des règles pour l'heure d'été. Vous ne pouvez pas découvrir le fuseau horaire avec un décalage, mais vous pouvez découvrir le décalage en fonction du fuseau horaire et des règles d'heure d'été.
igorsantos07
3
Citant le document officiel : Toutes les dates et heures tenant compte du fuseau horaire sont stockées en interne en UTC. Ils sont convertis en heure locale dans la zone spécifiée par le paramètre de configuration TimeZone avant d'être affichés au client.
Guillaume Husta
2
@ igorsantos07 Un fuseau horaire est un ensemble de règles / d'historique sur les changements d'heure d'été et autres changements. Votre formulation semble superflue. Et votre déclaration selon laquelle «un décalage est un fuseau horaire plus des règles pour l'heure d'été» est tout simplement erronée: un décalage est simplement un nombre d'heures, de minutes et de secondes - ni plus, ni moins.
Basil Bourque
40

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

Aucune des deux TIMESTAMPvariantes ne stocke 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 prévue), et non dans le format de stockage lui-même:

  • TIMESTAMP WITHOUT TIME ZONEstocke la date-heure locale (aka. date du calendrier mural et heure de l'horloge murale). Son fuseau horaire n'est pas spécifié pour autant que PostgreSQL puisse le dire (bien que votre application sache ce que c'est). Par conséquent, PostgreSQL n'effectue aucune conversion liée au fuseau horaire en entrée ou en sortie. Si la valeur a été entrée dans la base de données en tant que '2011-07-01 06:30:30', alors peu importe dans quel fuseau horaire vous l'affichez plus tard, elle indiquera toujours année 2011, mois 07, jour 01, 06 heures, 30 minutes et 30 secondes (dans un certain format). De plus, tout décalage ou fuseau horaire que vous spécifiez dans l'entrée est ignoré par PostgreSQL, donc '2011-07-01 06:30:30+00'et '2011-07-01 06:30:30+05'sont les mêmes que just '2011-07-01 06:30:30'. Pour les développeurs Java: c'est analogue à java.time.LocalDateTime.

  • TIMESTAMP WITH TIME ZONEstocke un point sur la ligne horaire UTC. Son aspect (combien d'heures, de minutes, etc.) dépend de votre fuseau horaire, mais il se réfère toujours 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, donc lorsque l'entrée ne contient pas de décalage ou de fuseau horaire explicite (comme '2011-07-01 06:30:30'), il est supposé être dans le fuseau horaire actuel de la session PostgreSQL, sinon le décalage ou le fuseau horaire explicitement spécifié est utilisé (comme dans '2011-07-01 06:30:30+05'). La sortie est affichée convertie dans le fuseau horaire actuel de la session PostgreSQL. Pour les développeurs Java: c'est analogue à java.time.Instant(avec une résolution inférieure cependant), mais avec JDBC et JPA 2.2, vous êtes censé le mapper à java.time.OffsetDateTime(ou à java.util.Dateoujava.sql.Timestamp bien sûr).

Certains disent que les deux TIMESTAMPvariantes stockent la date et l'heure UTC. En quelque sorte, mais à mon avis, il est déroutant de le dire ainsi. TIMESTAMP WITHOUT TIME ZONEest stocké comme un TIMESTAMP WITH TIME ZONE, qui, rendu avec le fuseau horaire UTC, donne la même année, mois, jour, heures, minutes, secondes et microsecondes que dans la date-heure locale. Mais ce n'est pas censé représenter le point sur la ligne de temps que dit l'interprétation UTC, c'est juste la façon dont les champs de date-heure locaux sont codés. (C'est un groupe de points sur la ligne du temps, car le fuseau horaire réel n'est pas UTC; nous ne savons pas ce que c'est.)

Ddekany
la source
Il n'y a rien de mal à récupérer un fichier en TIMESTAMP WITH TIME ZONEtant que fichier Instant. Les deux représentent un point sur la chronologie en UTC. Instantest préférable, à mon avis, par rapport à OffsetDateTimecar il est plus auto-documenté: A TIMESTAMP WITH TIME ZONEest toujours récupéré de la base de données en tant que UTC, et an Instantest toujours en UTC, donc une correspondance naturelle, tandis que an OffsetDateTimepeut transporter d'autres décalages.
Basil Bourque
@BasilBourque Malheureusement, la spécification JDBC actuelle, la spécification JPA 2.2, ainsi que la documentation PostgreSQL JDBC ne mentionnent OffsetDateTimeque le type Java mappé. Je ne sais pas si Instanceest toujours pris en charge officieusement quelque part.
ddekany
question, vous dites que tout décalage que je spécifie dans l'entrée tel que '2011-07-01 06:30:30+00'et '2011-07-01 06:30:30+05' est ignoré mais je suis capable de le faire insert into test_table (date) values ('2018-03-24T00:00:00-05:00'::timestamptz);et il le convertira correctement en utc. où la date est l'horodatage sans fuseau horaire. J'essaie de comprendre quelle est la valeur principale de l'horodatage avec le fuseau horaire et j'ai des problèmes.
pk1m
@ pk1m Vous compliquez les choses avec le ::timestamptz. Avec cela, vous convertissez la chaîne en TIMESTAMP WITH TIME ZONE, et quand elle sera ensuite convertie en WITHOUT TIME ZONE, cela stockera le jour du "calendrier mural" et l'heure de l'horloge murale de cet instant comme vu depuis le fuseau horaire de votre session (qui est peut-être UTC). Il ne s'agira toujours que d'un horodatage local avec un décalage non spécifié (pas de zone).
ddekany
Je travaille avec python, et c'est ce qui est inséré lors de l'insertion d'un objet datettime conscient de l'horodatage. Il me semble qu'il est utile d'utiliser l'horodatage avec le fuseau horaire, mais ce n'est pas nécessaire de gérer les fuseaux horaires.
pk1m
12

Voici un exemple qui devrait vous aider. Si vous avez un horodatage avec un fuseau horaire, vous pouvez convertir cet horodatage dans n'importe quel 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'

Production:

-[ 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
Serby
la source
5
La déclaration «ne sera pas convertie correctement» n'est tout simplement pas vraie. Vous devez comprendre ce timestampque vous timestamptzvoulez dire. timestamptzsignifie un point absolu dans le temps (UTC) alors que timestampindique ce que l'horloge a montré dans un certain fuseau horaire. Ainsi, lors de la conversion timestamptzvers un fuseau horaire, vous vous demandez ce qu'indiquait l'horloge à New York à ce moment absolu? alors que lors de la "conversion" de a timestamp, vous vous demandez quel était le moment absolu où l'horloge de New York a montré x?
fphilipe
La AT TIME ZONEconstruction est un casse-tête en soi, même si vous comprenez déjà les types WITHvs. WITHOUT TIME ZONEC'est donc un choix curieux pour les expliquer. (: ( AT TIME ZONEconvertit un WITH TIME ZONEhorodatage en WITHOUT TIME ZONEhorodatage, et vice versa ... pas exactement évident.)
ddekany
now()::timestamp AT TIME ZONE 'CST'n'a pas de sens, sauf si vous à quel moment une horloge pour la zone 'CST' afficherait l'heure que votre horloge locale affiche actuellement
Jasen