date – Sam & Max http://sametmax.com Du code, du cul Wed, 23 Dec 2020 13:35:02 +0000 en-US hourly 1 https://wordpress.org/?v=4.9.7 32490438 Faire manger du datetime à JSON en Python 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)
(, '{"test": "2000-01-01 01:01:01.000000", "autre": [true, 1]}')
>>> res = json_loads(res)
>>> print(type(res), res)
(, {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/ 6 9731
Manipuler les dates et les durées en Python 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/ 3 3875
Sérialiser et parser une date en Python: formats de strftime/strptime et timestamps 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/ 9 1272