JSON datetime antara Python dan JavaScript

393

Saya ingin mengirim objek datetime.datetime dalam bentuk serial dari Python menggunakan JSON dan de-serialize dalam JavaScript menggunakan JSON. Apa cara terbaik untuk melakukan ini?

Peter Mortensen
sumber
Apakah Anda lebih suka menggunakan perpustakaan atau Anda ingin membuat kode sendiri?
guettli

Jawaban:

370

Anda dapat menambahkan parameter 'default' ke json.dumps untuk menangani ini:

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

Yaitu ISO 8601 format .

Fungsi pengendali default yang lebih komprehensif:

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

Pembaruan: Menambahkan output jenis dan juga nilai.
Pembaruan: Juga menangani tanggal

JT.
sumber
11
Masalahnya adalah bahwa jika Anda memiliki beberapa objek lain dalam daftar / dict kode ini akan mengubahnya menjadi Tidak Ada.
Tomasz Wysocki
5
json.dumps tidak akan tahu cara mengonversi keduanya, tetapi pengecualian sedang ditekan. Sayangnya perbaikan satu baris lambda memiliki kekurangan itu. Jika Anda lebih suka pengecualian yang muncul pada hal yang tidak diketahui (yang merupakan ide bagus) gunakan fungsi yang saya tambahkan di atas.
JT.
9
format output penuh harus memiliki zona waktu di atasnya juga ... dan isoformat () tidak menyediakan fungsi ini ... jadi Anda harus memastikan untuk menambahkan info pada string sebelum kembali
Nick Franceschina
3
Ini cara terbaik untuk pergi. Mengapa ini tidak dipilih sebagai jawabannya?
Brendon Crawford
16
Lambda dapat diadaptasi untuk memanggil implementasi basis pada tipe non-datetime, sehingga TypeError dapat dinaikkan jika diperlukan:dthandler = lambda obj: obj.isoformat() if isinstance(obj, datetime) else json.JSONEncoder().default(obj)
Pascal Bourque
81

Untuk proyek lintas bahasa, saya menemukan bahwa string yang berisi tanggal RfC 3339 adalah cara terbaik untuk pergi. Tanggal RfC 3339 terlihat seperti ini:

  1985-04-12T23:20:50.52Z

Saya pikir sebagian besar formatnya jelas. Satu-satunya hal yang agak tidak biasa adalah "Z" pada akhirnya. Itu singkatan dari GMT / UTC. Anda juga bisa menambahkan offset zona waktu seperti +02: 00 untuk CEST (Jerman di musim panas). Saya pribadi lebih suka menyimpan semuanya dalam UTC sampai ditampilkan.

Untuk tampilan, perbandingan, dan penyimpanan Anda dapat membiarkannya dalam format string di semua bahasa. Jika Anda memerlukan tanggal perhitungan mudah untuk mengubahnya kembali ke objek tanggal asli dalam sebagian besar bahasa.

Jadi hasilkan JSON seperti ini:

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

Sayangnya, konstruktor Date Javascript tidak menerima string RfC 3339 tetapi ada banyak parser yang tersedia di Internet.

huTools.hujson mencoba menangani masalah penyandian paling umum yang mungkin Anda temui dalam kode Python termasuk objek tanggal / datetime sambil menangani zona waktu dengan benar.

maks
sumber
17
Mekanisme format tanggal ini didukung secara native, baik oleh datetime: datetime.isoformat () dan oleh simplejson, yang akan membuang datetimeobjek sebagai isoformatstring secara default. Tidak perlu strftimemelakukan peretasan manual .
jrk
9
@ jrk - Saya tidak mendapatkan konversi otomatis dari datetimeobjek ke isoformatstring. Bagi saya, simplejson.dumps(datetime.now())hasilTypeError: datetime.datetime(...) is not JSON serializable
kostmo
6
json.dumps(datetime.datetime.now().isoformat())Di sinilah keajaiban terjadi.
jathanism
2
Keindahan dari simplejson adalah bahwa jika saya memiliki struktur data yang kompleks, itu akan menguraikannya dan mengubahnya menjadi JSON. Jika saya harus melakukan json.dumps (datetime.datetime.now (). Isoformat ()) untuk setiap objek datetime, saya kehilangan itu. Apakah ada cara untuk memperbaikinya?
andrewrk
1
superjoe30: lihat stackoverflow.com/questions/455580/… tentang cara melakukan itu
maks
67

