JSON-Datumszeit zwischen Python und JavaScript

393

Ich möchte ein datetime.datetime-Objekt in serialisierter Form von Python mit JSON senden und in JavaScript mit JSON de-serialisieren. Was ist der beste Weg, dies zu tun?

Peter Mortensen
quelle
Verwenden Sie lieber eine Bibliothek oder möchten Sie diese selbst codieren?
Guettli

Antworten:

370

Sie können json.dumps den Parameter 'default' hinzufügen, um dies zu handhaben:

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"'

Welches ist ISO 8601 Format.

Eine umfassendere Standardhandlerfunktion:

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))

Update: Ausgabe von Typ und Wert hinzugefügt.
Update: Behandle auch das Datum

JT.
quelle
11
Das Problem ist, dass wenn Sie einige andere Objekte in list / dict haben, dieser Code sie in None konvertiert.
Tomasz Wysocki
5
json.dumps weiß auch nicht, wie diese konvertiert werden sollen, aber die Ausnahme wird unterdrückt. Leider hat ein einzeiliger Lambda-Fix seine Mängel. Wenn Sie lieber eine Ausnahme für die Unbekannten haben möchten (was eine gute Idee ist), verwenden Sie die oben hinzugefügte Funktion.
JT.
9
Das vollständige Ausgabeformat sollte auch eine Zeitzone enthalten ... und isoformat () bietet diese Funktionalität nicht ... daher sollten Sie sicherstellen, dass Sie diese Informationen an die Zeichenfolge anhängen, bevor Sie zurückkehren
Nick Franceschina
3
Dies ist der beste Weg. Warum wurde dies nicht als Antwort ausgewählt?
Brendon Crawford
16
Das Lambda kann angepasst werden, um die Basisimplementierung für Nicht-Datetime-Typen aufzurufen, sodass TypeError bei Bedarf ausgelöst werden kann:dthandler = lambda obj: obj.isoformat() if isinstance(obj, datetime) else json.JSONEncoder().default(obj)
Pascal Bourque
81

Bei sprachübergreifenden Projekten habe ich herausgefunden, dass Zeichenfolgen mit RfC 3339- Daten der beste Weg sind. Ein RfC 3339-Datum sieht folgendermaßen aus:

  1985-04-12T23:20:50.52Z

Ich denke, der größte Teil des Formats ist offensichtlich. Das einzig ungewöhnliche ist vielleicht das "Z" am Ende. Es steht für GMT / UTC. Sie können auch einen Zeitzonenversatz wie +02: 00 für MESZ (Deutschland im Sommer) hinzufügen. Ich persönlich bevorzuge es, alles in UTC zu behalten, bis es angezeigt wird.

Zum Anzeigen, Vergleichen und Speichern können Sie es in allen Sprachen im Zeichenfolgenformat belassen. Wenn Sie das Datum für Berechnungen benötigen, können Sie es in den meisten Sprachen einfach wieder in ein natives Datumsobjekt konvertieren.

Generieren Sie den JSON also wie folgt:

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

Leider akzeptiert der Date-Konstruktor von Javascript keine RfC 3339-Zeichenfolgen, aber im Internet sind viele Parser verfügbar.

huTools.hujson versucht, die häufigsten Codierungsprobleme im Python-Code zu behandeln, einschließlich Datums- / Datums- / Uhrzeitobjekten, während Zeitzonen korrekt behandelt werden.

max
quelle
17
Dieser Datumsformatierungsmechanismus wird nativ unterstützt, sowohl von datetime: datetime.isoformat () als auch von simplejson, wodurch datetimeObjekte isoformatstandardmäßig als Zeichenfolgen ausgegeben werden . Kein manuelles strftimeHacken erforderlich .
jrk
9
@jrk - Ich erhalte keine automatische Konvertierung von datetimeObjekten in die isoformatZeichenfolge. Für mich simplejson.dumps(datetime.now())ergibtTypeError: datetime.datetime(...) is not JSON serializable
kostmo
6
json.dumps(datetime.datetime.now().isoformat())Hier geschieht die Magie.
Nathanismus
2
Das Schöne an simplejson ist, dass wenn ich eine komplexe Datenstruktur habe, diese analysiert und in JSON umgewandelt wird. Wenn ich für jedes datetime-Objekt json.dumps (datetime.datetime.now (). Isoformat ()) ausführen muss, verliere ich das. Gibt es eine Möglichkeit, dies zu beheben?
Andrewrk
1
superjoe30: siehe stackoverflow.com/questions/455580/… wie das geht
max
67

