CHAPITRE 3

Temps et argent

Introduction

Par Gustavo Niemeyer et Facundo Batista

Les termes « aujourd’hui », « le week-end dernier », « l’année prochaine », etc. nous sont très familiers. Vous vous êtes sûrement demandé au moins une fois comment nos vies sont liées à cette idée du temps. Le concept de temps nous entoure, c’est donc naturel qu’il apparaisse dans de très nombreux programmes. Même les plus simples d’entre eux peuvent devoir gérer des instants, des délais, des comptes à rebours, des compteurs de vitesse, des calendriers, etc. Comme il se doit avec tout langage généraliste fier d’être livré avec tous ses accessoires, la bibliothèque standard de Python dispose de moyens solides pour combler ce type de besoins et il existe également des modules et des paquetages tiers qui ajoutent d’autres fonctionnalités.

Les opérations de calcul monétaires sont également un sujet intéressant, qui mérite notre attention, car elles sont intimement liées à notre vie quotidienne. Python 2.4 a introduit le support des nombres flottants décimaux (vous pouvez également en bénéficier avec la version 2.3, voir http://www.taniquetil.com.ar/facundo/bdvfiles/get_decimal.html ), ce qui en fait un bon candidat lorsqu’il faut éviter les nombres flottants binaires, comme c’est souvent le cas lorsque l’on travaille sur des sommes d’argent.

Ce chapitre est consacré au temps et à l’argent. Selon l’ancien dicton, on pourrait peut-être prétendre qu’il ne traite en fait que d’un seul sujet puisque, comme chacun sait « le temps c’est de l’argent ! ».

Le module time

Le module time de la bibliothèque standard de Python accède à une bonne partie des fonctionnalités temporelles offertes par la plate-forme sur laquelle il s’exécute. La documentation de votre système concernant les fonctions équivalentes de la bibliothèque C, ainsi que la connaissance des spécificités des différentes plates-formes vous seront par conséquent utiles.

Parmi les fonctions du module time, celle qui permet d’obtenir l’instant présent, time.time, est l’une des plus utilisées. La valeur qu’elle renvoie peut sembler un peu sibylline aux non initiés : c’est un nombre flottant correspondant au nombre de secondes écoulées depuis un instant bien précis, appelé epoch. Ce point de repère peut varier selon les plates-formes, mais il s’agit généralement du premier janvier 1970 à minuit.

Pour connaître l’epoch utilisé par votre système, tapez les instructions suivantes à l’invite de l’interpréteur interactif de Python :

 >>> import time
 >>> print time.asctime(time.gmtime(0))

Vous remarquerez que nous passons 0 (ce qui signifie 0 secondes après l’epoch) à la fonction time.gmtime. Celle-ci convertit n’importe quel instant en un tuple représentant ce moment précis sous une forme lisible, sans appliquer aucune conversion de fuseau horaire (GMT signifie « Greenwich mean time », un ancien terme désignant ce que l’on appelle désormais UTC, « Coordinated Universal Time »). Si vous souhaitez prendre en compte le décalage de votre fuseau local, utilisez plutôt la fonction time.localtime en lui passant le nombre de secondes écoulées depuis l’epoch.

Il est important de comprendre cette différence. En effet, si votre instant est déjà exprimé en heure locale, le passer à time.localtime ne produira pas le résultat attendu — sauf si vous avez la chance que votre fuseau horaire coïncide avec la zone horaire UTC, évidemment !

Voici comment décomposer un tuple représentant l’heure locale :

 annee, mois, jour_mois, heures, minutes, secondes, jour_sem, jour_an, dst =
              time.localtime()

Bien qu’il soit correct, ce code n’est pas très élégant et il ne serait certainement pas pratique à utiliser régulièrement. Vous pouvez éviter ce type de construction en utilisant des noms d’attributs évocateurs4 pour accéder aux différents éléments des tuples renvoyés par les fonctions temporelles. L’obtention du mois courant devient alors une expression simple et élégante :

 time.localtime().tm_mon

Vous remarquerez que nous n’avons passé aucun paramètre à localtime. Lorsque localtime, gmtime ou asctime sont appelées sans paramètre, elles utilisent toutes l’heure courante par défaut.

strftime et strptime sont deux fonctions très utiles du module time : la première permet de construire une chaîne à partir d’un tuple temporel, la seconde fait l’inverse et produit un tuple temporel à partir d’une chaîne. Ces deux fonctions attendent une chaîne de format permettant de préciser très exactement ce que l’on souhaite obtenir dans la chaîne résultante (ou, respectivement, ce que l’on s’attend à trouver dans la chaîne passée en paramètre). La page http://docs.python.org/lib/module-time.html présente toutes les spécifications de format utilisables par ces fonctions.

Le module time contient une autre fonction importante : time. sleep, qui permet de créer des délais dans les programmes Python. Bien que l’équivalent POSIX de cette fonction n’accepte qu’un paramètre entier, la version Python permet d’utiliser un nombre flottant afin d’autoriser des délais exprimés en fractions de secondes. Voici un exemple :

 for i in range(10):
 time.sleep(0.5)
 print "Clic !"

Ce morceau de code mettra environ 5 secondes pour s’exécuter et affichera Clic ! approximativement deux fois par seconde.

Objets temps et dates

Bien que le module time soit assez pratique, la bibliothèque standard contient également le module datetime, qui fournit de meilleures abstractions pour les concepts de date et d’heure grâce aux types time, date et datetime. La syntaxe pour construire des instances de ces types est assez claire :

 aujourd_hui = datetime.date.today()
 anniv = datetime.date(1977, 5, 4) # Le 4 mai 1977
 heure_courante = datetime.datetime.now().time()
 midi = datetime.time(12, 00)
 maintenant = datetime.datetime.now()
 epoch = datetime.datetime(1970, 1, 1)
 reunion = datetime.datetime(2005, 8, 3, 15, 30)

En outre, comme vous pouvez vous y attendre, les attributs et les méthodes de ces instances offrent un accès commode aux informations, ainsi que des opérations utiles. Les instructions suivantes, par exemple, créent une instance du type date représentant le jour courant puis obtiennent la même date de l’année suivante et, enfin, affichent le résultat sous la forme d’un triplet pointé :

 aujourd_hui = datetime.date.today()
 annee_prochaine = aujourd_hui.replace(year = aujourd_hui.year +
            1).strftime("%Y.%m.%d")
 print annee_prochaine

Vous remarquerez que l’on a incrémenté l’année à l’aide de la méthode replace. Il pourrait être tentant de modifier les attributs des instances de dates et d’heures, mais elles ne sont pas modifiables (ce qui est une bonne chose car cela signifie que l’on peut les utiliser dans un ensemble ou comme clés d’un dictionnaire !) : il faut donc créer de nouvelles instances au lieu de modifier celles qui existent déjà.

Le type timedelta du module datetime fournit également un support minimal pour les écarts de temps (des différences entre deux instants, ce qui équivaut grossièrement à des durées). Ce type permet de modifier une date donnée en l’incrémentant ou en la décrémentant d’une tranche de temps. On obtient également une valeur de ce type lorsque l’on fait la différence entre des temps ou des dates.

 >>> import datetime
 >>> nouvelAn = datetime.date(2005, 01, 01)
 >>> reveillon = datetime.date(2004, 12, 31)
 >>> unJour = nouvelAn - reveillon
 >>> print unJour
 1 day, 0:00:00
 >>>

En coulisses, une instance timedelta est représentée par un nombre de jours, de secondes et de micro-secondes mais vous pouvez en construire une en passant l’un de ces paramètres ainsi que d’autres multiplicateurs, comme des minutes, des heures et des semaines. On ne peut pas représenter d’autres types d’écarts (en mois et en années) — c’est volontaire car leurs significations et les résultats des opérations sont discutables (cette fonctionnalité est cependant offerte par le paquetage tiers dateutil — voir http://labix.org/python-dateutil.)

La conception de datetime peut être qualifiée de prudente. La décision de ne pas implémenter des opérations douteuses ou qui pourraient nécessiter des implémentations différentes selon les systèmes reflète la stratégie de développement globale de ce module. Il offre ainsi de bonnes interfaces dans la plupart des cas usuels et, ce qui est encore plus important, une base solide et cohérente pour les modules tiers construits au-dessus de lui.

La gestion des fuseaux horaires est également un domaine où la stratégie de conception prudente de datetime éclate au grand jour. Bien qu’il offre des méthodes pour interroger et initialiser les...