26 août 2016

(Datetime) Le temps en Python I : les types de base

Design & Code

Robin

Huart

Image developpeur.

26 août 2016

(Datetime) Le temps en Python I : les types de base

Design & Code

Robin

Huart

Image developpeur.

26 août 2016

(Datetime) Le temps en Python I : les types de base

Design & Code

Robin

Huart

Image developpeur.
  • Cible : Cet article s’adresse aux débutants en Python, c'est-à-dire soit des développeurs juniors soit des profils plus proches du business qui font un peu de Python sans trop connaître.

  • Objectifs/Enjeux métiers : La manipulation des dates et du temps côté métier est utilisée quotidiennement, pas forcément pour faire des choses compliquées. Par exemple pour les courbes de prix qui sont indexées dans le temps, les courbes de quantités de gaz ou d’électricité, ou de bien de manière générale. De même, dans d’autres domaines on utilise souvent des données temporelles. Dès que l’on commence à développer, à coder sur ce type de données on se retrouve à manipuler du temps d’une manière ou d’une autre. Il faudra croiser des données dans le temps et obtenir des indicateurs. Typiquement quand un business analyst commence à faire des manipulations compliquées avec ses données, il peut avoir à faire des transformations de données temporelles : journalier, demi-journalier… Agréger des données différentes qui ne sont pas dans les mêmes timezone, ect…

0. Avant-propos

Datetime Python - La référence au temps en programmation est quelque chose qui devient généralement souhaitable assez rapidement, ne serait-ce que pour dater des messages de log par exemple. Python est également très présent, c’est bien connu, dans de nombreuses applications centrées sur la manipulation de « la data ». On y rencontre généralement très tôt des séries temporelles sur lesquelles on va souhaiter faire tout un tas de manipulations plus ou moins complexes. Il est alors crucial de pouvoir effectuer facilement les opérations les plus génériques.

Un certain nombre d’outils existent en Python pour adresser cette problématique, trop pour que nous puissions tous les présenter ici. En outre, c’est un sujet plus complexe qu’il n’y paraît de prime abord et nombre de développeurs à leurs débuts (y compris moi) ont pu être tentés par des simplifications trompeuses.

Cet article est le premier d’une série consacrée à ce sujet. Il est l’occasion d’une introduction à la façon de représenter et de manipuler les dates et le temps dans le module de la bibliothèque standard dédié : datetime.

1. Présentation des Datetime Python

Ce module contient tous les outils de base pour représenter le temps. Trois types d’objets (classes) y sont définis pour ce faire :

  • Les dates avec la classe… date, tout simplement

  • Le « temps » avec la classe time, qui signifie en fait le temps infra-journalier (c’est-à-dire le moment de la journée)

  • La combinaison des deux, un repère temporel complet avec la date et l’heure de la journée, qu’on appelle logiquement… datetime, comme le module lui-même.

Même si ces noms ne respectent pas la convention de nommage usuelle (PascalCase), il s’agit bien de classes. Les paramètres de construction sont assez évidents :

  • Pour créer la date du 14 Juillet 2020 :

>>> import datetime as dt
>>>
>>> some_date = dt.date(2020, 7, 14)  # année, mois, jour
>>> str(some_date)
'2021-07-14'
  • Pour créer 8h 25m 37s :

>>> import datetime as dt
>>>
>>> some_time = dt.time(8, 25, 37)  # heures, minutes, secondes
>>> str(some_time)
'08:25:37'
  • Pour créer la combinaison des deux, autrement dit le 14 Juillet 2020 à 8h25 et 37s :

>>> import datetime as dt
>>>
>>> some_datetime = dt.datetime(2020, 7, 14, 8, 25, 37)
>>> str(some_datetime)
'2020-07-14 08:25:37'

Pour une date, les trois paramètres sont obligatoires. En ce qui concerne la partie « temps » en revanche, aucun ne l’est. Par défaut, si l’heure, les minutes et/ou les secondes ne sont pas précisées, elles valent 0. Illustration :

>>> str(dt.time(8))
'08:00:00'
>>> str(dt.datetime(2020, 7, 14))
'2020-07-14 00:00:00'
>>> str(dt.datetime(2020, 7, 14, 8))
'2020-07-14 08:00:00'
>>> str(dt.datetime(2020, 7, 14, 8, 25))
'2020-07-14 08:25:00'

2. Les opérations basiques

Ces classes de base définissent un certain nombre d’opérateurs bien pratiques. En premier lieu, les opérateurs de comparaison :

