web-dev-qa-db-fra.com

Quel type d'horodatage dois-je choisir dans une base de données PostgreSQL?

J'aimerais définir une meilleure pratique pour stocker des horodatages dans ma base de données Postgres dans le cadre d'un projet multi-fuseau horaire.

Je peux

  1. choisissez TIMESTAMP WITHOUT TIME ZONE et rappelez-vous quel fuseau horaire a été utilisé au moment de l'insertion pour ce champ
  2. choisissez TIMESTAMP WITHOUT TIME ZONE et ajoutez un autre champ contenant le nom du fuseau horaire utilisé lors de l'insertion.
  3. choisissez TIMESTAMP WITH TIME ZONE et insérez les horodatages en conséquence

J'ai une légère préférence pour l'option 3 (horodatage avec fuseau horaire), mais j'aimerais avoir une opinion éclairée à ce sujet.

116
Jerome WAGNER

Tout d’abord, la gestion du temps et l’arithmétique de PostgreSQL sont fantastiques et l’option 3 est acceptable dans le cas général. Cependant, il s’agit d’une vision incomplète du temps et des fuseaux horaires et peut être complétée:

  1. Enregistrez le nom du fuseau horaire d’un utilisateur en tant que préférence de l’utilisateur (par exemple, America/Los_Angeles, Pas -0700).
  2. Faites en sorte que les données des événements/temps des utilisateurs soient transmises localement à leur cadre de référence (probablement un décalage par rapport à UTC, tel que -0700).
  3. Dans l'application, convertissez l'heure en UTC et stockez-la à l'aide d'une colonne TIMESTAMP WITH TIME ZONE.
  4. Les demandes d’heure de retour locales dans le fuseau horaire d’un utilisateur (c’est-à-dire convertir de UTC en America/Los_Angeles).
  5. Définissez timezone de votre base de données sur UTC.

Cette option ne fonctionne pas toujours, car il peut être difficile d’obtenir le fuseau horaire d’un utilisateur et, par conséquent, le conseil de couverture consistant à utiliser TIMESTAMP WITH TIME ZONE Pour les applications allégées. Cela dit, laissez-moi vous expliquer plus en détail certains aspects généraux de cette option 4.

Comme pour l'option 3, la raison de WITH TIME ZONE Est parce que l'heure à laquelle quelque chose s'est passé est un moment absolu . WITHOUT TIME ZONE Donne un fuseau horaire relatif . Ne mélangez jamais jamais des TIMESTAMP absolus et relatifs.

Du point de vue de la programmation et de la cohérence, assurez-vous que tous les calculs sont effectués en utilisant UTC comme fuseau horaire. Ce n’est pas une exigence de PostgreSQL, mais cela aide lors de l’intégration à d’autres langages de programmation ou environnements. Définir un CHECK sur la colonne pour s'assurer que l'écriture dans la colonne d'horodatage a un décalage de fuseau horaire de 0 Est une position défensive qui empêche quelques classes de bogues (par exemple, un script dans un fichier et quelque chose d'autre trie les données temporelles à l'aide d'un tri lexical). Encore une fois, PostgreSQL n’a pas besoin de cela pour effectuer des calculs de date correctement ou pour convertir des fuseaux horaires (c.-à-d. PostgreSQL est très habile pour convertir des heures entre deux fuseaux horaires arbitraires). Pour vous assurer que les données entrant dans la base de données sont stockées avec un décalage de zéro:

CREATE TABLE my_tbl (
  my_timestamp TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
  CHECK(EXTRACT(TIMEZONE FROM my_timestamp) = '0')
);
test=> SET timezone = 'America/Los_Angeles';
SET
test=> INSERT INTO my_tbl (my_timestamp) VALUES (NOW());
ERROR:  new row for relation "my_tbl" violates check constraint "my_tbl_my_timestamp_check"
test=> SET timezone = 'UTC';
SET
test=> INSERT INTO my_tbl (my_timestamp) VALUES (NOW());
INSERT 0 1

Ce n'est pas parfait à 100%, mais il fournit une mesure anti-footshoot suffisamment forte pour s'assurer que les données sont déjà converties au format UTC. Il y a beaucoup d'opinions sur la façon de procéder, mais cela semble être le meilleur dans la pratique de mon expérience.

Les critiques sur la gestion des fuseaux horaires des bases de données sont largement justifiées (de nombreuses bases de données traitent cela avec une grande incompétence), cependant la gestion des horodatages et des fuseaux horaires par PostgreSQL est assez impressionnante (malgré quelques "fonctionnalités" ici et là). Par exemple, une de ces fonctionnalités:

