JSON datetime между Python и JavaScript

Я хочу отправить объект datetime.datetime в сериализованной форме из Python, используя JSON, и десериализовать в JavaScript, используя JSON. Каков наилучший способ сделать это?

13 ответов

Вы можете добавить параметр 'default' в 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))

Обновление: добавлен вывод типа и значения.
Обновление: также обрабатывать дату

Для кросс-языковых проектов я обнаружил, что строки с датами 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'))

К сожалению, конструктор Date в Javascript не принимает строки RfC 3339, но в Интернете доступно много анализаторов.

huTools.hujson пытается решить наиболее распространенные проблемы кодирования, с которыми вы можете столкнуться в коде Python, включая объекты даты / даты и времени, при правильной обработке часовых поясов.

Я решил это.

Допустим, у вас есть объект datetime Python, 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 имеют метод to JSON, который возвращает строку в стандартном формате. Поэтому, чтобы сериализовать вышеуказанный объект Javascript обратно в JSON, команда должна быть:

d.toJSON()

Что бы дать вам:

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

Эта строка, полученная в Python, может быть десериализована обратно в объект datetime:

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)

С помощью 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"]'

Вот довольно полное решение для рекурсивного кодирования и декодирования объектов datetime.datetime и datetime.date с использованием стандартной библиотеки json модуль. Это требует Python >= 2.6, так как %f Код формата в строке формата datetime.datetime.strptime() поддерживается только с тех пор. Для поддержки Python 2.5 удалите %f и уберите микросекунды из строки даты ISO, прежде чем пытаться преобразовать ее, но, конечно, вы потеряете точность в микросекундах. Для обеспечения совместимости со строками даты ISO из других источников, которые могут включать имя часового пояса или смещение UTC, вам также может потребоваться удалить некоторые части строки даты перед преобразованием. Для полного анализатора для строк даты ISO (и многих других форматов даты) см. Сторонний модуль dateutil.

Декодирование работает только тогда, когда строки даты 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))

Если вы уверены, что только Javascript будет использовать JSON, я предпочитаю передавать Javascript Date объекты напрямую.

ctime() метод на datetime объекты будут возвращать строку, которую может понять объект даты Javascript.

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

Javascript с радостью будет использовать его в качестве литерала объекта, и у вас уже есть встроенный объект Date.

Поздно в игре...:)

Очень простое решение - установить исправление по умолчанию для модуля json. Например:

import json
import datetime

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

Теперь вы можете использовать json.dumps(), как если бы он всегда поддерживал datetime...

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

Это имеет смысл, если вам требуется, чтобы это расширение модуля json всегда включалось, и вы не хотели менять способ, которым вы или другие используете сериализацию json (в существующем коде или нет).

Обратите внимание, что некоторые могут расценивать исправление библиотек таким образом как плохую практику. Особая осторожность должна быть предпринята, если вы захотите расширить свое приложение более чем одним способом - в таком случае я предлагаю использовать решение по ramen или JT и выбрать правильное расширение json в каждом случае.

Не так много, чтобы добавить в сообщество ответ вики, за исключением отметки времени!

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 вне, каркасы внешнего интерфейса, такие как angular, не смогут отображать дату в часовом поясе браузера:

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

Мой совет - использовать библиотеку. Есть несколько доступных на pypi.org.

Я использую этот, он работает хорошо: https://pypi.python.org/pypi/asjson

На стороне питона:

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)

где данные являются результатом Python

Для преобразования даты 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.

Видимо, "правильным" форматом даты 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
            # https://stackru.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)

Почему это так сложно.

Просто сделайте это:

      r = json.dumps(your_json_data, default=str)
your_json_data = json.loads(r)

Другие вопросы по тегам