JSON datetime między Pythonem a JavaScript

393

Chcę wysłać obiekt datetime.datetime w postaci serializowanej z Pythona za pomocą JSON i usuń serializację w JavaScript za pomocą JSON. Jak najlepiej to zrobić?

Peter Mortensen
źródło
Wolisz korzystać z biblioteki, czy sam chcesz to kodować?
guettli

Odpowiedzi:

370

Możesz dodać parametr „default” do json.dumps, aby to obsłużyć:

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

Co to jest ISO 8601 formatem .

Bardziej kompleksowa domyślna funkcja obsługi:

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

Aktualizacja: Dodano wynik zarówno typu, jak i wartości.
Aktualizacja: obsługuje także datę

JT.
źródło
11
Problem polega na tym, że jeśli masz jakieś inne obiekty na liście / dict, ten kod przekonwertuje je na Brak.
Tomasz Wysocki,
5
json.dumps też nie będzie wiedział, jak je przekonwertować, ale wyjątek jest tłumiony. Niestety poprawka lambda w jednym wierszu ma swoje wady. Jeśli wolisz wyjątek dotyczący niewiadomych (co jest dobrym pomysłem), skorzystaj z funkcji, którą dodałem powyżej.
JT.
9
pełny format wyjściowy powinien mieć również strefę czasową ... a isoformat () nie zapewnia tej funkcji ... więc powinieneś upewnić się, że dołączasz te informacje do łańcucha przed powrotem
Nick Franceschina
3
To najlepsza droga. Dlaczego nie wybrano tego jako odpowiedzi?
Brendon Crawford
16
Lambda może być przystosowana do wywoływania implementacji bazowej dla typów innych niż data, więc TypeError może zostać podniesiony w razie potrzeby:dthandler = lambda obj: obj.isoformat() if isinstance(obj, datetime) else json.JSONEncoder().default(obj)
Pascal Bourque
81

W przypadku projektów międzyjęzykowych odkryłem, że ciągi zawierające daty RfC 3339 są najlepszym sposobem. Data RfC 3339 wygląda następująco:

  1985-04-12T23:20:50.52Z

Myślę, że większość formatu jest oczywista. Jedyną dość niezwykłą rzeczą może być „Z” na końcu. Oznacza GMT / UTC. Możesz również dodać przesunięcie strefy czasowej, takie jak +02: 00 dla CEST (Niemcy latem). Osobiście wolę trzymać wszystko w UTC, dopóki nie zostanie wyświetlone.

Do wyświetlania, porównań i przechowywania możesz pozostawić go w formacie ciągu we wszystkich językach. Jeśli potrzebujesz daty do obliczeń, łatwo przekonwertuj ją na natywny obiekt daty w większości języków.

Więc wygeneruj JSON w ten sposób:

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

Niestety konstruktor daty Javascript nie akceptuje ciągów RfC 3339, ale w Internecie dostępnych jest wiele parserów .

huTools.hujson próbuje poradzić sobie z najczęstszymi problemami z kodowaniem, które mogą wystąpić w kodzie Pythona, w tym z obiektami daty / daty i godziny, przy prawidłowej obsłudze stref czasowych.

max
źródło
17
Ten mechanizm formatowania daty jest natywnie obsługiwany, zarówno przez datetime: datetime.isoformat (), jak i przez simplejson, który domyślnie zrzuca datetimeobiekty jako isoformatciągi znaków. Nie ma potrzeby ręcznego strftimehakowania.
jrk
9
@jrk - Nie otrzymuję automatycznej konwersji datetimeobiektów na isoformatciąg. Dla mnie simplejson.dumps(datetime.now())wydajnośćTypeError: datetime.datetime(...) is not JSON serializable
kostmo
6
json.dumps(datetime.datetime.now().isoformat())tam, gdzie dzieje się magia.
jathanism
2
Piękno simplejson polega na tym, że jeśli mam złożoną strukturę danych, parsuje ją i zamienia w JSON. Jeśli muszę zrobić json.dumps (datetime.datetime.now (). Isoformat ()) dla każdego obiektu datetime, tracę to. Czy istnieje sposób, aby to naprawić?
andrewrk
1
superjoe30: zobacz stackoverflow.com/questions/455580/... jak to zrobić
maks.
67

