Fecha y hora JSON entre Python y JavaScript

393

Quiero enviar un objeto datetime.datetime en forma serializada desde Python usando JSON y deserializar en JavaScript usando JSON. ¿Cuál es la mejor manera de hacer esto?

Peter Mortensen
fuente
¿Prefieres usar una biblioteca o quieres codificar esto tú mismo?
guettli

Respuestas:

370

Puede agregar el parámetro 'predeterminado' a json.dumps para manejar esto:

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

Cuál es el formato ISO 8601 .

Una función de controlador predeterminada más completa:

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

Actualización: salida agregada de tipo y valor.
Actualización: también maneja la fecha

JT.
fuente
11
El problema es que si tiene otros objetos en list / dict, este código los convertirá en None.
Tomasz Wysocki
55
json.dumps tampoco sabrá cómo convertirlos, pero se está suprimiendo la excepción. Lamentablemente, una solución lambda de una línea tiene sus deficiencias. Si prefiere que se genere una excepción en las incógnitas (que es una buena idea), use la función que he agregado anteriormente.
JT.
99
el formato de salida completo también debe tener una zona horaria ... e isoformat () no proporciona esta funcionalidad ... por lo que debe asegurarse de agregar esa información en la cadena antes de regresar
Nick Franceschina
3
Este es el mejor camino a seguir. ¿Por qué no se seleccionó esto como respuesta?
Brendon Crawford
16
La lambda se puede adaptar para llamar a la implementación base en tipos que no son de fecha y hora, por lo que TypeError se puede generar si es necesario:dthandler = lambda obj: obj.isoformat() if isinstance(obj, datetime) else json.JSONEncoder().default(obj)
Pascal Bourque
81

Para proyectos en varios idiomas, descubrí que las cadenas que contienen fechas RfC 3339 son la mejor opción . Una fecha RfC 3339 se ve así:

  1985-04-12T23:20:50.52Z

Creo que la mayoría del formato es obvio. La única cosa algo inusual puede ser la "Z" al final. Es sinónimo de GMT / UTC. También puede agregar un desplazamiento de zona horaria como +02: 00 para CEST (Alemania en verano). Personalmente prefiero mantener todo en UTC hasta que se muestre.

Para la visualización, las comparaciones y el almacenamiento, puede dejarlo en formato de cadena en todos los idiomas. Si necesita la fecha para realizar los cálculos, vuelva a convertirla en un objeto de fecha nativo en la mayoría de los idiomas.

Así que genera el JSON así:

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

Desafortunadamente, el constructor Fecha de Javascript no acepta cadenas RfC 3339 pero hay muchos analizadores disponibles en Internet.

huTools.hujson intenta manejar los problemas de codificación más comunes que puede encontrar en el código de Python, incluidos los objetos de fecha / fecha y hora, mientras maneja las zonas horarias correctamente.

max
fuente
17
Este mecanismo de formato de fecha es compatible de forma nativa, tanto por datetime: datetime.isoformat () como por simplejson, que volcará los datetimeobjetos como isoformatcadenas de forma predeterminada. No hay necesidad de strftimepiratería manual .
jrk el
99
@jrk: no obtengo conversión automática de datetimeobjetos a la isoformatcadena. Para mí, los simplejson.dumps(datetime.now())rendimientosTypeError: datetime.datetime(...) is not JSON serializable
kostmo
66
json.dumps(datetime.datetime.now().isoformat())Es donde sucede la magia.
jathanism
2
La belleza de simplejson es que si tengo una estructura de datos compleja, la analizará y la convertirá en JSON. Si tengo que hacer json.dumps (datetime.datetime.now (). Isoformat ()) para cada objeto datetime, lo pierdo. ¿Hay alguna manera de arreglar esto?
andrewrk
1
superjoe30: vea stackoverflow.com/questions/455580/… sobre cómo hacerlo
máximo
67

Lo he resuelto.

Digamos que tiene un objeto de fecha y hora de Python, d , creado con datetime.now (). Su valor es:

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

Puede serializarlo a JSON como una cadena de fecha y hora ISO 8601:

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

El objeto de fecha y hora de ejemplo se serializaría como:

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

Este valor, una vez recibido en la capa Javascript, puede construir un objeto Date:

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

A partir de Javascript 1.8.5, los objetos Date tienen un método toJSON, que devuelve una cadena en un formato estándar. Para serializar el objeto Javascript anterior de nuevo a JSON, por lo tanto, el comando sería:

d.toJSON()

Lo que te daría:

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

Esta cadena, una vez recibida en Python, podría deserializarse de nuevo a un objeto de fecha y hora:

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

Esto da como resultado el siguiente objeto de fecha y hora, que es el mismo con el que comenzó y, por lo tanto, correcto:

datetime.datetime(2011, 5, 25, 20, 34, 5, 787000)
usuario240515
fuente
50

Utilizando json, puede subclasificar JSONEncoder y anular el método predeterminado () para proporcionar sus propios serializadores personalizados:

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)

Entonces, puedes llamarlo así:

>>> DateTimeJSONEncoder().encode([datetime.datetime.now()])
'["2010-06-15T14:42:28"]'
ramen
fuente
77
Mejora menor: uso obj.isoformat(). También puede usar la dumps()llamada más común , que toma otros indentargumentos útiles (como ): simplejson.dumps (myobj, cls = JSONEncoder, ...)
rcoup
3
Porque eso llamaría el método de los padres de JSONEncoder, no el método de los padres de DateTimeJSONEncoder. Es decir, subirías dos niveles.
Brian Arsuaga
30

