Python, pytz et Django sont sur un bateau

Ce billet date de plusieurs années, ses informations peuvent être devenues obsolètes.

Python fait la distinction entre les objets datetime aware et naive et permet la création d’objets qui dérivent de tzinfo, mais ces derniers doivent être créés “à la main”.

Heureusement pytz dispose des infos sur tous les fuseaux horaires de la planète et aide à gérer les problèmes liés aux changements d’heures au prix de quelques différences avec les objets datetime tels que décrits dans la documentation Python. En effet pytz dispose de 2 mécanismes “fiables” pour construire des objets datetime aware :

  1. Instancier un objet pytz.timezone et utiliser sa méthode localize() sur un objet datetime naive
  2. convertir un objet datetime déjà aware avec la méthode de l’objet datetime.astimezone()

À propos des différences avec le manuel Python et de l’aveu même de l’auteur de pytz :

Yes, it sucks that the API is different to that described in the Python manuals

Ça c’est bien dit, et j’ajouterais même que ça suXe le panda décédé.

Si malgré tout vous persistez à utiliser datetime.replace(tzinfo=tz) avec des instances de pytz.timezone, et bien vous prenez un aller simple pour l’enfer :

In [1]: import datetime, pytz
In [2]: paris = pytz.timezone('Europe/Paris')
In [3]: now = datetime.datetime.now()
In [4]: now.replace(tzinfo=paris)
Out[4]: datetime.datetime(2013, 4, 4, 4, 23, 16, 981874, tzinfo=<DstTzInfo 'Europe/Paris' PMT+0:09:00 STD>)

Comment ? 'Europe/Paris' PMT+0:09:00 STD, vous vous foutez de ma gueule ?

What is happening is that there is not a single DstTzInfo for a time zone, but several; one for each distinct period. The __repr__ method is what we have to tell them apart.

[...]

At the moment, it returns the DstTzInfo for the first period in history.

Et oui, l’histoire des changements d’heures évolue constamment. Par exemple Medvedev l’a supprimé, mais peut-être que Poutine va le rétablir. Et si l’histoire des changements d’heures en France vous intéresse, vous pouvez faire une recherche avec le terme “heure legale” sur Legifrance.

J’en arrive maintenant à Django.

Le support des time zones est arrivé avec Django 1.4, voir It's about time!

S’il est activé, tous les champs datetime des formulaires sont interprétés dans la time zone courante via la méthode to_python de DateTimeField.

Ce qui est bien mais pas top !

Ce comportement est correct dans la majorité des cas mais devient faux dès l'instant où vous devez associer à une date un fuseau horaire différent du fuseau horaire actif.

Le problème se pose lors des DST transitions. Par exemple avec le setting TIME_ZONE défini à 'Europe/Paris', le 31-03-2013 02:00 (heure du passage à l’heure d’été en France) lèvera toujours une erreur alors que ce moment est parfaitement valide dans le fuseau horaire 'America/Los_Angeles'.

Une solution possible est alors d’écrire une nouvelle classe NaiveDateTimeField (sic) qui hérite de django.forms.fields.BaseTemporalField et qui n’interprète pas les datetime dans le fuseau horaire paramétré, puis de faire la validation (lever les exceptions pytz NonExistentTimeError et AmbiguousTimeError le cas échéant) et de les rendre aware en fonction des fuseaux horaires de destination dans la méthode clean() du formulaire.

Voilà, j’espère que vous aurez remarqué que je n’ai pas fait de blagues avec JCVD.

Allez, à + dans le bus ;)

Avant Fuseaux horaires et JavaScript Après Backbone.js, retour d’expérience

Tag Kemar Joint