Saya sudah mengerjakannya.

Katakanlah Anda memiliki objek datetime Python, d , dibuat dengan datetime.now (). Nilainya adalah:

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

Anda bisa membuat cerita bersambung menjadi JSON sebagai string datetime ISO 8601:

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

Objek datetime contoh akan diserialisasi sebagai:

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

Nilai ini, setelah diterima di lapisan Javascript, dapat membuat objek Tanggal:

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

Pada Javascript 1.8.5, Objek tanggal memiliki metode toJSON, yang mengembalikan string dalam format standar. Untuk membuat serial objek Javascript di atas kembali ke JSON, maka perintahnya adalah:

d.toJSON()

Yang akan memberi Anda:

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

String ini, setelah diterima dengan Python, dapat dideserialisasi kembali ke objek datetime:

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

Ini menghasilkan objek datetime berikut, yang sama dengan yang Anda mulai dan karenanya benar:

datetime.datetime(2011, 5, 25, 20, 34, 5, 787000)
pengguna240515
sumber
50

Dengan menggunakan json, Anda dapat mensubklasifikasikan JSONEncoder dan mengganti metode default () untuk memberikan serializers khusus:

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)

Kemudian, Anda dapat menyebutnya seperti ini:

>>> DateTimeJSONEncoder().encode([datetime.datetime.now()])
'["2010-06-15T14:42:28"]'
Ramen
sumber
7
Peningkatan minor - gunakan obj.isoformat(). Anda juga dapat menggunakan dumps()panggilan yang lebih umum , yang menggunakan argumen berguna lainnya (seperti indent): simplejson.dumps (myobj, cls = JSONEncoder, ...)
rcoup
3
Karena itu akan memanggil metode induk JSONEncoder, bukan metode induk DateTimeJSONEncoder. Yaitu, Anda akan naik dua tingkat.
Brian Arsuaga
30

Berikut adalah solusi yang cukup lengkap untuk pengkodean dan penguraian ulang objek datetime.datetime dan datetime.date secara rekursif menggunakan jsonmodul library standar . Ini perlu Python> = 2.6 karena %fkode format dalam string format datetime.datetime.strptime () hanya didukung sejak saat itu. Untuk dukungan Python 2.5, jatuhkan %fdan lepaskan mikrodetik dari string tanggal ISO sebelum mencoba mengonversinya, tetapi Anda akan kehilangan presisi mikrodetik, tentu saja. Untuk interoperabilitas dengan string tanggal ISO dari sumber lain, yang mungkin menyertakan nama zona waktu atau offset UTC, Anda juga mungkin perlu menghapus beberapa bagian string tanggal sebelum konversi. Untuk parser lengkap untuk string tanggal ISO (dan banyak format tanggal lainnya) lihat modul dateutil pihak ketiga .

Decoding hanya berfungsi ketika string tanggal ISO adalah nilai dalam notasi objek literal JavaScript atau dalam struktur bersarang di dalam objek. String tanggal ISO, yang merupakan item dari array level atas tidak akan akan diterjemahkan.

Yaitu ini berfungsi:

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

Dan ini juga:

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

Tapi ini tidak berfungsi seperti yang diharapkan:

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

Berikut kodenya:

__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
sumber
Jika Anda mencetak tanggal seperti datetime.datetime.utcnow().isoformat()[:-3]+"Z"itu akan persis seperti apa yang dihasilkan JSON.stringify () dalam javascript
w00t
24

Jika Anda yakin hanya Javascript yang akan menggunakan JSON, saya lebih suka meneruskan Javascript Date objek secara langsung.

The ctime()metode pada datetimeobjek akan mengembalikan string bahwa Javascript Tanggal objek bisa mengerti.

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

Javascript akan dengan senang hati menggunakannya sebagai objek literal, dan Anda memiliki objek Date Anda.

