web-dev-qa-db-fra.com

JSON datetime entre Python et JavaScript

Je souhaite envoyer un objet datetime.datetime sous forme sérialisée à partir de Python à l'aide de JSON et désérialiser en JavaScript à l'aide de JSON. Quelle est la meilleure façon de procéder?

377
kevin

Vous pouvez ajouter le paramètre 'default' à json.dumps pour gérer ceci:

date_handler = lambda obj: (
    obj.isoformat()
    if isinstance(obj, (datetime.datetime, datetime.date))
    else None
)
json.dumps(datetime.datetime.now(), default=date_handler)
'"2010-04-20T20:08:21.634121"'

Qui est ISO 8601 format. 

Une fonction de gestionnaire par défaut plus complète:

def handler(obj):
    if hasattr(obj, 'isoformat'):
        return obj.isoformat()
    Elif isinstance(obj, ...):
        return ...
    else:
        raise TypeError, 'Object of type %s with value of %s is not JSON serializable' % (type(obj), repr(obj))

Mise à jour: Sortie ajoutée de type ainsi que de valeur.
Mise à jour: gérer également la date 

364
JT.

Pour les projets multilingues, j'ai découvert que les chaînes contenant RfC 3339 / sont la meilleure solution. Une date RfC 3339 ressemble à ceci:

  1985-04-12T23:20:50.52Z

Je pense que la plupart du format est évident. La seule chose quelque peu inhabituelle peut être le "Z" à la fin. Il représente GMT/UTC. Vous pouvez également ajouter un décalage horaire comme +02: 00 pour CEST (Allemagne en été). Personnellement, je préfère tout conserver en UTC jusqu'à ce qu'il soit affiché.

Pour l'affichage, les comparaisons et le stockage, vous pouvez le laisser au format chaîne dans toutes les langues. Si vous avez besoin de la date de calcul, il est facile de la reconvertir en objet de date natif dans la plupart des langues.

Donc, générez le JSON comme ceci:

  json.dump(datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ'))

Malheureusement, le constructeur Date de Javascript n'accepte pas les chaînes RfC 3339 mais il existe de nombreux analyseurs disponibles sur Internet.

huTools.hujson tente de gérer les problèmes de codage les plus courants que vous pourriez rencontrer en code Python, notamment les objets date/date/heure, tout en gérant correctement les fuseaux horaires.

75
max

Je l'ai travaillé.

Supposons que vous avez un objet datetime Python, d , créé avec datetime.now (). Sa valeur est:

datetime.datetime(2011, 5, 25, 13, 34, 5, 787000)

Vous pouvez le sérialiser en JSON en tant que chaîne datetime ISO 8601:

import json    
json.dumps(d.isoformat())

L'exemple d'objet datetime serait sérialisé comme suit:

'"2011-05-25T13:34:05.787000"'

Cette valeur, une fois reçue dans la couche Javascript, peut construire un objet Date:

var d = new Date("2011-05-25T13:34:05.787000");

Depuis Javascript 1.8.5, les objets Date ont une méthode toJSON, qui renvoie une chaîne dans un format standard. Pour sérialiser l'objet Javascript ci-dessus en JSON, la commande serait donc:

d.toJSON()

Ce qui vous donnerait:

'2011-05-25T20:34:05.787Z'

Cette chaîne, une fois reçue en Python, pourrait être désérialisée en un objet datetime:

datetime.strptime('2011-05-25T20:34:05.787Z', '%Y-%m-%dT%H:%M:%S.%fZ')

Cela se traduit par l'objet datetime suivant, identique à celui avec lequel vous avez commencé et donc correct:

datetime.datetime(2011, 5, 25, 20, 34, 5, 787000)
63
user240515

Avec json, vous pouvez sous-classer JSONEncoder et remplacer la méthode default () pour fournir vos propres sérialiseurs personnalisés:

import json
import datetime

class DateTimeJSONEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime.datetime):
            return obj.isoformat()
        else:
            return super(DateTimeJSONEncoder, self).default(obj)

Ensuite, vous pouvez l'appeler comme ceci:

>>> DateTimeJSONEncoder().encode([datetime.datetime.now()])
'["2010-06-15T14:42:28"]'
49
ramen