Rozpracowałem to.

Załóżmy, że masz obiekt Pytetime datetime, d , utworzony za pomocą datetime.now (). Jego wartość to:

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

Możesz serializować to do JSON jako ciąg datetime ISO 8601:

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

Przykładowy obiekt datetime zostałby serializowany jako:

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

Ta wartość, po otrzymaniu w warstwie JavaScript, może utworzyć obiekt Date:

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

Począwszy od JavaScript 1.8.5, obiekty Date mają metodę toJSON, która zwraca ciąg znaków w standardowym formacie. Aby serializować powyższy obiekt JavaScript z powrotem do JSON, polecenie powinno wyglądać następująco:

d.toJSON()

Co dałoby ci:

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

Ten ciąg, raz otrzymany w Pythonie, można przekształcić z postaci szeregowej z powrotem do obiektu datetime:

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

Powoduje to powstanie następującego obiektu datetime, który jest tym samym, który zacząłeś i dlatego jest poprawny:

datetime.datetime(2011, 5, 25, 20, 34, 5, 787000)
użytkownik240515
źródło
50

Za pomocą jsonmożna podklasować JSONEncoder i przesłonić metodę default (), aby zapewnić własne niestandardowe serializatory:

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)

Następnie możesz nazwać to tak:

>>> DateTimeJSONEncoder().encode([datetime.datetime.now()])
'["2010-06-15T14:42:28"]'
ramen
źródło
7
Niewielkie ulepszenie - użycie obj.isoformat(). Możesz także użyć bardziej popularnego dumps()wywołania, które pobiera inne przydatne argumenty (jak indent): simplejson.dumps (myobj, cls = JSONEncoder, ...)
rcoup
3
Ponieważ wywołałoby to metodę rodzica JSONEncoder, a nie metodę DateTimeJSONEncoder. IE, wchodziłbyś o dwa poziomy wyżej.
Brian Arsuaga,
30

Oto dość kompletne rozwiązanie do rekurencyjnego kodowania i dekodowania obiektów datetime.datetime i datetime.date przy użyciu standardowego jsonmodułu bibliotecznego . Wymaga to Python> = 2.6, ponieważ %fkod formatu w ciągu formatu datetime.datetime.strptime () jest obsługiwany tylko od tego czasu. Aby obsługiwać Python 2.5, upuść %fi usuń mikrosekundy z ciągu daty ISO przed próbą konwersji, ale stracisz precyzję mikrosekund. Aby zapewnić zgodność z ciągami dat ISO z innych źródeł, które mogą obejmować nazwę strefy czasowej lub przesunięcie UTC, konieczne może być również usunięcie niektórych części ciągu daty przed konwersją. Pełny parser dla ciągów daty ISO (i wielu innych formatów dat) znajduje się w module dateutil innej firmy .

Dekodowanie działa tylko wtedy, gdy ciągi daty ISO są wartościami w dosłownym zapisie obiektu w JavaScript lub w zagnieżdżonych strukturach w obiekcie. Ciągi daty ISO, które są elementami tablicy najwyższego poziomu, nie będą dekodowane.

Tzn. To działa:

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

I to także:

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

Ale to nie działa zgodnie z oczekiwaniami:

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

Oto kod:

__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
źródło
Jeśli wydrukujesz datę w taki sposób, datetime.datetime.utcnow().isoformat()[:-3]+"Z"że będzie dokładnie taka, jak JSON.stringify () produkuje w javascript
w00t
24

Jeśli masz pewność, że tylko JavaScript będzie zużywał JSON, wolę przekazywać Dateobiekty JavaScript bezpośrednio.

ctime()Metoda na datetimeobiektach zwróci napis, że obiekt Date JavaScript może zrozumieć.

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