Ich habe es herausgefunden.

Angenommen, Sie haben ein Python-Datum / Uhrzeit-Objekt, d , das mit datetime.now () erstellt wurde. Sein Wert ist:

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

Sie können es als ISO 8601-Datums- / Uhrzeitzeichenfolge in JSON serialisieren:

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

Das Beispiel-Datum / Uhrzeit-Objekt würde wie folgt serialisiert:

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

Dieser Wert kann nach dem Empfang in der Javascript-Ebene ein Datumsobjekt erstellen:

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

Ab Javascript 1.8.5 verfügen Date-Objekte über eine toJSON-Methode, die eine Zeichenfolge in einem Standardformat zurückgibt. Um das obige Javascript-Objekt wieder in JSON zu serialisieren, lautet der Befehl daher:

d.toJSON()

Welches würde Ihnen geben:

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

Diese in Python empfangene Zeichenfolge kann wieder in ein Datum / Uhrzeit-Objekt deserialisiert werden:

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

Dies führt zu dem folgenden Datum / Uhrzeit-Objekt, mit dem Sie begonnen haben und das daher korrekt ist:

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

Mit jsonkönnen Sie JSONEncoder in Unterklassen unterteilen und die default () -Methode überschreiben, um Ihre eigenen benutzerdefinierten Serialisierer bereitzustellen:

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)

Dann können Sie es so nennen:

>>> DateTimeJSONEncoder().encode([datetime.datetime.now()])
'["2010-06-15T14:42:28"]'
Ramen
quelle
7
Kleinere Verbesserung - Verwendung obj.isoformat(). Sie können auch den allgemeineren dumps()Aufruf verwenden, der andere nützliche Argumente (wie indent) benötigt: simplejson.dumps (myobj, cls = JSONEncoder, ...)
rcoup
3
Da dies die übergeordnete Methode von JSONEncoder aufrufen würde, nicht die übergeordnete Methode von DateTimeJSONEncoder. IE, Sie würden zwei Ebenen steigen.
Brian Arsuaga
30

Hier ist eine ziemlich vollständige Lösung zum rekursiven Codieren und Decodieren von datetime.datetime- und datetime.date-Objekten unter Verwendung des Standardbibliotheksmoduls json. Dies erfordert Python> = 2.6, da der %fFormatcode in der Formatzeichenfolge datetime.datetime.strptime () nur seitdem unterstützt wird. Wenn Sie Python 2.5 unterstützen möchten, löschen Sie die %fund entfernen Sie die Mikrosekunden von der ISO-Datumszeichenfolge, bevor Sie versuchen, sie zu konvertieren. Natürlich verlieren Sie jedoch die Genauigkeit von Mikrosekunden. Für die Interoperabilität mit ISO-Datumszeichenfolgen aus anderen Quellen, die einen Zeitzonennamen oder einen UTC-Versatz enthalten können, müssen Sie möglicherweise vor der Konvertierung auch einige Teile der Datumszeichenfolge entfernen. Einen vollständigen Parser für ISO-Datumszeichenfolgen (und viele andere Datumsformate) finden Sie im Dateutil- Modul eines Drittanbieters .

Die Dekodierung funktioniert nur, wenn die ISO-Datumszeichenfolgen Werte in einer JavaScript-Literalobjektnotation oder in verschachtelten Strukturen innerhalb eines Objekts sind. ISO-Datumszeichenfolgen, die Elemente eines Arrays der obersten Ebene sind, werden nicht dekodiert.

Dh das funktioniert:

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'}

Und das auch:

>>> 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)}]

Dies funktioniert jedoch nicht wie erwartet:

>>> 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']

Hier ist der 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 __name__ == '__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))
Chris Arndt
quelle
Wenn Sie das Datum so drucken, wie datetime.datetime.utcnow().isoformat()[:-3]+"Z"es ist, entspricht es genau dem, was JSON.stringify () in Javascript erzeugt
w00t
24

Wenn Sie sicher sind, dass nur Javascript den JSON verbraucht, übergebe ich Javascript- DateObjekte lieber direkt.

Die ctime()Methode für datetimeObjekte gibt eine Zeichenfolge zurück, die das Javascript Date-Objekt verstehen kann.

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

Javascript verwendet dies gerne als Objektliteral, und Sie haben Ihr Date-Objekt direkt eingebaut.