Voici une solution assez complète pour le codage et le décodage récursifs des objets datetime.datetime et datetime.date à l'aide du module bibliothèque standard json. Cela nécessite Python> = 2.6, car le code de formatage %f dans la chaîne de formatage datetime.datetime.strptime () n'est pris en charge que depuis. Pour la prise en charge de Python 2.5, supprimez le %f et supprimez les microsecondes de la chaîne de date ISO avant d'essayer de les convertir, mais vous perdrez évidemment la précision en microsecondes. Pour assurer l'interopérabilité avec les chaînes de date ISO provenant d'autres sources, pouvant inclure un nom de fuseau horaire ou un décalage UTC, vous devrez peut-être également supprimer certaines parties de la chaîne de date avant la conversion. Pour un analyseur complet des chaînes de date ISO (et de nombreux autres formats de date), voir le module tiers dateutil .

Le décodage ne fonctionne que lorsque les chaînes de date ISO sont des valeurs dans une notation JavaScript Literal d'objet ou dans des structures imbriquées dans un objet. Date ISO Les chaînes, qui sont les éléments d'un tableau de niveau supérieur, ne seront pas décodées.

C'est à dire. cela marche:

date = datetime.datetime.now()
>>> json = dumps(dict(foo='bar', innerdict=dict(date=date)))
>>> json
'{"innerdict": {"date": "2010-07-15T13:16:38.365579"}, "foo": "bar"}'
>>> loads(json)
{u'innerdict': {u'date': datetime.datetime(2010, 7, 15, 13, 16, 38, 365579)},
u'foo': u'bar'}

Et cela aussi:

>>> json = dumps(['foo', 'bar', dict(date=date)])
>>> json
'["foo", "bar", {"date": "2010-07-15T13:16:38.365579"}]'
>>> loads(json)
[u'foo', u'bar', {u'date': datetime.datetime(2010, 7, 15, 13, 16, 38, 365579)}]

Mais cela ne fonctionne pas comme prévu:

>>> json = dumps(['foo', 'bar', date])
>>> json
'["foo", "bar", "2010-07-15T13:16:38.365579"]'
>>> loads(json)
[u'foo', u'bar', u'2010-07-15T13:16:38.365579']

Voici le code:

__all__ = ['dumps', 'loads']

import datetime

try:
    import json
except ImportError:
    import simplejson as json

class JSONDateTimeEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, (datetime.date, datetime.datetime)):
            return obj.isoformat()
        else:
            return json.JSONEncoder.default(self, obj)

def datetime_decoder(d):
    if isinstance(d, list):
        pairs = enumerate(d)
    Elif isinstance(d, dict):
        pairs = d.items()
    result = []
    for k,v in pairs:
        if isinstance(v, basestring):
            try:
                # The %f format code is only supported in Python >= 2.6.
                # For Python <= 2.5 strip off microseconds
                # v = datetime.datetime.strptime(v.rsplit('.', 1)[0],
                #     '%Y-%m-%dT%H:%M:%S')
                v = datetime.datetime.strptime(v, '%Y-%m-%dT%H:%M:%S.%f')
            except ValueError:
                try:
                    v = datetime.datetime.strptime(v, '%Y-%m-%d').date()
                except ValueError:
                    pass
        Elif isinstance(v, (dict, list)):
            v = datetime_decoder(v)
        result.append((k, v))
    if isinstance(d, list):
        return [x[1] for x in result]
    Elif isinstance(d, dict):
        return dict(result)

def dumps(obj):
    return json.dumps(obj, cls=JSONDateTimeEncoder)

def loads(obj):
    return json.loads(obj, object_hook=datetime_decoder)

if __== '__main__':
    mytimestamp = datetime.datetime.utcnow()
    mydate = datetime.date.today()
    data = dict(
        foo = 42,
        bar = [mytimestamp, mydate],
        date = mydate,
        timestamp = mytimestamp,
        struct = dict(
            date2 = mydate,
            timestamp2 = mytimestamp
        )
    )

    print repr(data)
    jsonstring = dumps(data)
    print jsonstring
    print repr(loads(jsonstring))
30
Chris Arndt

Si vous êtes certain que seul le langage JSON sera utilisé par Javascript, je préfère passer directement les objets Javascript Date.

La méthode ctime() sur les objets datetime renverra une chaîne que l'objet Date Javascript peut comprendre.

