Час дати JSON між Python та JavaScript


393

Я хочу надіслати об’єкт datetime.datetime в серіалізованому вигляді з Python за допомогою JSON та де-серіалізувати в JavaScript за допомогою JSON. Який найкращий спосіб зробити це?


Ви вважаєте за краще використовувати бібліотеку чи хочете це самостійно кодувати?
guettli

Відповіді:


370

Ви можете додати параметр 'за замовчуванням' до json.dumps, щоб вирішити це:

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

Який формат ISO 8601 .

Більш всеосяжна функція обробника за замовчуванням:

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

Оновлення: Додано вихід типу, а також значення.
Оновлення: також обробляти дату


11
Проблема полягає в тому, що якщо у вас є деякі інші об'єкти в списку / диктаті, цей код перетворить їх у None.
Tomasz Wysocki

5
json.dumps не знає, як їх конвертувати, але виняток придушується. На жаль, одна виправлена ​​лямбда-лінія має свої недоліки. Якщо ви хочете, щоб виняток був піднятий на невідомих (це гарна ідея), використовуйте функцію, яку я додав вище.
JT.

9
повний вихідний формат також повинен мати на ньому часовий пояс ... а isoformat () не забезпечує цю функціональність ... тому вам слід обов’язково додати цю інформацію до рядка перед поверненням
Нік Франческа,

3
Це найкращий шлях. Чому це не було обрано як відповідь?
Брендон Крофорд

16
Лямбда може бути адаптована для виклику базової реалізації для типів без дати, тому TypeError можна підняти за потреби:dthandler = lambda obj: obj.isoformat() if isinstance(obj, datetime) else json.JSONEncoder().default(obj)
Pascal Bourque

81

Для мовних проектів я з'ясував, що рядки, що містять дати RfC 3339, є найкращим способом. Дата RfC 3339 виглядає так:

  1985-04-12T23:20:50.52Z

Я думаю, що більша частина формату очевидна. Єдиною дещо незвичною річчю може бути "Z" наприкінці. Це означає GMT / UTC. Ви також можете додати зміщення часового поясу на зразок +02: 00 для CEST (Німеччина влітку). Я особисто вважаю за краще тримати все в UTC, поки воно не відобразиться.

Для відображення, порівняння та зберігання можна залишити його у рядковому форматі на всіх мовах. Якщо вам потрібна дата для обчислень, просто перетворіть її на рідний об'єкт дати на більшості мов.

Тож генеруйте JSON так:

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

На жаль, конструктор дати Javascript не приймає рядки RfC 3339, але в Інтернеті є багато парсерів .

huTools.hujson намагається вирішити найпоширеніші проблеми кодування, які ви можете зіткнутися в коді Python, включаючи об'єкти дати / дати, при правильній обробці часових поясів.


17
Цей механізм форматування дат підтримується спочатку: як datetimetimetime.isoformat (), так і користувачем simplejson, який скидає datetimeоб'єкти як isoformatрядки за замовчуванням. Немає необхідності в ручному strftimeзлому.
jrk

9
@jrk - я не отримую автоматичного перетворення з datetimeоб'єктів у isoformatрядок. Для мене simplejson.dumps(datetime.now())врожайністьTypeError: datetime.datetime(...) is not JSON serializable
костмо

6
json.dumps(datetime.datetime.now().isoformat())там, де відбувається магія.
ятанізм

2
Краса simplejson полягає в тому, що якщо у мене складна структура даних, вона розбере її і перетворить на JSON. Якщо мені доведеться робити json.dumps (datetime.datetime.now (). Isoformat ()) для кожного об'єкта datetime, я втрачаю це. Чи є спосіб це виправити?
andrewrk

1
superjoe30: см stackoverflow.com/questions/455580 / ... про те , як це зробити
макс

67

Я це розробив.

Скажімо, у вас є об'єкт Python datetime, d , створений за допомогою datetime.now (). Його значення:

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

Ви можете серіалізувати його до JSON як строку дати ISO 8601:

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

Приклад об'єкта datetime буде серіалізований як:

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

Це значення, щойно отримане в шарі Javascript, може побудувати об'єкт Date:

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

Станом на Javascript 1.8.5, об’єкти Date мають метод toJSON, який повертає рядок у стандартному форматі. Щоб серіалізувати вищевказаний об’єкт Javascript назад у JSON, отже, командою було б:

d.toJSON()

Що б вам дало:

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

Цей рядок, щойно отриманий в Python, може бути дезаріалізований назад до об'єкта дати:

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

Це призводить до появи наступного об'єкта datetime, який є тим самим, з якого ви почали, і тому правильний:

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

50

Використовуючи json, ви можете підкласифікувати JSONEncoder та замінити метод default (), щоб забезпечити власні спеціалізовані серіалізатори:

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)

Тоді ви можете назвати це так:

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

7
Незначне покращення - використання obj.isoformat(). Ви також можете скористатись звичайнішим dumps()викликом, який займає інші корисні аргументи (наприклад indent): simplejson.dumps (myobj, cls = JSONEncoder, ...)
rcoup

