La .Taverne de l'Invisible

Une page de Le Lab de la taverne

L’importance des timezone avec le changement d'heure

Par Darko Stankovski, 29/10/2021 (Mise à jour 31/03/2026) — Débutant

Gérer les dates, c’est un truc assez pénible à faire à la main. Tous les langages proposent une bibliothèque pour en simplifier la gestion et en Python, c'est la bibliothèque datetime. Généralement, nous nous contentons de créer un instant (une certaine heure un certain jour) naif, c'est à dire sans l'information de timezone. C'est facile et jugé suffisant. Après tout, tant que l’on reste dans le même fuseau horaire, il n’y a pas de problème.

Oui mais le week-end dernier, c'était le changement d'heure. Et là, mine de rien, ça peut être pénible…

Nous allons donc voir aujourd’hui pourquoi il est important de gérer les timezone même si on travaille dans une seule zone géographique.

Précisions sur la notion de date

En informatique, nous parlons de dates mais il s’agit de la notion d’instants, c’est à dire qu’à l'information d'année, mois et jour, il faut ajouter celle d'heure, minute, seconde voir même de microseconde. Une date au sens humain (un jour d’un mois d’une année) possède simplement les autres informations à 0, donc minuit.

Les timezone, qu’est-ce que c’est ?

Les timezone, c'est tout simplement la notion de fuseaux horaires. Si vous planifiez un meeting le lundi 29 mars 2021 à 9h00, autant il n'y aura pas de problème pour votre voisin de bureau mais pour votre contact sur la New York… il faudra préciser pour qui, c'est à dire dans quel fuseau horaire.

Un fuseau horaire, ce n'est rien d'autre qu'un décalage avec un instant de référence et oui, UTC.

Mais ce décalage est de combien ? C’est là que rentre en jeu la base de données IANA. Celle-ci indique en fonction de la localisation géographique le nombre d’heures à ajouter ou soustraire. Attention… Ce n’est pas forcément un nombre ronds. L’Australie par exemple est sur 3 fuseaux horaires : UTC+10, UTC+9:30 et UTC+8:45… Oui, Perth et Alice Springs ont un décalage de 45 minutes…

Mais un fuseau horaire ne se limite pas aux contraintes géographiques. Si vous travaillez sur des dates historiques, il faut considérer des changements administratifs comme le changement d’heure avec les heures d’hiver et d’été. Et il y a aussi des curiosités comme les îles Samoa qui n’ont pas eu de vendredi 30 décembre 2011.

Ce que Python fournit

La bibliothèque Python datetime comporte, pour représenter les dates, une classe datetime. Oui, je sais… Elle comporte également une classe timedelta pour représenter des durées. Celles-ci peuvent être créé soit à l'aide d'un constructeur soit par soustraction de dates.

Pour la gestion des timezone, Python propose depuis la version 3.9 une implémentation concrète de la base IANA issue de la PEP 615 : zoneinfo.

Pour information, avant Python 3.9, il fallait passer par des projets tiers et en particulier pytz. Ce projet est toujours maintenu (en 2026) pour fournir le support concret aux codes utilisant une version de Python antérieur à la 3.9.

Un point de vocabulaire

Le paradoxe des dates c’est qu’elles ne peuvent réellement être comparées que si elles possèdent l’information de timezone. En Python, un objet date sans timezone est dit naive, traduit par naïf. Un objet avec est cette information est dit aware ce qui est traduit dans la doc par avisé. J'aurai tendance à écrire localisé.

Utilisation de zoneinfo

Créer des dates aware

zoneinfo permet de créer des objets ZoneInfo (oui, vous voyez à la casse que cette lib est plus moderne), ce que nous allons faire après avoir importé la dépendance.

from zoneinfo import ZoneInfo

paris_tz = ZoneInfo('Europe/Paris')
new_york_tz = ZoneInfo('America/New_York')

Pour connaitre les arguments possibles, zoneinfo fournit une fonction available_timezones() qui en retourne la liste.

Nous pouvons maintenant créer une date aware. Il y a pour cela 2 possibilités :

  • assigner le timezone à la création
  • assigner le timezone à une date naïve.

Dans le code suivant, la ligne 3 crée une date localisée, la ligne 6 localise une date naïve créée précédemment.

from datetime import datetime, timedelta

summer_time = datetime(2021, 10, 30, tzinfo=paris_tz)

naive_summer_time = datetime(2021, 10, 30)
summer_time = naive_summer_time.replace(tzinfo=paris_tz)

Attention, n'oubliez pas un comportement fondamental avec datetime : ce sont des objets immuables. Ainsi, la méthode .replace() retourne un nouvel objet. naive_summer_time reste naïf.

En soi, c'est pratique, ça permet de le réutiliser au besoin.

Relocaliser une date

Mon premier exemple parlait de gestion de rendez-vous avec quelqu'un qui est dans un autre fuseau horaire. Si je définis un un meeting le lundi 29 mars 2021 à 9h00 pour moi, à quelle date est-ce que cela correspondra pour lui ?

Nous allons créer un objet représentant le début du meeting localisé à Paris puis avec la méthode astimezone(), nous allons créer la date pour New York à partir de notre date localisée :

meeting_fr = datetime(2021, 3, 29, 9, tzinfo=paris_tz)
meeting_us = meeting_fr.astimezone(new_york_tz)
print(meeting_us) #  affiche 2021-03-29 03:00:00-04:00

Nous avons donc la bonne information de date qui pique un peu à 3h du matin…

Localiser une date naïve avec astimezone : le piège

Attention à un petit piège : vous pouvez être tenté d'utiliser astimezone() à la place de replace() pour localiser une date naïve. La doc vous le suggère. Cependant, vous n’aurez pas (toujours) le même comportement :