import datetime
date = datetime.datetime.today()
json = '{"mydate":new Date("%s")}' % date.ctime()

Javascript utilisera volontiers cela comme un littéral d'objet, et votre objet Date est intégré directement dans.

23
Triptych

Tard dans le jeu ... :) 

Une solution très simple consiste à patcher le module JSON par défaut. Par exemple:

import json
import datetime

json.JSONEncoder.default = lambda self,obj: (obj.isoformat() if isinstance(obj, datetime.datetime) else None)

Maintenant, vous pouvez utiliser json.dumps () comme si elle avait toujours supporté datetime ...

json.dumps({'created':datetime.datetime.now()})

Cela a du sens si vous avez besoin de cette extension du module json et que vous ne souhaitiez pas changer la façon dont vous ou d'autres personnes utilisez la sérialisation json (dans le code existant ou non). 

Notez que certains peuvent considérer les correctifs des bibliothèques de cette manière comme une mauvaise pratique . Il faut faire particulièrement attention au cas où vous souhaiteriez étendre votre application de plusieurs façons - dans ce cas, je suggère d'utiliser la solution en: ramen ou JT et choisissez l’extension JSON appropriée dans chaque cas. 

11
davidhadas

Pas grand chose à ajouter à la réponse du wiki de la communauté, sauf pour timestamp !

Javascript utilise le format suivant:

new Date().toJSON() // "2016-01-08T19:00:00.123Z"

Côté Python (pour le gestionnaire json.dumps, voir les autres réponses):

>>> from datetime import datetime
>>> d = datetime.strptime('2016-01-08T19:00:00.123Z', '%Y-%m-%dT%H:%M:%S.%fZ')
>>> d
datetime.datetime(2016, 1, 8, 19, 0, 0, 123000)
>>> d.isoformat() + 'Z'
'2016-01-08T19:00:00.123000Z'

Si vous laissez ce Z, les frameworks frontend comme angular ne peuvent pas afficher la date dans le fuseau horaire local du navigateur:

> $filter('date')('2016-01-08T19:00:00.123000Z', 'yyyy-MM-dd HH:mm:ss')
"2016-01-08 20:00:00"
> $filter('date')('2016-01-08T19:00:00.123000', 'yyyy-MM-dd HH:mm:ss')
"2016-01-08 19:00:00"
6
user1338062

Côté python:

import time, json
from datetime import datetime as dt
your_date = dt.now()
data = json.dumps(time.mktime(your_date.timetuple())*1000)
return data # data send to javascript

Du côté javascript:

var your_date = new Date(data)

où les données sont issues de python

4
Sank

Mon conseil est d'utiliser une bibliothèque. Il en existe plusieurs disponibles sur pypi.org.

J'utilise celui-ci, ça marche bien: https://pypi.python.org/pypi/asjson

4
guettli

Apparemment Le format de date JSON (bien JavaScript) «correct» est 2012-04-23T18: 25: 43.511Z - UTC et «Z». Sans cela, JavaScript utilisera le fuseau horaire local du navigateur Web lors de la création d'un objet Date () à partir de la chaîne.

Pour une heure "naïve" (ce que Python appelle une heure sans fuseau horaire et qui suppose que c'est local), le ci-dessous va forcer le fuseau horaire local afin qu'il puisse ensuite être converti correctement en UTC:

def default(obj):
    if hasattr(obj, "json") and callable(getattr(obj, "json")):
        return obj.json()
    if hasattr(obj, "isoformat") and callable(getattr(obj, "isoformat")):
        # date/time objects
        if not obj.utcoffset():
            # add local timezone to "naive" local time
            # https://stackoverflow.com/questions/2720319/python-figure-out-local-timezone
            tzinfo = datetime.now(timezone.utc).astimezone().tzinfo
            obj = obj.replace(tzinfo=tzinfo)
        # convert to UTC
        obj = obj.astimezone(timezone.utc)
        # strip the UTC offset
        obj = obj.replace(tzinfo=None)
        return obj.isoformat() + "Z"
    Elif hasattr(obj, "__str__") and callable(getattr(obj, "__str__")):
        return str(obj)
    else:
        print("obj:", obj)
        raise TypeError(obj)

def dump(j, io):
    json.dump(j, io, indent=2, default=default)

pourquoi est-ce si difficile.

0
cagney