Introduction à DateTime¶

Définir ce qu'est une date est toujours un peu compliqué. En dessous de la seconde on compte en base 10, puis pour les secondes et minutes on compte en base 60, pour les heures on passe en base 24 et ensuite pour les jours cela peut varier suivant les ans. Pour être encore plus précis il existe des années durant lesquelles on ajoute 1 seconde entre le 31 décembre à minuit et le 1er janvier à 0h. À cela il faut ajouter la notion de fuseaux horaires. Bref créer un objet DateTime est compliqué et malheureusement son utilisation l'est un peu aussi.

  1. Définir une date/heure
  2. Définir un intervalle de temps

Définir une date/heure¶

Pour commencer il faut charger la bibliothèque datetime. Dans cette bibliothèque le premier objet qui nous intéresse est datetime.

In [1]:
import datetime

bastille_day = datetime.datetime(1789,7,14)

print('bastille day:', bastille_day)
print('now it is:', datetime.datetime.now())

bastille_day < datetime.datetime.now()
bastille day: 1789-07-14 00:00:00
now it is: 2020-04-14 11:49:56.008806
Out[1]:
True

On peut aussi utiliser des chaînes de caractères ce qui est utile lorsqu'on lit un fichier. Pour cela on utilise la bibliothèque dateutil qui est une extension de datetime :

In [2]:
import dateutil

bastille_day = dateutil.parser.parse('14/07/1789')
bastille_attack = dateutil.parser.parse('1789-07-14 15:30:00')  
bastille_attack = dateutil.parser.parse('1789-07-14T15:30:00')  # ISO 8601
xmas_NY = dateutil.parser.parse('2018-12-24 23:59:59-05:00')    # UTC - 5

print('Get information:')
print(bastille_day.year, bastille_attack.weekday(), xmas_NY.hour)
print('\nExtract date and time:')
print(bastille_attack.date(), '---', bastille_attack.time())
Get information:
1789 1 23

Extract date and time:
1789-07-14 --- 15:30:00

Il est possible de lire et écrire des dates dans des formats quelconques en indiquant le format avec les directives décrites dans strftime-strptime-behavior de la documentation.

In [3]:
# string parse time
datetime.datetime.strptime('03/01/17, 10h03 PM', '%m/%d/%y, %Ih%M %p')
Out[3]:
datetime.datetime(2017, 3, 1, 22, 3)
In [4]:
# string from time
datetime.datetime.strftime(xmas_NY, 'It is %A the %dth of %B of the year %Y.')
Out[4]:
'It is Monday the 24th of December of the year 2018.'

Note : parfois on importe datetime avec l'alias dt (import datetime as dt) mais cela peut poser des problèmes car dt est parfois utilisé comme variable pour définir un intervalle de temps. Donc on redéfinit dt sans s'en rendre compte et tout est cassé.

Il est aussi possible de connaitre le jour de la semaine ou la semaine de l'année d'une date :

In [5]:
print(bastille_day.weekday())     # Tuesday (Monday is 0)
print(bastille_day.isoweekday())  # Tuesday (Monday is 1)
1
2
In [6]:
bastille_day.isocalendar()  # year, week of the year, day of the week
Out[6]:
(1789, 29, 2)

Intervalle de temps¶

Additionner deux dates n'a pas de sens par contre ajouter du temps à une date en a. Aussi nous utilisons un intervalle de temps ou timedelta.

In [7]:
battle_duration = datetime.timedelta(hours=1, minutes=30) # we cannot add more than days
print('Battle duration: %s or %s seconds' % (battle_duration, battle_duration.total_seconds()))

end_bastille_attack = bastille_attack + battle_duration
print('Battle end:', end_bastille_attack)
Battle duration: 1:30:00 or 5400.0 seconds
Battle end: 1789-07-14 17:00:00
In [8]:
20 * battle_duration  # 30 hours = 1 day and 21600 seconds
Out[8]:
datetime.timedelta(1, 21600)

Pour ajouter un mois ou un an, il faut utiliser relativedelta de dateutil.relativedelta. Attention à bien mettre le 's' à la fin de months ou years sinon c'est considéré comme une information absolue à savoir month=2 veut dire février.

In [9]:
print(bastille_attack + dateutil.relativedelta.relativedelta(months=1))
1789-08-14 15:30:00

On doit toujours manipuler ensemble des dates de même type, avec fuseau horaire (aware) ou sans (naive).

In [10]:
now_UTC = datetime.datetime.now(datetime.timezone.utc)
xmas_NY - now_UTC  # result is (days, seconds, microseconds)
Out[10]:
datetime.timedelta(-477, 69002, 630841)