-- Make sure we're all working off of the same local time zone
test=> SET timezone = 'America/Los_Angeles';
SET
test=> SELECT NOW();
              now              
-------------------------------
 2011-05-27 15:47:58.138995-07
(1 row)

test=> SELECT NOW() AT TIME ZONE 'UTC';
          timezone          
----------------------------
 2011-05-27 22:48:02.235541
(1 row)

Notez que AT TIME ZONE 'UTC' Supprime les informations de fuseau horaire et crée un relatif TIMESTAMP WITHOUT TIME ZONE En utilisant le cadre de référence de votre cible (UTC).

Lors de la conversion d'un TIMESTAMP WITHOUT TIME ZONE Incomplet en un TIMESTAMP WITH TIME ZONE, Le fuseau horaire manquant est hérité de votre connexion:

test=> SET timezone = 'America/Los_Angeles';
SET
test=> SELECT EXTRACT(TIMEZONE_HOUR FROM NOW());
 date_part 
-----------
        -7
(1 row)
test=> SELECT EXTRACT(TIMEZONE_HOUR FROM TIMESTAMP WITH TIME ZONE '2011-05-27 22:48:02.235541');
 date_part 
-----------
        -7
(1 row)

-- Now change to UTC    
test=> SET timezone = 'UTC';
SET
-- Create an absolute time with timezone offset:
test=> SELECT NOW();
              now              
-------------------------------
 2011-05-27 22:48:40.540119+00
(1 row)

-- Creates a relative time in a given frame of reference (i.e. no offset)
test=> SELECT NOW() AT TIME ZONE 'UTC';
          timezone          
----------------------------
 2011-05-27 22:48:49.444446
(1 row)

test=> SELECT EXTRACT(TIMEZONE_HOUR FROM NOW());
 date_part 
-----------
         0
(1 row)

test=> SELECT EXTRACT(TIMEZONE_HOUR FROM TIMESTAMP WITH TIME ZONE '2011-05-27 22:48:02.235541');
 date_part 
-----------
         0
(1 row)

La ligne du bas:

  • stocker le fuseau horaire d’un utilisateur en tant qu’étiquette nommée (par exemple America/Los_Angeles) et non en décalage par rapport à UTC (par exemple -0700)
  • utiliser UTC pour tout sauf s'il existe une raison impérieuse de stocker un décalage non nul
  • traiter toutes les heures UTC non nulles comme une erreur de saisie
  • ne mélangez jamais les horodatages relatifs et absolus
  • utilisez aussi UTC comme timezone dans la base de données si possible