JavaScript z przyjemnością użyje tego jako literału obiektu, a ty masz wbudowany obiekt Date.

Tryptyk
źródło
12
Technicznie niepoprawny JSON, ale jest to poprawny literał obiektu JavaScript. (Ze względu na zasadę ustawiłbym Content-Type na text / javascript zamiast application / json.) Jeśli konsument zawsze i na zawsze będzie tylko implementacją JavaScript, to tak, to całkiem eleganckie. Użyłbym tego.
system PAUZA
13
.ctime()jest BARDZO złym sposobem na przekazanie informacji o czasie, .isoformat()jest znacznie lepszy. Co .ctime()robi jest wyrzucić strefę czasową i czas letni jakby nie istnieją. Ta funkcja powinna zostać zabita.
Evgeny
Lata później: proszę, nigdy nie rozważaj tego. To zadziała tylko wtedy, gdy wyewidencjonujesz () swój Json w JavaScript, czego naprawdę nie powinieneś ...
domenukk 11.04.19
11

Późno w grze ... :)

Bardzo prostym rozwiązaniem jest załatanie domyślnego modułu json. Na przykład:

import json
import datetime

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

Teraz możesz używać json.dumps () tak, jakby zawsze obsługiwał datetime ...

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

Ma to sens, jeśli wymagasz tego rozszerzenia modułu json, aby zawsze się uruchamiało i nie chciałeś zmieniać sposobu, w jaki ty lub inni używacie serializacji json (w istniejącym kodzie lub nie).

Zauważ, że niektórzy mogą uważać łatanie bibliotek w ten sposób za złą praktykę. Należy zachować szczególną ostrożność, jeśli chcesz rozszerzyć swoją aplikację na więcej niż jeden sposób - w takim przypadku sugeruję skorzystanie z rozwiązania przez ramen lub JT i wybranie odpowiedniego rozszerzenia json w każdym przypadku.

davidhadas
źródło
6
To po cichu zjada obiekty, które nie są serializowane i zamienia je w None. Zamiast tego możesz zgłosić wyjątek.
Blender
6

Niewiele do dodania do odpowiedzi wiki społeczności, z wyjątkiem znacznika czasu !

JavaScript używa następującego formatu:

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

Strona Pythona (dla json.dumpsobsługi zobacz inne odpowiedzi):

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

Jeśli pominiesz to Z, frameworki takie jak kątowe nie będą mogły wyświetlać daty w lokalnej strefie czasowej przeglądarki:

> $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"
użytkownik1338062
źródło
4

Po stronie pytona:

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

Po stronie javascript:

var your_date = new Date(data)

gdzie dane pochodzą z Pythona

Zatonął
źródło
0

Najwyraźniej „właściwy” format daty JSON (dobrze JavaScript) jest 2012-04-23T18: 25: 43.511Z - UTC i „Z”. Bez tego JavaScript będzie używał lokalnej strefy czasowej przeglądarki podczas tworzenia obiektu Date () z ciągu.

Przez „naiwny” czas (który Python nazywa czasem bez strefy czasowej i zakłada, że ​​jest lokalny) poniżej wymusi lokalną strefę czasową, aby można ją było następnie poprawnie przekonwertować na 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
            # /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)

dlaczego to takie trudne

Cagney
źródło
0

Do konwersji daty w Pythonie na JavaScript obiekt daty musi mieć określony format ISO, tj. Format ISO lub numer UNIX. Jeśli w formacie ISO brakuje pewnych informacji, możesz najpierw przekonwertować na numer uniksowy, używając najpierw Date.parse. Ponadto Date.parse działa również z React, podczas gdy nowa data może wywołać wyjątek.

Jeśli masz obiekt DateTime bez milisekund, należy wziąć pod uwagę następujące kwestie. :

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

Przykładowa data może być również zmienną w obiekcie result.data po wywołaniu interfejsu API.

Aby wyświetlić opcje wyświetlania daty w żądanym formacie (np. Wyświetlanie długich dni tygodnia), sprawdź dokument MDN .

Patrick
źródło