Attention : ajouter un intervalle de temps à une heure n'a pas de sens car si on dépasse minuit on devrait changer de jour mais l'objet time n'a pas la notion de jour :

In [11]:
bastille_attack.time() + battle_duration
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-11-5ccb98975b56> in <module>
----> 1 bastille_attack.time() + battle_duration

TypeError: unsupported operand type(s) for +: 'datetime.time' and 'datetime.timedelta'

En résumé on peut faire :

  • timedelta1 + timedelta2

  • timedelta1 - timedelta2

  • timedelta1 / timedelta2

  • timedelta1 % timedelta2

  • i * timedelta1

  • datetime2 = datetime1 + timedelta1

  • datetime2 = datetime1 - timedelta1

  • timedelta1 = datetime2 - datetime1

Epoch time¶

Sous Unix la date est comptée en seconde depuis le 1er janvier 1970 temps universel (UTC). Ainsi une date est un réel et il devient très simple de faire des opérations sur les dates.

Ce système de date est dans la bibliothèque time.

In [12]:
import time

print(time.time())  # now
print(time.ctime())
print(time.localtime())
print(time.gmtime())
1586857796.5689988
Tue Apr 14 11:49:56 2020
time.struct_time(tm_year=2020, tm_mon=4, tm_mday=14, tm_hour=11, tm_min=49, tm_sec=56, tm_wday=1, tm_yday=105, tm_isdst=1)
time.struct_time(tm_year=2020, tm_mon=4, tm_mday=14, tm_hour=9, tm_min=49, tm_sec=56, tm_wday=1, tm_yday=105, tm_isdst=0)

On l'utilise souvent pour chronométrer une partie d'un programme :

In [13]:
start = time.time()
time.sleep(1.2)
end = time.time()
print("dt = %4.2f s" % (end - start))
dt = 1.20 s

Pour convertir une date exprimée en string au format de time on a strptime avec la structure de la date en 2e argument. L'inverse se fait avec strftime.

In [14]:
t = time.strptime('01/01/2000', '%m/%d/%Y')  # Beware, this is localtime
print(t)
print(time.mktime(t))   # to convert a struct_time in seconds
time.struct_time(tm_year=2000, tm_mon=1, tm_mday=1, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=5, tm_yday=1, tm_isdst=-1)
946681200.0

S'il est tentant d'utiliser ce temps en secondes pour sa simplicité, il a aussi ses limites. Ainsi tracer une courbe en fonction de dates en secondes est illisible alors qu'avec datetime les bibliothèques graphiques interprètent correctement les dates et font de jolis tracés.

Numpy et le temps¶

Numpy, que l'on verra plus tard, est une bibliothèque qui a aussi son système de temps. Lorsqu'on intègre des dates définies par datetime c'est relativement transparent ce qui fait qu'on peut utiliser Numpy sans entrer dans son système de date. Si maintenant on désire faire des calculs qui font intervenir le temps alors il faut s'y mettre car Numpy aura converti les datetime qu'on lui aura fournis en son format.

Numpy datetime propose donc :

  • numpy.datetime64 (64 car sur 64 bits et parcque datetime était déjà pris)
  • numpy.timedelta64

Une fois cela dit le reste marche comme pour datetime :

In [15]:
import numpy as np

np.datetime64('2005-02-25T03:30')
Out[15]:
numpy.datetime64('2005-02-25T03:30')
In [16]:
np.datetime64('2009') + np.timedelta64(20, 'D')
Out[16]:
numpy.datetime64('2009-01-21')

avec comme convention pour indiquer un intervalle de temps :

code sens
Y an
M mois
W semaine
D jour
h heure
m minute
s seconde
ms milliseconde

Cette convention sert aussi à définir des types plus grossiers de date. Ainsi datetime64[D] est une date au jour près et datetime64[m] à la minute près. Cela est utilisé lorsqu'on définit un dtype dans Numpy (pour arrondir une date ou pour avoir un ensemble de dates homogènes par exemple).

In [17]:
now = np.datetime64(datetime.datetime.now())
print(now) 
today = now.astype('datetime64[D]')
print(today)
2020-04-14T11:49:58.018705
2020-04-14

Numpy sait calculer le nombre de jours ouvrés :

In [18]:
np.busday_count(np.datetime64('2020'), today)
Out[18]:
74

{{ PreviousNext("10 sys - under the hood.ipynb", "12 xml.ipynb")}}

In [ ]: