Sam & Max » datetime http://sametmax.com Du code, du cul Sat, 07 Nov 2015 10:56:13 +0000 en-US hourly 1 http://wordpress.org/?v=4.1 Faire manger du datetime à JSON en Python 4 http://sametmax.com/faire-manger-du-datetime-a-json-en-python/ http://sametmax.com/faire-manger-du-datetime-a-json-en-python/#comments Sun, 09 Mar 2014 20:32:12 +0000 http://sametmax.com/?p=9731 La guerre de la sérialisation a plus ou moins été gagnée par JSON. Le XML est relégué aux documents très riches et aux systèmes legacy. Le YML est cantonné a des niches (et en plus souvent des niches en Ruby, c’est dire !). Et les formats binaires, sont gardés pour les besoins de perf. Le reste, c’est du JSON, du JSON et du JSON.

Seulement le JSON ne permet pas de sauvegarder des dates, seulement des chaînes, des entiers, des booléens et null. Heureusement on peut créer son propre dialecte au dessus de JSON pour y remédier, mais il faut avoir un un parseur qui le gère.

En Python on peut créer sa propre classe d’encodeur et décodeur de JSON et donc techniquement ajouter n’importe quel type.

Voici une recette pour en créer un qui gère le type datetime de manière transparente :

import re
import json
 
from datetime import datetime
 
# On hérite simplement de l'encodeur de base pour faire son propre encodeur
class JSONEncoder(json.JSONEncoder):
 
    # Cette méthode est appelée pour serialiser les objets en JSON
    def default(self, obj):
        # Si l'objet est de type datetime, on retourne une chaîne formatée
        # représentant l'instant de manière classique
        # ex: "2014-03-09 19:51:32.7689"
        if isinstance(obj, datetime):
            return obj.strftime('%Y-%m-%d %H:%M:%S.%f')
        return json.JSONEncoder.default(self, obj)
 
 
# On fait l'opération exactement inverse pour le décodeur
class JSONDecoder(json.JSONDecoder):
 
 
    # On écrase la méthode qui permet de décoder les paires clé / valeur
    # du format JSON afin que chaque valeur passe par notre moulinette
    def object_pairs_hook(self, obj):
        return dict((k, self.decode_on_match(v)) for k, v in obj)
 
 
    # notre moulinette
    def decode_on_match(self, obj):
 
        # une petite regex permet de savoir si la chaine est une date
        # sérialisée selon notre format précédent
        match = re.search(r'\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{6}', unicode(obj))
        # si oui, on parse et on retourne le datetime
        if match:
            return datetime.strptime(match.string, self.datetime_format)
 
        # sinon on retourne l'objet tel quel
        return obj
 
# On se fait des raccourcis pour loader et dumper le json
 
def json_dumps(data):
    return JSONEncoder().encode(data)
 
 
def json_loads(string):
    return JSONDecoder().decode(string)

Usage :

>>> res = json_dumps({'test': datetime(2000, 1, 1, 1, 1, 1), 'autre': [True, 1]})
>>> print(type(res), res)
(<type 'str'>, '{"test": "2000-01-01 01:01:01.000000", "autre": [true, 1]}')
>>> res = json_loads(res)
>>> print(type(res), res)
(<type 'dict'>, {u'test': u'2000-01-01 01:01:01.000000', u'autre': [True, 1]})

Minibelt contient une version un peu plus élaborée de ce code qui prend en compte les types date, time et timedelta ainsi que pas mal d’options de configuration.

]]>
http://sametmax.com/faire-manger-du-datetime-a-json-en-python/feed/ 4
Les time zones en Python 6 http://sametmax.com/les-time-zones-en-python/ http://sametmax.com/les-time-zones-en-python/#comments Tue, 15 Oct 2013 18:23:38 +0000 http://sametmax.com/?p=7448 arrow, une lib Python qui résout le problème]]> Si vous suivez un peu le blog, vous savez comment manipuler les dates en Python. Et c’est trop cool. Parce que le temps, c’est quelque chose de difficile en programmation, comme la gestion des monnaies ou la traduction d’une interface.

Mais notre article ne traite pas des time zones, c’est à dire de la gestion des fuseaux horaires. La raison est très simple, Sam est une grosse feignasse, et comme c’est un sujet super galère, il l’a soigneusement évité, vous renvoyant à l’usage de pytz. Démerdez vous.