>>> dt.date(2020, 1, 1) == dt.date(2020, 1, 1)
True
>>> dt.date(2020, 1, 1) >= dt.date(2020, 1, 1)
True
>>> dt.date(2020, 1, 1) < dt.date(2020, 1, 1)
False
>>> dt.date(2020, 1, 1) < dt.date(2020, 1, 2)
True
>>> dt.time(8) >= dt.time(8, 1)
False
>>> dt.time(8) < dt.time(8, 1)
True
>>> dt.datetime(2020, 7, 14, 8, 25) <= dt.datetime(2020, 7, 30, 8)
True
>>> dt.datetime(2020, 7, 14, 8, 25) > dt.datetime(2020, 7, 30, 8)
False

Il est également possible d’effectuer quelques opérations arithmétiques de base, comme calculer la durée séparant deux datetimes :

>>> dt.datetime(2020, 7, 16, 12, 10) - dt.datetime(2020, 7, 14, 8, 25)
datetime.timedelta(days=2, seconds=13500)
>>> str(dt.datetime(2020, 7, 16, 12, 10) - dt.datetime(2020, 7, 14, 8, 25))
'2 days, 3:45:00'

La différence entre deux dates génère un objet d’un nouveau type, timedelta, qui représente une durée. De façon symétrique, on peut construire nos propres durées pour les additionner à des datetimes :

>>> str(dt.datetime(2020, 7, 14, 8, 25) + dt.timedelta(days=2, hours=3, minutes=45))
'2020-07-16 12:10:00'

Ces additions et différences sont également définies pour les dates, mais pas pour les objets de type time.

3. Formatage et interprétation

Nous avons vu que la transformation en chaînes de caractères par défaut correspond à un style anglo-saxon assez classique. Selon les endroits du monde, d’autres styles de représentation textuelle peuvent être préférés. Pour cela, la classe datetime Python met à disposition la méthode strftime qui prend pour unique argument une chaîne de caractères représentant le format à utiliser, à assembler à l’aide d’un mini-langage dédié dont la documentation est très claire. Quelques exemples :

>>> dt.datetime(2020, 7, 14, 8, 25).strftime("%Y-%m-%d %H:%M:%S")
'2020-07-14 08:25:00'
>>> dt.datetime(2020, 7, 14, 8, 25).strftime("%d/%m/%Y %Hh%Mm%Ss")
'14/07/2020 08h25m00s'
>>> dt.datetime(2020, 7, 14, 8, 25).strftime("le %d/%m/%Y à %Hh%M")
'le 14/07/2020 à 08h25'
>>> dt.datetime(2020, 7, 14, 8, 25).strftime("%A, %B %dth %Y, %I:%M %p")
'Tuesday, July 14th 2020, 08:25 AM'

Comme le précise la doc, cette méthode est aussi disponible pour les classes date et time avec le même mini-langage. Dans ce cas, les éventuels éléments de format non pertinents (par exemple le format d’année %Y pour un objet de type time) ne génèrent pas d’exception car des valeurs par défaut sont utilisées (comme l’année 1900).

L’opération inverse de strftime, à savoir interpréter une chaîne de caractères pour la transformer en objet Python, s’appelle le parsing. Les classes du module datetime définissent une méthode de classe nommée strptime à cette fin, qui s’appuie sur le même mini-langage. Elle requiert logiquement deux arguments, le texte à analyser et le format auquel ce texte est censé être conforme. Reprenons les exemples précédents dans le sens inverse :

>>> dt.datetime.strptime("2020-07-14 08:25:00", "%Y-%m-%d %H:%M:%S")
datetime.datetime(2020, 7, 14, 8, 25)
>>> dt.datetime.strptime("14/07/2020 08h25m00s", "%d/%m/%Y %Hh%Mm%Ss")
datetime.datetime(2020, 7, 14, 8, 25)
>>> dt.datetime.strptime("le 14/07/2020 à 08h25", "le %d/%m/%Y à %Hh%M")
datetime.datetime(2020, 7, 14, 8, 25)
>>> dt.datetime.strptime("Tuesday, July 14th 2020, 08:25 AM", "%A, %B %dth %Y, %I:%M %p")
datetime.datetime(2020, 7, 14, 8, 25)

Si vous souhaitez approfondir le sujet des Datetime en Python, nous pouvons vous accompagner ! Rapprochez-vous de notre expertise Python et de nos experts pour plus de renseignements.

4. Quelques autres méthodes utiles Datetime Python

Il est possible d’extraire les parties date et time d’une datetime par des méthodes éponymes :

>>> dt.datetime(2020, 7, 14, 8, 25).date()
datetime.date(2020, 7, 14)
>>> dt.datetime(2020, 7, 14, 8, 25).time()
datetime.time(8, 25)