Triptychon
quelle
12
Technisch nicht gültiges JSON, aber es ist ein gültiges JavaScript-Objektliteral. (Aus Prinzip würde ich den Inhaltstyp auf text / javascript anstelle von application / json setzen.) Wenn der Verbraucher immer und für immer nur eine JavaScript-Implementierung sein wird, dann ist dies ziemlich elegant. Ich würde es benutzen.
System PAUSE
13
.ctime()ist ein sehr schlechter Weg, um Zeitinformationen weiterzugeben, .isoformat()ist viel besser. Was .ctime()wegwirft, ist Zeitzone und Sommerzeit wegzuwerfen, als ob sie nicht existieren. Diese Funktion sollte beendet werden.
Evgeny
Jahre später: Bitte denken Sie nie daran. Dies wird immer nur funktionieren, wenn Sie Ihren Json in Javascript
auswerten
11

Spät im Spiel ... :)

Eine sehr einfache Lösung besteht darin, die Standardeinstellung des JSON-Moduls zu patchen. Zum Beispiel:

import json
import datetime

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

Jetzt können Sie json.dumps () so verwenden, als hätte es datetime immer unterstützt ...

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

Dies ist sinnvoll, wenn Sie diese Erweiterung des JSON-Moduls benötigen, um immer aktiv zu werden, und die Art und Weise, wie Sie oder andere die JSON-Serialisierung verwenden (entweder im vorhandenen Code oder nicht), nicht ändern möchten.

Beachten Sie, dass einige das Patchen von Bibliotheken auf diese Weise als schlechte Praxis betrachten. Besondere Vorsicht ist geboten, wenn Sie Ihre Anwendung auf mehrere Arten erweitern möchten. In diesem Fall empfehle ich, die Lösung von Ramen oder JT zu verwenden und jeweils die richtige JSON-Erweiterung auszuwählen.

Davidhadas
quelle
6
Dies frisst stillschweigend nicht serialisierbare Objekte und verwandelt sie in None. Möglicherweise möchten Sie stattdessen eine Ausnahme auslösen.
Blender
6

Der Community-Wiki-Antwort gibt es nicht viel hinzuzufügen, außer dem Zeitstempel !

Javascript verwendet das folgende Format:

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

Python-Seite (für den json.dumpsHandler siehe die anderen Antworten):

>>> 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'

Wenn Sie dieses Z weglassen, können Frontend-Frameworks wie Angular das Datum nicht in der browser-lokalen Zeitzone anzeigen:

> $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"
user1338062
quelle
4

Auf der Python-Seite:

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

Auf der Javascript-Seite:

var your_date = new Date(data)

Dabei stammen die Daten aus Python

Versank
quelle
4

Mein Rat ist, eine Bibliothek zu benutzen. Es gibt mehrere auf pypi.org.

Ich benutze dieses, es funktioniert gut: https://pypi.python.org/pypi/asjson

guettli
quelle
0

Anscheinend ist das "richtige" JSON-Datumsformat (auch JavaScript) 2012-04-23T18: 25: 43.511Z - UTC und "Z". Ohne dieses JavaScript wird beim Erstellen eines Date () -Objekts aus der Zeichenfolge die lokale Zeitzone des Webbrowsers verwendet.

Für eine "naive" Zeit (was Python eine Zeit ohne Zeitzone nennt und dies als lokal voraussetzt) erzwingt das Folgende eine lokale Zeitzone, damit sie dann korrekt in UTC konvertiert werden kann:

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
            # /programming/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)

Warum ist das so schwer?

cagney
quelle
0

Für die Datumskonvertierung von Python in JavaScript muss das Datumsobjekt ein bestimmtes ISO-Format haben, dh ein ISO-Format oder eine UNIX-Nummer. Wenn dem ISO-Format einige Informationen fehlen, können Sie zuerst mit Date.parse in die Unix-Nummer konvertieren. Darüber hinaus funktioniert Date.parse auch mit React, während ein neues Datum möglicherweise eine Ausnahme auslöst.

Wenn Sie ein DateTime-Objekt ohne Millisekunden haben, muss Folgendes berücksichtigt werden. ::

  var unixDate = Date.parse('2016-01-08T19:00:00') 
  var desiredDate = new Date(unixDate).toLocaleDateString();

Das Beispieldatum kann nach einem API-Aufruf auch eine Variable im Objekt result.data sein.

Optionen zum Anzeigen des Datums im gewünschten Format (z. B. zum Anzeigen langer Wochentage) finden Sie im MDN-Dokument .

Patrick
quelle