3
Оскільки це закликало б метод батьківського JSONEncoder, а не метод батька DateTimeJSONEncoder. IE, ви б піднімалися на два рівні.
Брайан Арсуага

30

Ось досить повне рішення для рекурсивного кодування та декодування об’єктів datetime.datetime та datetime.date за допомогою стандартного jsonмодуля бібліотеки . Для цього потрібен Python> = 2.6, оскільки %fкод формату в рядку формату datetime.datetime.strptime () підтримується лише з того часу. Для підтримки Python 2.5 опустіть %fі зніміть мікросекунди з рядка дати ISO перед тим, як спробувати перетворити її, але, звичайно, ви втратите точність мікросекунд. Для сумісності з рядками дати ISO з інших джерел, які можуть включати назву часового поясу або зміщення UTC, вам також може знадобитися зняти деякі частини рядка дати перед перетворенням. Повний аналіз для рядків дат ISO (та багатьох інших форматів дати) див. У сторонньому модулі datautil .

Розшифровка працює лише тоді, коли рядки дати ISO є значеннями в нотаційній нотації об'єкта JavaScript або в вкладених структурах всередині об'єкта. Рядки дати ISO, які є елементами масиву верхнього рівня, не будуть декодуватися.

Тобто це працює:

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

І це теж:

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

Але це не працює, як очікувалося:

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

Ось код:

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

Якщо ви надрукуєте дату, datetime.datetime.utcnow().isoformat()[:-3]+"Z"вона буде точно схожа на те, що виробляє
JSON.stringify

24

Якщо ви впевнені, що JSON споживає лише Javascript, я вважаю за краще передавати Dateоб’єкти Javascript безпосередньо.

ctime()Метод на datetimeоб'єктах повертає рядок , що дата об'єкт Javascript може зрозуміти.

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

Javascript з радістю буде використовувати це як буквальний об'єкт, і ви отримали свій об'єкт Date вбудований прямо.


12
Технічно недійсний JSON, але це дійсний літеральний об’єкт JavaScript. (Для принципу я б встановив Content-Type на текст / javascript замість програми / json.) Якщо споживач завжди і назавжди буде лише реалізацією JavaScript, то так, це досить елегантно. Я б цим скористався.
система ПАУЗА

13
.ctime()ДУЖЕ поганий спосіб передавати інформацію про час, .isoformat()набагато краще. Що .ctime()полягає в тому, щоб викинути часовий пояс і літній час, як їх немає. Ця функція повинна бути вбита.
Євгеній

Роками пізніше: будь ласка, ніколи не думайте робити це. Це коли-небудь спрацює, якщо ви eval () ваш json в Javascript, якого ви дійсно не повинні ...
domenukk

11

Пізно в грі ... :)

Дуже просте рішення - виправити модуль json за замовчуванням. Наприклад:

import json
import datetime

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

Тепер ви можете використовувати json.dumps () так, ніби він завжди підтримував дату ...

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

Це має сенс, якщо вам потрібно, щоб це розширення до модуля json завжди починалося, і ви не хочете змінювати спосіб використання ви серіализацією json (чи в існуючому коді, чи ні).

Зауважте, що деякі можуть вважати виправлення бібліотек таким чином поганою практикою. Необхідно бути особливо обережним, якщо ви хочете розширити свою заявку більш ніж одним способом - такий випадок, я пропоную використовувати рішення за допомогою ramen або JT та вибирати належне розширення json у кожному конкретному випадку.


6
Це мовчки поїдає не серіалізаційні об'єкти і перетворює їх у None. Ви можете замість цього викинути виняток.
Блендер

6

Не багато для додання відповіді у вікі спільноти, крім часової позначки !

Javascript використовує наступний формат:

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

Сторона Python (про json.dumpsобробник див. Інші відповіді):

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

Якщо ви вийдете з цього виходу Z, рамки прямої лінії, такі як кутова, не можуть відображати дату в локальному часовому поясі браузера:

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

4

З боку пітона:

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

З боку javascript:

var your_date = new Date(data)

де дані є результатом пітона



0

Мабуть, "правильний" формат дати JSON (добре JavaScript) - 2012-04-23T18: 25: 43.511Z - UTC та "Z". Без цього JavaScript буде використовувати локальний часовий пояс веб-браузера під час створення об’єкта Date () з рядка.

У "наївний" час (те, що Python називає часом без часового поясу, і це передбачає місцеве), нижче буде застосовано локальний часовий пояс, щоб потім його можна було правильно перетворити на 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)

чому це так важко.


0

Для перетворення дати Python в JavaScript об'єкт дати повинен бути у визначеному форматі ISO, тобто у форматі ISO або UNIX. Якщо у форматі ISO бракує деякої інформації, то ви можете спочатку перетворити на номер Unix за допомогою Date.parse. Більше того, Date.parse працює з React, а нова дата може викликати виняток.

Якщо у вас є об'єкт DateTime без мілісекунд, необхідно врахувати наступне. :

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

Прикладна дата може бути однаковою мірою змінною в об'єкті result.data після виклику API.

Щоб отримати параметри відображення дати у бажаному форматі (наприклад, для відображення довгих буднів), перегляньте документ MDN .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.