Note aléatoire sur le langage de programmation: Le type de données datetime de Python est très efficace pour maintenir la distinction entre les temps absolus et relatifs (bien que frustrant au début jusqu'à ce que vous le complétiez avec une bibliothèque comme PyTZ ).


[~ # ~] éditer [~ # ~]

Laissez-moi vous expliquer un peu plus la différence entre le relatif et l'absolu.

Le temps absolu est utilisé pour enregistrer un événement. Exemples: "Utilisateur 123 connecté" ou "une cérémonie de remise des diplômes commence le 2011-05-28 2pm PST." Indépendamment de votre fuseau horaire local, si vous pouviez vous téléporter sur le lieu de l'événement, vous pourriez être témoin de l'événement. La plupart des données temporelles dans une base de données sont absolues (et doivent donc être TIMESTAMP WITH TIME ZONE, Idéalement avec un décalage de +0 et un libellé textuel représentant les règles régissant le fuseau horaire particulier - pas un décalage).

Un événement relatif consisterait à enregistrer ou à programmer l'heure de quelque chose du point de vue d'un fuseau horaire à déterminer. Exemples: "Les portes de notre entreprise ouvrent à 8 heures du matin et ferment à 21 heures", "Rendez-vous tous les lundis à 7 heures du matin pour un petit-déjeuner hebdomadaire" ou "Chaque Halloween à 20 heures". En général, l'heure relative est utilisée dans un modèle ou une usine pour les événements, et l'heure absolue est utilisée pour presque tout le reste. Il convient de signaler une exception rare qui devrait illustrer la valeur des temps relatifs. Utilisez un horodatage relatif pour les événements futurs suffisamment lointains dans lesquels il pourrait exister une incertitude quant au moment absolu où quelque chose pourrait se produire. Voici un exemple concret:

Supposons que c’est l’année 2004 et que vous deviez programmer une livraison le 31 octobre 2008 à 13h00 sur la côte ouest des États-Unis (c’est-à-dire America/Los_Angeles/PST8PDT). Si vous aviez stocké cette heure en utilisant l'heure absolue avec ’2008-10-31 21:00:00.000000+00’::TIMESTAMP WITH TIME ZONE, La livraison se serait affichée à 14 heures parce que le gouvernement américain avait adopté le Energy Policy Act of 2005 qui modifiait les règles régissant l'heure d'été. En 2004, lorsque la livraison était planifiée, la date 10-31-2008 Aurait été l'heure normale du Pacifique (+8000), Mais à partir de l'année 2005, les bases de données de fuseaux horaires ont reconnu que 10-31-2008 Aurait été Heure avancée du Pacifique (+0700). Stocker un horodatage relatif avec le fuseau horaire aurait abouti à un calendrier de livraison correct, car un horodatage relatif est à l’abri du sabotage abusif du Congrès. La limite entre l'utilisation de temps relatifs et de temps absolus pour la planification est une ligne floue, mais ma règle générale est que la planification pour tout ce qui va aller plus loin que 3-6mo devrait utiliser des horodatages relatifs (planifié = absolu vs planifié = relative ???).

L'autre/dernier type de temps relatif est le INTERVAL. Exemple: "la session expirera 20 minutes après la connexion d'un utilisateur". Un INTERVAL peut être utilisé correctement avec un horodatage absolu (TIMESTAMP WITH TIME ZONE) Ou un horodatage relatif (TIMESTAMP WITHOUT TIME ZONE). Il est également correct de dire "une session utilisateur expire 20 minutes après une connexion réussie (login_utc + session_duration)" ou "notre réunion de petit-déjeuner ne peut durer que 60 minutes (recurring_start_time + meeting_length)".

Dernier brouillage: DATE, TIME, TIME WITHOUT TIME ZONE Et TIME WITH TIME ZONE Sont tous des types de données relatifs. Par exemple: '2011-05-28'::DATE Représente une date relative car vous ne disposez d'aucune information de fuseau horaire qui pourrait être utilisée pour identifier minuit. De même, '23:23:59'::TIME Est relatif car vous ne connaissez ni le fuseau horaire ni le DATE représenté par l'heure. Même avec '23:59:59-07'::TIME WITH TIME ZONE, Vous ne savez pas ce que serait le DATE. Et enfin, DATE avec un fuseau horaire n'est pas en fait un DATE, c'est un TIMESTAMP WITH TIME ZONE:

test=> SET timezone = 'America/Los_Angeles';
SET
test=> SELECT '2011-05-11'::DATE AT TIME ZONE 'UTC';
      timezone       
---------------------
 2011-05-11 07:00:00
(1 row)

test=> SET timezone = 'UTC';
SET
test=> SELECT '2011-05-11'::DATE AT TIME ZONE 'UTC';
      timezone       
---------------------
 2011-05-11 00:00:00
(1 row)

Mettre des dates et des fuseaux horaires dans des bases de données est une bonne chose, mais il est facile d'obtenir des résultats subtilement incorrects. Un effort supplémentaire minimal est requis pour stocker les informations de temps correctement et complètement, mais cela ne signifie pas l'effort supplémentaire est toujours nécessaire.

139
Sean

La réponse de Sean est trop complexe et trompeuse.

Le fait est que "WITH TIME ZONE" et "WITHOUT TIME ZONE" stockent la valeur sous la forme d'un horodatage UTC absolu. La différence réside dans la façon dont l'horodatage est affiché. Lorsque "AVEC fuseau horaire", la valeur affichée est la valeur UTC enregistrée traduite dans la zone de l'utilisateur. Lorsque "SANS fuseau horaire", la valeur enregistrée dans l'heure UTC est tordue de manière à afficher le même cadran d'horloge quelle que soit la zone définie par l'utilisateur ".

La seule situation dans laquelle un "SANS fuseau horaire" est utilisable est le cas où une valeur faciale d'horloge est applicable quel que soit le fuseau horaire actuel. Par exemple, lorsqu'un horodatage indique le moment où les isoloirs peuvent fermer (c'est-à-dire qu'ils se ferment à 20h00 indépendamment du fuseau horaire d'une personne).

Utilisez le choix 3. Utilisez toujours "AVEC fuseau horaire" sauf s'il existe une raison très spécifique de ne pas le faire.

54
Jay

Ma préférence va à l'option 3, car Postgres peut alors effectuer une grande partie du travail en recalculant les horodatages par rapport au fuseau horaire, alors qu'avec les deux autres, vous devrez le faire vous-même. Les frais de stockage supplémentaires liés au stockage de l'horodatage avec un fuseau horaire sont vraiment négligeables, sauf si vous parlez de millions d'enregistrements. Dans ce cas, vous avez probablement déjà besoin de beaucoup de stockage.

5
GordonM