Triptych
sumber
12
Secara teknis JSON tidak valid, tetapi ini adalah objek JavaScript yang benar. (Demi prinsip saya akan mengatur Content-Type untuk text / javascript bukan aplikasi / json.) Jika konsumen akan selalu dan selamanya menjadi hanya implementasi JavaScript, maka ya, ini cukup elegan. Saya akan menggunakannya.
system PAUSE
13
.ctime()adalah cara yang SANGAT buruk untuk menyampaikan informasi waktu, .isoformat()jauh lebih baik. Apa yang .ctime()dilakukan adalah membuang zona waktu dan penghematan siang hari seolah-olah tidak ada. Fungsi itu harus dibunuh.
Evgeny
Bertahun-tahun kemudian: tolong jangan pernah mempertimbangkan melakukan ini. Ini hanya akan bekerja jika Anda eval () json Anda dalam Javascript yang Anda benar-benar tidak boleh ...
domenukk
11

Terlambat dalam permainan ... :)

Solusi yang sangat sederhana adalah dengan menambal standar modul json. Sebagai contoh:

import json
import datetime

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

Sekarang, Anda dapat menggunakan json.dumps () seolah-olah itu selalu mendukung datetime ...

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

Ini masuk akal jika Anda memerlukan ekstensi ini ke modul json untuk selalu masuk dan ingin tidak mengubah cara Anda atau orang lain menggunakan serialisasi json (baik dalam kode yang ada atau tidak).

Perhatikan bahwa beberapa orang mungkin menganggap menambal perpustakaan dengan cara itu sebagai praktik yang buruk. Perhatian khusus perlu diambil jika Anda mungkin ingin memperpanjang aplikasi Anda dengan lebih dari satu cara - adalah kasus seperti itu, saya sarankan untuk menggunakan solusi oleh ramen atau JT dan pilih ekstensi json yang tepat dalam setiap kasus.

davidhadas
sumber
6
Ini secara diam-diam memakan objek yang tidak bersambung dan mengubahnya menjadi None. Anda mungkin ingin melempar pengecualian.
Blender
6

Tidak banyak yang ditambahkan ke jawaban wiki komunitas, kecuali stempel waktu !

Javascript menggunakan format berikut:

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

Sisi python (untuk json.dumpspawang, lihat jawaban lain):

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

Jika Anda membiarkan Z keluar, kerangka kerja frontend seperti angular tidak dapat menampilkan tanggal di zona waktu browser-lokal:

> $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"
pengguna1338062
sumber
4

Di sisi 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

Di sisi javascript:

var your_date = new Date(data)

di mana data adalah hasil dari python

Sank
sumber
4

Saran saya adalah menggunakan perpustakaan. Ada beberapa yang tersedia di pypi.org.

Saya menggunakan ini, ini berfungsi baik: https://pypi.python.org/pypi/asjson

guettli
sumber
0

Rupanya format tanggal JSON "JavaScript" yang benar adalah 2012-04-23T18: 25: 43.511Z - UTC dan "Z". Tanpa JavaScript ini akan menggunakan zona waktu lokal browser web saat membuat objek Date () dari string.

Untuk waktu "naif" (apa yang Python sebut waktu tanpa zona waktu dan ini dianggap lokal) di bawah ini akan memaksa zona waktu lokal sehingga kemudian dapat dikonversi dengan benar ke 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)

mengapa ini sangat sulit.

cagney
sumber
0

Untuk konversi tanggal Python ke JavaScript, objek tanggal harus dalam format ISO tertentu, yaitu format ISO atau nomor UNIX. Jika format ISO tidak memiliki beberapa info, maka Anda dapat mengonversi ke nomor Unix dengan Date.parse terlebih dahulu. Selain itu, Date.parse bekerja dengan Bereaksi juga sementara Tanggal baru dapat memicu pengecualian.

Jika Anda memiliki objek DateTime tanpa milidetik, hal-hal berikut perlu dipertimbangkan. :

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

Tanggal contoh bisa juga variabel di objek result.data setelah panggilan API.

Untuk opsi untuk menampilkan tanggal dalam format yang diinginkan (mis. Untuk menampilkan hari kerja yang panjang) periksa MDN doc .

Patrick
sumber