14  Les dates en Python

Travailler avec les dates est systématiquement compliqué. Tous les langages proposent une bibliothèque pour simplifier la tâche.

La distribution standard Python fournit le module datetime. Celui-ci contient fondamentalement deux classes qui permettent de représenter les concepts fondamentaux :

14.1 Création d’un instant avec la classe datetime

Les paramètres du constructeur sont : l’année, le mois, le jour, l’heure, les minutes, les secondes, les microsondes et le timezone. Les paramètres autre que le timezone sont des entiers et seuls l’année, mois et jour sont nécessaires.

La gestion des fuseaux horaires est assez complexe, elle sera abordée dans une partie dédiée. 

In [1]: from datetime import datetime

In [2]: datetime(2015, 12, 16)
Out[2]: datetime.datetime(2015, 12, 16, 0, 0)

L’objet créé avec uniquement l’année, mois et jour est donc un instant à la date donnée à minuit.

La classe datetime contient des méthodes de classe pour créer un instant autrement que par le passage de tous les paramètres. Ainsi, pour représenter l’instant présent, nous avons la méthode now(). 

In [3]: datetime.now()
Out[3]: datetime.datetime(2015, 12, 16, 14, 30, 13, 835376)

Notez qu’il existe également une méthode utcnow() qui crée un objet datetime en UTC. Pour cela, l’interpréteur se reposera sur les informations du système. L’objet datetime créé sera naïf. 

In [3]: datetime.now()
Out[3]: datetime.datetime(2015, 12, 16, 14, 30, 13, 835376)
In [4]: datetime.utcnow()
Out[4]: datetime.datetime(2015, 12, 16, 13, 30, 13, 835376)

Consultez la documentation pour les autres méthodes de classe.

14.2 Objet datetime et chaines de caractères

Pour convertir une chaine de caractères représentant une date en objet de type datetime ou inversement, la classe datetime dispose de deux méthodes : .strptime(date, format) et .strftime(format).

Le paramètre format est une chaine de caractère décrivant le format à lire ou à produire. Sa syntaxe est décrite dans la documentation :

https://docs.python.org/3.12/library/datetime.html#strftime-strptime-behavior

Pour lire une date à partir d’une chaine de caractères et générer un objet datetime, nous allons utiliser la méthode strptime(string, format). 

In [4]: date_object = datetime.strptime('Jun 1 2005  1:33PM', 
                                        '%b %d %Y %I:%M%p')

Pour créer une chaine de caractères représentant un objet datetime, nous utiliserons la méthode strftime(format). 

In [5]: date_object.strftime('%Y-%m-%d %H:%M:%S.%f')
Out[5]: '2005-06-01 13:33:00.000000'

Par défaut, la date est en anglais. Pour travailler avec les informations de date en français, il faut modifier la locale : 

In [6]: date_object.strftime('%a %d %B %Y')
Out[6]: 'Wed 01 June 2005'

In [7]: import locale

In [8]: locale.setlocale(locale.LC_TIME, 'fr_FR')
Out[8]: 'fr_FR'

In [9]: date_object.strftime('%a %d %B %Y')
Out[9]: 'Mer 01 juin 2005'

14.3 Les durées

Travailler avec des dates, c’est travailler avec les différences entre ces dates soit des durées. Elles sont représentées par des objets datetime.timedelta. La différence entre deux instants permet d’obtenir une durée. 

In [10]: duration = datetime(2017, 12, 15) - datetime(2015, 12, 16)

In [11]: print(duration)
730 days, 0:00:00

In [12]: duration
Out[12]: datetime.timedelta(730)

On peut évidemment instancier un objet de type timedelta et ainsi déterminer des instants à partir d’autres instants. 

In [13]: cuisson_oeuf = timedelta(seconds=180)

In [14]: datetime.now() + cuisson_oeuf
Out[14]: datetime.datetime(2017, 9, 10, 12, 12, 52, 776483)

Des opérations mathématiques sont donc possibles entre des objets de type datetime (soustraction), timedelta (addition et soustraction), datetime et timedelta (addition et soustraction) et timedelta et des valeurs numériques (multiplication et division).

Les objets de type timedelta ne sont pas adaptés à la représentation des durées (conversion en chaines de caractères human-friendly). Elles ne proposent pas de méthodes de parsing ou de formatage de la donnée. L’information de durée ne peut être extraite qu’en jour, secondes et microsecondes.

14.4 Jours et heures

Le module datetime propose des objets de type datetime.dateet datetime.time pour représenter une date (jour) et un instant dans une journée. Il est possible soit d’instancier spécifiquement ces données, soit de les extraire d’un objet de type datetime.

In [15]: ep8 = datetime(2017, 12, 15)

In [16]: ep8.date()
Out[16]: datetime.date(2017, 12, 15)

In [17]: from datetime import time

In [18]: show_time = time(20, 30)

In [19]: print(show_time)
20:30:00

14.5 Le temps : des objets immuables

Les informations liées au temps sont des données critiques. Afin de ne pas les altérer par accident, les objets sont immuables, ils ne peuvent pas être modifiés. La méthode replace() de la classe datetime permet de modifier un attribut de l’objet mais celle-ci retourne une copie de l’objet. 

In [20]: ep8.replace(year=2019, day=20)
Out[20]: datetime.datetime(2019, 12, 20, 0, 0)

In [21]: print(ep8)
2017-12-15 00:00:00

Si vous souhaitez modifier une donnée, il faut donc le faire explicitement.

14.6 Les fuseaux horaires