Qu’est-ce qui a changé ? Le sujet est-il devenu plus simple ? Sam a-t-il un regain de motivation ? Le fait de parler de lui à la 3eme personne est-il signe de sénilité ?

Bien sûr que non, c’est simplement que je suis tombé sur arrow, une lib Python qui résout le problème. Pour faire simple, c’est presque exactement la lib que j’avais commencé à coder pour la gestion du temps. Sauf que le mec, lui, il l’a terminé, alors que la mienne, elle est au fin fond d’un repo git poussiéreux depuis 8 mois.

Bref, laissez tout tomber, n’installez plus dateutil, pytz et autres libs que j’ai pu vous recommander par le passé (elle sont incluses et abstraites par arrow). Faites juste :

pip install arrow

Tout commence par l’UTC

Comme pour la gestion des encodings, la gestion des dates se fait en utilisant une base commune. Pour l’encoding, c’est unicode, pour les dates, c’est UTC.

Donc, si vous prévoyez de gérer différents fuseaux horaires dans votre programme, TOUT votre programme – j’ai dit TOUT – doit manipuler exclusivement des dates en UTC.

Comme avec l’encoding, l’idée est la suivante :

  • A l’entrée de votre programme, vous acceptez des dates avec une timezone. Vous convertissez ces dates vers l’UTC.
  • Dans votre programme, vous manipuler uniquement de l’UTC.
  • A la sortie du programme, vous renvoyez des dates dans une timezone.

Ceci suppose donc que :

  • Vous savez ce qu’est une entrée et une sortie. Je vous fait un rappel : tout ce qui est pas dans votre programme (query base de données, socket, fichiers, print, input utilisateur, clic, affichage, etc.).
  • Vous DEVEZ savoir la timezone de ce qui rentre. Si vous ne le savez pas, vous ne pouvez absolument rien faire à part afficher à votre utilisateur que vous ne savez pas et le réglage par défaut . Pour la sortie, si vous ne savez pas, vous pouvez sortir de l’UTC par défaut, ça mange pas de pain. Mais pour l’entrée, il faut lire la spec, la doc, demandé au collègue, au fournisseur, au client, peut importe, il faut trouver l’info.

Avec arrow, c’est simplissime…

Si vous créez une date vous même, il suffit de toujours le faire en UTC :

>>> import arrow
>>> maintenant = arrow.utcnow() # heure et date actuelle en UTC
>>> print(maintenant)
2013-10-15T17:15:19.139000+00:00
>>> from datetime import datetime
>>> date_de_sortie_des_goonies = arrow.get(datetime(1985, 12, 4), 'US/Pacific')
>>> print(date_de_sortie_des_goonies)
1985-12-04T00:00:00-08:00
>>> date_de_sortie_des_goonies = date_de_sortie_des_goonies.to('utc')
>>> print(date_de_sortie_des_goonies)
1985-12-04T08:00:00+00:00, on a des dates uniquements en UTC.

Si vous importez une date, c’est très simple également :

>>> arrow.get(1367900664) # un timestamp marche..
<Arrow [2013-05-07T04:24:24+00:00]>
>>> arrow.get('1367900664') # ... même en string
<Arrow [2013-05-07T04:24:24+00:00]>
>>> arrow.get(1367900664.152325) # ... ou en float
<Arrow [2013-05-07T04:24:24.152325+00:00]>
>>> arrow.get('2013-09-30T15:34:00.000-07:00').to('utc') # parser une date standard...
<Arrow [2013-09-30T22:34:00+00:00]>
>>> from dateutil import tz # pour parser une date custo
>>> arrow.Arrow.strptime('2013-05-05 12:30:45', '%Y-%m-%d %H:%M:%S', tz.gettz('Paris/Europe'))
<Arrow [2013-05-05T12:30:45+00:00]>

Tout ça renvoie de l’UTC.

Et quand vous voulez afficher une date, vous pouvez tout simplement la reconvertir vers la timezone de votre choix avec la méthode to() et la formater comme avec format()

Stockage des dates

Quand on stocke une date (dans une base de données, un fichier, etc), il faut se demander quel est le but du stockage. Si le but est de stocker la date d’un événement, mettez la date en UTC. Un timestamp suffit, ou alors la représentation YYYY-MM-DD hh:mm:ss. Par contre, si vous voulez stocker une date liée à un événement et un lieu (par exemple un rendez-vous d’affaire ou le décollage d’un avion), stockez la date en UTC avec sa timezone dans un champ à côté, sinon vous serez bien baisé quand vous voudrez la ressortir.

Ah oui, et réglez toujours tous vos serveurs sur UTC. Toujours.

En bonus

On peut obtenir toutes les infos utiles sans tourner autour du pot :

>>> a = arrow.utcnow()
>>> print(a.datetime)
2013-10-15 17:48:28.335000+00:00
>>> print(a.timestamp)
1381859308
>>> print(repr(a.naive)) # on accès à l'objet datetime au cas où...
datetime.datetime(2013, 10, 15, 17, 49, 25, 515000)
>>> print(a.tzinfo) # ... et à l'objet tzinfo
tzutc()
>>> print(a.year, a.hour)
(2013, 17)

Et on peut faire des replacements et des calculs flous, comme avec dateutil :

>>> a = arrow.utcnow()
>>> print(a.format('DD, MMMM', locale='fr_FR'))
15, Octobre
>>> a = a.replace(day=30)
>>> print(a.format('DD, MMMM', locale='fr_FR'))
30, Octobre
>>> a = a.replace(days=+1)
>>> print(a.format('DD, MMMM', locale='fr_FR'))
31, Octobre
>>> a = a.replace(months=-1)
>>> print(a.format('DD, MMMM', locale='fr_FR'))
30, Septembre

Faire de l’affichage relatif :

>>> before = arrow.utcnow().replace(hours=-3)
>>> before.humanize(locale='Fr_fr'))
'il y a 3 heures'

Bref, arrow, c’est de la bombe baby !

]]>
http://sametmax.com/les-time-zones-en-python/feed/ 6
Manipuler les dates et les durées en Python 1 http://sametmax.com/manipuler-les-dates-et-les-durees-en-python/ http://sametmax.com/manipuler-les-dates-et-les-durees-en-python/#comments Wed, 26 Dec 2012 16:09:47 +0000 http://sametmax.com/?p=3875 datetimes et calendar, c'est déjà plus cool.]]> Manipuler des dates, c’est chaud. Le calendrier, c’est plein de subtilités comme les années bissextiles, les mois qui ont 4 possibilités de nombre de jours, les semaines qui commencent un dimanche aux US et un lundi en France… Autant dire que faire ça à la mano, c’est pas marrant, marrant.

Avec les modules datetimes et calendar, c’est déjà plus cool.

Date et heure

Datetime est un module qui permet de manipuler des dates et des durées sous forme d’objets. L’idée est simple: vous manipulez l’objet pour faire tous vos calculs, et quand vous avez besoin de l’afficher, vous formatez l’objet en chaîne de caractères.

On peut créer artificiellement un objet datetime, ses paramètres sont:

datetime(année, mois, jour, heure, minute, seconde, microseconde, fuseau horaire)

Mais seuls “année”, “mois” et “jour” sont obligatoires.

>>> from datetime import datetime
>>> datetime(2000, 1, 1)
datetime.datetime(2000, 1, 1, 0, 0)

Nous sommes ici le premier janvier 2000, à la seconde et la minute zéro, de l’heure zéro.

On peut bien entendu récupérer l’heure et la date du jour:

>>> maintenant = datetime.now()
>>> maintenant
datetime.datetime(2012, 12, 24, 18, 20, 4, 534918)
>>> maintenant.year
2012
>>> maintenant.month
12
>>> maintenant.day
24
>>> maintenant.hour
18
>>> maintenant.minute
20
>>> maintenant.second
4
>>> maintenant.microsecond
534918
>>> maintenant.isocalendar() # année, semaine, jour
(2012, 52, 2)

Enfin, si vous souhaitez uniquement vous occuper de la date ou de l’heure:

>>> from datetime import date, time, datetime
>>> date(2000, 1, 1)
datetime.date(2000, 1, 1)
>>> time(12, 12, 12)
datetime.time(12, 12, 12)
>>> maintenant = datetime.now()
>>> maintenant.date()
datetime.date(2012, 12, 25)
>>> maintenant.time()
datetime.time(15, 2, 16, 704000)

Notez que j’écris cet article le jour de Noël, en famille. Et que là mon frangin est en train d’aider ma mère à éplucher des pommes tandis que je les regarde de loin. C’est mal. Je vais leur donner un coup de main et je reviens.

*Sam épluche les pommes et s’efforce de se tenir éloigné du débat sur la condition des canards gavés pour leur foie. Il quitte la table discrètement dès le premier usage de wikipédia pour défendre les volatiles*

Back.

Je disais donc…

Durée

En plus de pouvoir récupérer la date du jour, on peut calculer la différence entre deux dates. Par exemple, combien de temps y a-t-il entre aujourd’hui et le premier jour de l’an 2000 ?

>>> duree = datetime.now() - datetime(2000, 1, 1)
>>> duree
datetime.timedelta(4741, 66999, 829132)

Et vous découvrez ici un autre objet: le timedelta. Cet objet représente une durée en jours, secondes et microsecondes.

>>> duree.days
4741
>>> duree.seconds
67007
>>> duree.mi
duree.microseconds  duree.min
>>> duree.microseconds
943760
>>> duree.total_seconds() # addition de toutes les secondes + les jours
409689407.94376

On peut créer son propre timedelta:

>>> timedelta(days=3, seconds=100)
datetime.timedelta(3, 100)

Cela permet de répondre à la question : “Quelle date serons-nous dans 2 jours, 4 heures, 3 minutes, et 12 secondes ?”:

>>> datetime.now() + timedelta(days=2, hours=4, minutes=3, seconds=12)
datetime.datetime(2012, 12, 26, 22, 44, 6, 251071)

A chaque fois, un nouvel objet est retourné. En effet, les objets datetime et timedelta sont immutables. Ainsi si vous voulez utiliser une version légèrement différente d’un objet datetime, il faudra toujours en créer un nouveau. Par exemple:

>>> maintenant = datetime.now()
>>> maintenant.replace(year=1995) # on créer un nouvel objet
datetime.datetime(1995, 12, 25, 0, 9, 46, 185000)

Vous noterez que je ne parles pas de fuseau horaire. Et bien c’est parce que l’implémentation Python est particulièrement ratée : l’API est compliquée et les données ne sont pas à jour. Il faut dire que la mesure du temps, contrairement à ce qu’on pourrait penser, n’est pas vraiment le truc le plus stable du monde, et des pays changent régulièrement leur manière de faire.

Donc je trollerai sur fuseaux dans un article plus gluant sur lequel, une fois n’est pas coutume, je taclerai Python. Restons sur les dates pures.

Autre modules

Une fois que vous maitrisez datetime, vous pouvez retourner lire notre article sur le formatage de date.

Mais ce n’est pas tout, il y d’autres modules liés à la gestion du temps en Python.

Dans la lib standard, il y a calendar.

Il permet de manipuler un calendrier comme un objet, et itérer sur les jours d’un mois, les semaines, vérifier les caractéristiques d’un jour en particulier, etc. :

>>> import calendar
>>> calendar.mdays # combien de jour par mois ?
[0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
>>> calendar.isleap(2000) # est-ce une année bissextile ?
True
>>> calendar.weekday(2000, 1, 1) # quel jour était cette date ?
5
>>> calendar.MONDAY, calendar.TUESDAY, calendar.WEDNESDAY, calendar.THURSDAY, calendar.FRIDAY, calendar.SATURDAY, calendar.SUNDAY 
(0, 1, 2, 3, 4, 5, 6)

On peut instancier un calendrier et itérer dessus:

>>> cal = calendar.Calendar()
>>> cal.getfirstweekday()
0
>>> list(cal.iterweekdays())
[0, 1, 2, 3, 4, 5, 6]
>>> list(cal.itermonthdays(2000, 1)) 
[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, 0]
>>> list(cal.itermonthdates(2000, 1))
[datetime.date(1999, 12, 27), datetime.date(1999, 12, 28), datetime.date(1999, 12, 29), datetime.date(1999, 12, 30), datetime.date(1999, 12, 31), datetime.date(2000, 1, 1), datetime.date(2000, 1, 2), datetime.date(2000, 1, 3), datetime.date(2000, 1, 4), datetime.date(2000, 1, 5), datetime.date(2000, 1, 6), datetime.date(2000, 1, 7), datetime.date(2000, 1, 8), datetime.date(2000, 1, 9), datetime.date(2000, 1, 10), datetime.date(2000, 1, 11), datetime.date(2000, 1, 12), datetime.date(2000, 1, 13), datetime.date(2000, 1, 14), datetime.date(2000, 1, 15), datetime.date(2000, 1, 16), datetime.date(2000, 1, 17), datetime.date(2000, 1, 18), datetime.date(2000, 1, 19), datetime.date(2000, 1, 20), datetime.date(2000, 1, 21), datetime.date(2000, 1, 22), datetime.date(2000, 1, 23), datetime.date(2000, 1, 24), datetime.date(2000, 1, 25), datetime.date(2000, 1, 26), datetime.date(2000, 1, 27), datetime.date(2000, 1, 28), datetime.date(2000, 1, 29), datetime.date(2000, 1, 30), datetime.date(2000, 1, 31), datetime.date(2000, 2, 1), datetime.date(2000, 2, 2), datetime.date(2000, 2, 3), datetime.date(2000, 2, 4), datetime.date(2000, 2, 5), datetime.date(2000, 2, 6)]
>>> cal.monthdayscalendar(2000, 1)
[[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, 0]]

Comme souvent Python vient aussi avec de très bons modules tierces parties pour manipuler les dates:

  • dateutils est un datetime boosté aux hormones qui permet notamment de donnée des durées floues comme “+ 1 mois” et gérer des événements qui se répètent. Il est dans tous mes projets par défaut.
  • babel n’est pas spécialisé dans les dates mais dans la localisation. Le module possède des outils pour formater des dates selon le format de chaque pays, et aussi avec des formats naturels comme “il y a une minute”.
  • pytz est une implémentation saine de gestion des fuseaux horaires en Python. On y reviendra.
]]>
http://sametmax.com/manipuler-les-dates-et-les-durees-en-python/feed/ 1
Sérialiser et parser une date en Python: formats de strftime/strptime et timestamps 8 http://sametmax.com/serialiser-et-parser-une-date-en-python-formats-de-strftimestrptime-et-timestamps/ http://sametmax.com/serialiser-et-parser-une-date-en-python-formats-de-strftimestrptime-et-timestamps/#comments Mon, 23 Jul 2012 12:00:22 +0000 http://sametmax.com/?p=1272 même format.]]> Ça fait 10 ans que j’en fais, et je ne me souviens jamais des lettres à utiliser pour les formats. Pourtant j’utilise tout le temps le même format: celui de MySQL et JSON par défaut, avec en plus les microsecondes, car l’ordre alphanumérique de ce format est le même que l’ordre chronologique, ce qui rend son traitement facile.

Je le met ici pour l’avoir toujours sous la main. Marre de chercher si truc est en minuscule, et machin en majuscule. Je me fais baiser à chaque fois.

Sérialiser une date pour avoir: année, mois, jour, heure, minute, seconde, microseconde :

>>> import datetime
>>> datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')
>>> '2012-07-22 16:19:00.539570'

A l’inverse, parser une date:

>>> datetime.datetime.strptime('2012-07-22 16:19:00.539570', '%Y-%m-%d %H:%M:%S.%f')
>>> datetime.datetime(2012, 7, 22, 16, 19, 0, 539570)

Tous les formats sont listés ici.

Et tant qu’on y est, le snippet de manipulation de timestamp (qui pour le fun, n’est pas par défaut dans le module datetime):

>>> import calendar
>>> calendar.timegm(d.utctimetuple()) # d'un objet date vers un timestamp
>>> 1342973940
>>> datetime.datetime.fromtimestamp(1342973940) # et l'inverse
>>> datetime.datetime(2012, 7, 22, 18, 19)

Le timestamp est un timestamp basé sur une epoch of 1970 et un format POSIX, avec toute ce que ça implique, et notamment le fait qu’on perd les microsecondes.

]]>
http://sametmax.com/serialiser-et-parser-une-date-en-python-formats-de-strftimestrptime-et-timestamps/feed/ 8