Aquí hay una solución bastante completa para codificar y decodificar recursivamente los objetos datetime.datetime y datetime.date utilizando el jsonmódulo de biblioteca estándar . Esto necesita Python> = 2.6 ya que el %fcódigo de formato en la cadena de formato datetime.datetime.strptime () solo es compatible desde entonces. Para el soporte de Python 2.5, suelte %fy quite los microsegundos de la cadena de fecha ISO antes de intentar convertirlo, pero perderá precisión de microsegundos, por supuesto. Para la interoperabilidad con las cadenas de fecha ISO de otras fuentes, que pueden incluir un nombre de zona horaria o desplazamiento UTC, es posible que también necesite quitar algunas partes de la cadena de fecha antes de la conversión. Para obtener un analizador completo de cadenas de fecha ISO (y muchos otros formatos de fecha), consulte el dateutil de terceros módulo .

La decodificación solo funciona cuando las cadenas de fecha ISO son valores en una notación de objeto literal de JavaScript o en estructuras anidadas dentro de un objeto. Las cadenas de fecha ISO, que son elementos de una matriz de nivel superior, no se decodificarán.

Es decir, esto funciona:

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

Y esto también:

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

Pero esto no funciona como se esperaba:

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

Aquí está el código:

__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
fuente
Si imprime la fecha como datetime.datetime.utcnow().isoformat()[:-3]+"Z"si fuera exactamente como lo que JSON.stringify () produce en javascript
w00t
24

Si está seguro de que solo Javascript consumirá el JSON, prefiero pasar Javascript Date objetos directamente.

El ctime()método en los datetimeobjetos devolverá una cadena que el objeto Fecha de Javascript puede entender.

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

Javascript lo usará felizmente como un objeto literal, y tienes tu objeto Date integrado.

Tríptico
fuente
12
Técnicamente no es JSON válido, pero es un literal de objeto JavaScript válido. (En aras de los principios, establecería el Tipo de contenido en texto / javascript en lugar de aplicación / json.) Si el consumidor siempre y para siempre será solo una implementación de JavaScript, entonces sí, esto es bastante elegante. Lo usaría
PAUSA del sistema
13
.ctime()es una manera MUY mala de pasar información de tiempo, .isoformat()es mucho mejor. Lo que .ctime()hace es tirar la zona horaria y el horario de verano como si no existieran. Esa función debería ser eliminada.
Evgeny el
Años después: por favor, nunca consideres hacer esto. Esto solo funcionará si evalúa () su json en Javascript, lo que realmente no debería ...
Domingo
11

Tarde en el juego ... :)

Una solución muy simple es parchear el módulo json predeterminado. Por ejemplo:

import json
import datetime

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

Ahora, puede usar json.dumps () como si siempre hubiera admitido datetime ...

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

Esto tiene sentido si necesita que esta extensión del módulo json se active siempre y no desee cambiar la forma en que usted u otros usan la serialización json (ya sea en el código existente o no).

Tenga en cuenta que algunos pueden considerar parchear bibliotecas de esa manera como una mala práctica. Se debe tener especial cuidado en caso de que desee extender su aplicación de más de una manera; en tal caso, sugiero usar la solución por ramen o JT y elegir la extensión json adecuada en cada caso.

davidhadas
fuente
66
Esto en silencio come objetos no serializables y los convierte en None. Es posible que desee lanzar una excepción en su lugar.
Blender
6

¡No hay mucho que agregar a la respuesta wiki de la comunidad, a excepción de la marca de tiempo !

Javascript usa el siguiente formato:

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

Lado de Python (para el json.dumpscontrolador, vea las otras respuestas):

>>> 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 deja esa Z fuera, los marcos frontend como angular no pueden mostrar la fecha en la zona horaria local del navegador:

> $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"
usuario1338062
fuente
4

En el lado de 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

En el lado de javascript:

var your_date = new Date(data)

donde los datos son resultado de python

Se hundió
fuente
0

Aparentemente, el formato de fecha JSON "correcto" (bien JavaScript) es 2012-04-23T18: 25: 43.511Z - UTC y "Z". Sin esto, JavaScript utilizará la zona horaria local del navegador web al crear un objeto Date () a partir de la cadena.

Por un tiempo "ingenuo" (lo que Python llama un tiempo sin zona horaria y esto supone que es local) lo siguiente forzará la zona horaria local para que luego se pueda convertir correctamente a 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)

Por qué es tan difícil.

cagney
fuente
0

Para la conversión de fecha de Python a JavaScript, el objeto de fecha debe estar en formato ISO específico, es decir, formato ISO o número UNIX. Si el formato ISO carece de información, puede convertirlo al número de Unix con Date.parse primero. Además, Date.parse también funciona con React, mientras que la nueva Fecha podría desencadenar una excepción.

En caso de que tenga un objeto DateTime sin milisegundos, debe considerarse lo siguiente. :

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

La fecha de ejemplo también podría ser una variable en el objeto result.data después de una llamada a la API.

Para ver las opciones para mostrar la fecha en el formato deseado (por ejemplo, para mostrar los días de semana largos), consulte el documento MDN .

Patricio
fuente