Jusqu’ici, nous avons utilisé des objets datetime sans fuseau horaire. Nous parlons alors de dates naïves. Des dates naïves ne peuvent pas réellement être comparées entre elles. La notion de timezone ou fuseau horaire, c’est le décalage de date en fonction du lieu géographique. Mais il y a également les notions politique dont le plus simple est le changement d’heure. Il faut donc ajouter la notion de fuseau horaire et le gérer.

14.6.1 Attribuer une timezone

Pour rendre les dates aware (traduit par avisées), il faut leur associer un timezone. Python propose depuis la version 3.9 une implémentation concrète de la base IANA issue de la PEP 615 : zoneinfo.

Note : 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.

14.6.2 Créer des dates aware

La première étape est de créer un objet ZoneInfo.

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)

14.6.3 Re-localiser une date

Si une date avisée est localisée dans un timezone. Pour la convertir à un autre timezone, il y a la méthode .astimezone().

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

14.6.4 Changement d’heure : une gestion calendaire

Python réalise ses calculs de temps entre dates avisées de manière calendaire :

>>> 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

14.6.5 Calcul des durées réelles

Pour calculer les durées réelles, il faut passer par une référence : UTC.

>>> 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

14.7 Le calendrier

14.7.1 Des fonctions et constantes utilitaires 

Le module calendar offre des fonctionnalités pour gérer un calendrier ou une date dans un calendrier. Le module propose avant tout un certain nombre de constantes et de fonction afin d’accéder à des données usuelles.

In [22]: import calendar
In [23]: calendar.EPOCH
Out[23]: 1970
In [24]: calendar.MONDAY
Out[24]: 0
In [25]: calendar.TUESDAY
Out[25]: 1
In [26]: calendar.January
Out[26]: 1

Vous pouvez voir avec le code ci-dessus que pour Python, lundi est représenté par la valeur 0 et janvier par 1. Les constantes proposées par le module sont donc utiles pour éviter toute confusion.

Le module calendar propose également des constantes et méthodes liées aux dates ou aux mois :

In [27]: calendar.mdays
Out[27]: [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]

In [28]: calendar.isleap(2018) # annee bissextile
Out[28]: False

In [29]: calendar.leapdays(2000, 2018)
Out[29]: 5

In [30]: calendar.weekday(2017, 12, 13)
Out[30]: 2

Le module calendar intègre également de quoi mettre en forme des informations de calendrier. 

In [31]: calendar.prmonth(2017, 12)
   décembre 2017
Lu Ma Me Je Ve Sa Di
             1  2  3
 4  5  6  7  8  9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31

Ces fonctions sont similaires à l’instruction cal du bash. L’instruction ci-dessus est équivalente à print(calendar.month(2017, 12)).

14.7.2 La classe Calendar

Le module calendar permet également de manipuler un calendrier sous forme d’objet, il propose en effet une classe Calendar. Celle-ci prend un paramètre qui est le premier jour de la semaine, par défaut c’est 0 soit lundi.

Cette classe fournit surtout des données liées au formatage. Elle ne fait pas de formatage en elle même, ceci est à déléguer aux classes spécialisées. 

In [43]: cal = calendar.Calendar()                                              

In [44]: print(list(cal.itermonthdays(2020, 3)))                                
[0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 0, 0, 0, 0, 0]

In [45]: print(list(cal.itermonthdays2(2020, 3)))                               
[(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (1, 6), (2, 0), (3, 1), (4, 2), (5, 3), (6, 4), (7, 5), (8, 6), (9, 0), (10, 1), (11, 2), (12, 3), (13, 4), (14, 5), (15, 6), (16, 0), (17, 1), (18, 2), (19, 3), (20, 4), (21, 5), (22, 6), (23, 0), (24, 1), (25, 2), (26, 3), (27, 4), (28, 5), (29, 6), (30, 0), (31, 1), (0, 2), (0, 3), (0, 4), (0, 5), (0, 6)]

In [46]: print(list(cal.itermonthdates(2020, 3)))                               
[datetime.date(2020, 2, 24), datetime.date(2020, 2, 25), datetime.date(2020, 2, 26), datetime.date(2020, 2, 27), datetime.date(2020, 2, 28), datetime.date(2020, 2, 29), datetime.date(2020, 3, 1), datetime.date(2020, 3, 2), datetime.date(2020, 3, 3), datetime.date(2020, 3, 4), datetime.date(2020, 3, 5), datetime.date(2020, 3, 6), datetime.date(2020, 3, 7), datetime.date(2020, 3, 8), datetime.date(2020, 3, 9), datetime.date(2020, 3, 10), datetime.date(2020, 3, 11), datetime.date(2020, 3, 12), datetime.date(2020, 3, 13), datetime.date(2020, 3, 14), datetime.date(2020, 3, 15), datetime.date(2020, 3, 16), datetime.date(2020, 3, 17), datetime.date(2020, 3, 18), datetime.date(2020, 3, 19), datetime.date(2020, 3, 20), datetime.date(2020, 3, 21), datetime.date(2020, 3, 22), datetime.date(2020, 3, 23), datetime.date(2020, 3, 24), datetime.date(2020, 3, 25), datetime.date(2020, 3, 26), datetime.date(2020, 3, 27), datetime.date(2020, 3, 28), datetime.date(2020, 3, 29), datetime.date(2020, 3, 30), datetime.date(2020, 3, 31), datetime.date(2020, 4, 1), datetime.date(2020, 4, 2), datetime.date(2020, 4, 3), datetime.date(2020, 4, 4), datetime.date(2020, 4, 5)]