>>> naive_summer_time = datetime(2021, 10, 30)
>>> naive_summer_time.replace(tzinfo=new_york_tz)
datetime.datetime(2021, 10, 30, 0, 0, tzinfo=zoneinfo.ZoneInfo(key='America/New_York'))

>>> naive_summer_time.astimezone(new_york_tz)
datetime.datetime(2021, 10, 29, 18, 0, tzinfo=zoneinfo.ZoneInfo(key='America/New_York'))

Python suppose que la date naïve est dans le fuseau horaire de votre système local avant de faire la conversion. Si vous utilisez astimezone() pour localiser une date locale, ça passe. Mais pas pour un autre fuseau horaire.

Pour éviter toute confusion, utilisez replace().

Le passage à l’heure d’hiver ou d'été

C'est là que ça va être subtile car ZoneInfo est destiné à la gestion de données calendaires. Prenons l'année 2026. En 2026, nous passerons à l’heure d’hiver dans la nuit du 24 au 25 octobre. Pour rappel, l’heure d’hiver correspond à l’heure légale. Le passage à l’heure d’été ajoute une heure à l’heure légale.

Voyons le comportement : le 24 octobre, nous serons à l'heure d'été. Le 26, à l'heure d'hiver. Entre les deux, ils se seront écoulés non pas 48 mais 49 heures.

Dans les lignes suivantes, je vais :

  • créer 2 objets pour représenter ces deux dates (à minuit)
  • observer la différence d'offset du timezone
  • ajouter 2 jours à la date d'été et observer le retour
  • faire la soustraction entre les deux et observer le retour
>>> summer_time = datetime(2026, 10, 24, tzinfo=paris_tz)

>>> winter_time = datetime(2026, 10, 26, tzinfo=paris_tz)

# L'été est bien en UTC+2
>>> print(summer_time)
2026-10-24 00:00:00+02:00

# L'hiver est bien en UTC+1
>>> print(winter_time)
2026-10-26 00:00:00+01:00

# 2 jours calendaires sont ajoutés et le timezone adapté
>>> print(summer_time + timedelta(days=2))
2026-10-26 00:00:00+01:00

# La différence est calendaire et non absolue
>>> print(winter_time - summer_time)
2 days, 0:00:00

En Python, le calcul d'ajout d'un timedelta à un objet datetime se fait de manière calendaire et non de manière absolue (en tenant compte du temps réel écoulé). Ceci est pratique pour gérer des informations d'agenda mais pas pour gérer des calculs de temps.

Comment calculer les durées réelles ?

Pour calculer la durée réelle, il faut passer sur une date de référence : UTC. Il s'agit de la pratique de la gestion des dates pour éviter toute ambiguïté et indépendante du comportement des libs (pytz fait un calcul absolu…).

Pour les calculs, nous aurons donc :

>>> utc_tz = ZoneInfo("UTC")

>>> summer_time_utc = summer_time.astimezone(utc_tz)
>>> winter_time_utc = winter_time.astimezone(utc_tz)

>>> print(summer_time_utc)
2026-10-23 22:00:00+00:00

>>> print(winter_time_utc)
2026-10-25 23:00:00+00:00

# La différence est la durée réelle
>>> print(winter_time_utc - summer_time_utc)
2 days, 1:00:00

# La nouvelle date est à 48 heures réels
>>> print(summer_time_utc + timedelta(days=2))
2026-10-25 22:00:00+00:00

# Et pour finir, une illustration de la relocalisation
>>> print((summer_time_utc + timedelta(days=2)).astimezone(paris_tz))
2026-10-25 23:00:00+01:00

Ceci illustre que la bonne pratique a toujours consisté à stocker et calculer en UTC, afficher en local. lorsque vous obtenez une date localisée, convertissez-la en UTC. Faites vos calculs et traitements sur ces dates. Si vous avez besoin de présenter la date à l’utilisateur, vous la relocalisez pour lui à ce moment là.

Bonus : lever l’ambiguïté entre 2h00 et 3h00

Dans la nuit du 24 au 25 octobre 2026, combien d'heures séparent 2h30 de 4h30 ? Le piège, c'est de quelle 2h30 parlons-nous ? Lors du passage de l'heure d'été à l'heure d'hiver, nous reculons nos montres. Il y a donc 2 périodes de 2h00 à 3h00.

La réponse à cette situation est le paramètre optionnel fold de datetime qui a té ajouté en Python 3.6. Ce paramètre attends 2 valeurs, 0 et 1 qui représentent respectivement la période été (le défaut) et hiver des deux moments avec la même représentation du temps.

summer_time = datetime(2026, 10, 25, 2, 30, fold=0, tzinfo=paris_tz)

fold_time = datetime(2026, 10, 25, 2, 30, fold=1, tzinfo=paris_tz)

print(summer_time == fold_time)
# Affiche : True

print(summer_time.astimezone(ZoneInfo("UTC")))
# Affiche : 2026-10-25 00:30:00+00:00

print(fold_time.astimezone(ZoneInfo("UTC")))
# Affiche : 2026-10-25 01:30:00+00:00

Attention cependant, un test d'égalité entre ces deux objets retournera True et la différence sera de 0. Python manipulant ces dates de manière calendaire. Le principe de convertir en dates UTC s'applique toujours.

En conclusion

Manipuler les dates n'est jamais facile. Même si cela parait inutile (parce que ça passe), localisez toujours vos objets datetime.

Si vous avez besoin d'effectuer des opérations calendaires, vous pouvez les réaliser sur ces objets.

Mais si vous avez besoin de faire des manipulations de durée physiques ou de les stocker, convertissez les en UTC et faites vos opérations en UTC. Vous les reconvertirez pour les besoins de l'utilisateur.

Licence Creative Commons

Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Pas de Modification 4.0 International