À l’inverse, une date et une heure de la journée peuvent être combinées pour donner une datetime à l’aide d’une méthode de classe :

>>> some_date = dt.date(2020, 7, 14)
>>> some_time = dt.time(8, 25)
>>> dt.datetime.combine(some_date, some_time)
datetime.datetime(2020, 7, 14, 8, 25)

Un formatage particulier suivant la norme ISO 8601 est possible avec la méthode isoformat :

>>> dt.datetime(2020, 7, 14, 8, 25).isoformat()
'2020-07-14T08:25:00'
>>> dt.date(2020, 7, 14).isoformat()
'2020-07-14'
>>> dt.time(8, 25).isoformat()
'08:25:00'

Potentiellement utile aussi, des méthodes de classes permettent de construire la date et l’heure courante :

>>> dt.date.today()
datetime.date(2021, 4, 6)
>>> dt.datetime.today()
datetime.datetime(2021, 4, 6, 2, 10, 7, 603991)
>>> dt.datetime.now()
datetime.datetime(2021, 4, 6, 2, 10, 16, 315196)

Il n’y a pas de méthode directe pour avoir simplement l’heure courante, mais elle s’obtient tout de même facilement en faisant :

>>> dt.datetime.now().time()
datetime.time(2, 13, 2, 256634)

Remarquez le dernier chiffre de l’ordre des centaines de milliers qui est le dernier composant des datetime et time générées, je ne les ai pas évoquées jusqu’à présent mais il s’agit des microsecondes. Elles font partie de la signature du constructeur des classes datetime et time.

Enfin, quand on manipule des durées, il est parfois utile de les convertir en secondes. Pour ce faire, il existe là encore une méthode, total_seconds :

>>> one_hour_duration = dt.datetime(2020, 7, 14, 9, 25) - dt.datetime(2020, 7, 14, 8, 25)
>>> one_hour_duration.total_seconds()
3600.0

Notez que la valeur de retour est un nombre réel, pas un entier. C’est simplement dû au fait que dans le cas général, comme on vient de le voir avec datetime.now() par exemple, la précision des mesures va jusqu’à la microseconde.

4. Le temps Unix

Il existe une autre façon de représenter le temps utilisée principalement par les systèmes d’exploitation, qu’on appelle le temps Unix (ou POSIX, mais la notion existe aussi sous Windows), et qu’on désigne aussi très souvent par le mot anglais timestamp. Il s’agit d’un nombre entier assez grand, qui compte le nombre de secondes qui se sont écoulées depuis une origine du temps arbitraire. Cette origine est désignée en anglais par le terme Epoch et est par convention le 1er Janvier 1970 à minuit pile (début de journée).

On retrouve ce temps notamment dans le retour de certaines fonctions utilisant directement les services du système d’exploitation, comme dans le module os. La fonction os.stat(path), par exemple, renvoie un tuple de taille 10 avec plusieurs informations. Le module stat propose des constantes permettant d’associer des noms, plus parlant que de simples indices, pour accéder aux différents éléments de ce tuple. Ainsi si je veux accéder à la date de dernier accès à mon répertoire « Documents », je peux faire :

>>> import os, stat
>>>
>>> os.stat("Documents")
os.stat_result(st_mode=16749, st_ino=844424930251873, st_dev=1188220516, st_nlink=1, st_uid=0, st_gid=0, st_size=4096, st_atime=1617836601, st_mtime=1617493877, st_ctime=1552969013)
>>> os.stat("Documents")[stat.ST_ATIME]
1617836081

Un humain préférera normalement avoir cette information sous forme de datetime. Pour effectuer cette transformation, la classe datetime Python propose encore une fois une méthode qui fait tout le travail :

>>> last_access_timestamp = os.stat("Documents")[stat.ST_ATIME]
>>> last_access_datetime = dt.datetime.fromtimestamp(last_access_timestamp)
>>> print(last_access_timestamp)
1617837212
>>> print(last_access_datetime)
2021-04-08 01:13:32

5. Conclusion

Nous venons de voir les bases permettant de commencer à manipuler les objets temporels. Les interfaces proposées par le module datetime Python sont assez simples et plaisantes à utiliser. Néanmoins, ceux qui ont déjà un peu abordé ce sujet auront remarqué que j’ai occulté la partie qui rend la question du temps réellement complexe : la localisation (c’est-à-dire la gestion des fuseaux horaires). C’est volontaire, car cette complexité supplémentaire fera l’objet du prochain article, qui supposera que les bases présentées ici sont globalement maîtrisées.