JSON datetime ระหว่าง Python และ JavaScript


393

ฉันต้องการส่งวัตถุ datetime.datetime ในรูปแบบต่อเนื่องจาก Python โดยใช้JSONและยกเลิกการทำให้เป็นอนุกรมใน JavaScript โดยใช้ JSON วิธีที่ดีที่สุดในการทำเช่นนี้คืออะไร?


คุณต้องการใช้ห้องสมุดหรือคุณต้องการรหัสนี้ด้วยตัวเอง?
guettli

คำตอบ:


370

คุณสามารถเพิ่มพารามิเตอร์ '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))

อัปเดต: เพิ่มเอาต์พุตประเภทและค่า
อัปเดต: จัดการกับวันที่ด้วย


11
ปัญหาคือว่าถ้าคุณมีวัตถุอื่น ๆ ในรายการ / dict รหัสนี้จะแปลงเป็นไม่มี
Tomasz Wysocki

5
json.dumps จะไม่ทราบวิธีการแปลงไฟล์เหล่านั้น แต่ข้อยกเว้นจะถูกแทนที่ น่าเสียดายที่การแก้ไขแลมบ์ดาบรรทัดเดียวมีข้อบกพร่อง หากคุณต้องการมีข้อยกเว้นเกี่ยวกับสิ่งที่ไม่รู้จัก (ซึ่งเป็นความคิดที่ดี) ให้ใช้ฟังก์ชันที่ฉันเพิ่มไว้ด้านบน
JT

9
รูปแบบผลลัพธ์เต็มควรมีเขตเวลาเช่นกัน ... และ isoformat () ไม่ได้มีฟังก์ชั่นนี้ ... ดังนั้นคุณควรตรวจสอบให้แน่ใจว่าได้ผนวกข้อมูลบนสตริงก่อนที่จะส่งคืน
Nick Franceschina

3
นี่เป็นวิธีที่ดีที่สุด เหตุใดจึงไม่เลือกคำตอบนี้
Brendon Crawford

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

น่าเสียดายที่ตัวสร้าง Date ของ Javascript ไม่ยอมรับสตริง RfC 3339 แต่มีparsersมากมายในอินเทอร์เน็ต

huTools.hujsonพยายามจัดการปัญหาการเข้ารหัสที่พบบ่อยที่สุดที่คุณอาจเจอในรหัส Python รวมถึงออบเจ็กต์วันที่ / วันที่และเวลาในการจัดการเขตเวลาอย่างถูกต้อง


17
กลไกการจัดรูปแบบวันที่นี้ได้รับการสนับสนุนตามปกติทั้งโดยdatetime: datetime.isoformat () และโดยsimplejsonซึ่งจะถ่ายโอนข้อมูลdatetimeวัตถุเป็นisoformatสตริงตามค่าเริ่มต้น ไม่จำเป็นต้องstrftimeแฮ็คคู่มือ
jrk

9
@jrk - ฉันไม่ได้รับการแปลงอัตโนมัติจากdatetimeวัตถุเป็นisoformatสตริง สำหรับฉันsimplejson.dumps(datetime.now())อัตราผลตอบแทนTypeError: datetime.datetime(...) is not JSON serializable
kostmo

6
json.dumps(datetime.datetime.now().isoformat())เป็นที่ที่เวทมนต์เกิดขึ้น
jathanism

2
ความสวยงามของ simplejson คือถ้าฉันมีโครงสร้างข้อมูลที่ซับซ้อนมันจะทำการแยกวิเคราะห์และเปลี่ยนเป็น JSON ถ้าฉันต้องทำ json.dumps (datetime.datetime.now (). isoformat ()) สำหรับวัตถุ datetime ทุกครั้งฉันจะสูญเสียสิ่งนั้น มีวิธีแก้ไขปัญหานี้หรือไม่?
andrewrk

1
superjoe30: ดูstackoverflow.com/questions/455580/…เกี่ยวกับวิธีการทำเช่นนั้น
สูงสุด

67

ฉันทำงานเสร็จแล้ว

สมมติว่าคุณมี Python datetime object, d , สร้างขึ้นด้วย datetime.now () ค่าของมันคือ:

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

คุณสามารถทำให้เป็นอนุกรมกับ JSON เป็นสตริง datetime 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:

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คุณสามารถ subclass JSONEncoder และแทนที่วิธีเริ่มต้น () เพื่อให้ serializers กำหนดเองของคุณเอง:

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()โทรทั่วไปซึ่งใช้ args ที่มีประโยชน์อื่น ๆ (เช่นindent): simplejson.dumps (myobj, cls = JSONEncoder, ... )
rcoup

3
เพราะนั่นจะเรียกเมธอดพาเรนต์ของ JSONEncoder ไม่ใช่เมธอด DateTimeJSONEncoder ของ IE คุณจะเพิ่มขึ้นสองระดับ
Brian Arsuaga

30

ต่อไปนี้เป็นโซลูชันที่สมบูรณ์แบบสำหรับการเข้ารหัสและถอดรหัส datetime.datetime และ datetime.date แบบวนซ้ำโดยใช้jsonโมดูลไลบรารีมาตรฐาน สิ่งนี้ต้องการ Python> = 2.6 เนื่องจาก%fรหัสรูปแบบในสตริงรูปแบบ datetime.datetime.strptime () รองรับตั้งแต่นั้นมาเท่านั้น สำหรับการรองรับ Python 2.5 ให้วาง%fและถอดไมโครวินาทีออกจากสตริงวันที่ ISO ก่อนที่จะพยายามแปลง แต่คุณจะต้องลดความแม่นยำของไมโครวินาที สำหรับการทำงานร่วมกันกับสตริงวันที่ ISO จากแหล่งอื่น ๆ ซึ่งอาจรวมถึงชื่อเขตเวลาหรือออฟเซ็ต UTC คุณอาจต้องตัดส่วนของสตริงวันที่บางส่วนออกก่อนการแปลง สำหรับ parser ที่สมบูรณ์สำหรับสตริงวันที่ 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))

หากคุณพิมพ์วันที่เช่นdatetime.datetime.utcnow().isoformat()[:-3]+"Z"นั้นจะเหมือนกับสิ่งที่ JSON.stringify () สร้างในจาวาสคริปต์
w00t

24

หากคุณมั่นใจว่ามีเพียงจาวาสคริปต์เท่านั้นที่จะใช้งาน JSON ได้ฉันต้องการส่งDateวัตถุJavascript โดยตรง

ctime()วิธีการเกี่ยวกับdatetimeวัตถุที่จะกลับสตริงว่าจาวาสคริวันที่วัตถุสามารถเข้าใจ

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

จาวาสคริปต์จะใช้สิ่งนั้นเป็นตัวอักษรอย่างมีความสุขและคุณได้สร้างวัตถุวันที่ของคุณ


12
ในทางเทคนิคไม่ถูกต้อง JSON แต่มันเป็นวัตถุ JavaScript ที่ถูกต้องตามตัวอักษร (เพื่อประโยชน์ของหลักการฉันจะตั้งค่า Content-Type เป็น text / javascript แทน application / json.) หากผู้บริโภคจะเป็นเพียงการนำ JavaScript ไปใช้เสมอและตลอดไปใช่แล้วนี่มันค่อนข้างสง่างาม ฉันจะใช้มัน
ระบบหยุดชั่วคราว

13
.ctime()เป็นวิธีที่แย่มากในการส่งผ่านข้อมูลเวลา.isoformat()ดีกว่ามาก สิ่งที่.ctime()ทิ้งเขตเวลาและการปรับเวลาตามฤดูกาลเหมือนไม่มีอยู่ ควรฆ่าฟังก์ชันนั้น
Evgeny

หลายปีต่อมา: โปรดอย่าพิจารณาทำสิ่งนี้ นี้จะทำงานหากคุณ eval () json ของคุณใน Javascript ซึ่งคุณไม่ควร ...
24919

11

ในช่วงท้ายเกม ... :)

วิธีแก้ไขที่ง่ายมากคือการแก้ไขค่าเริ่มต้นของโมดูล 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 (ไม่ว่าจะในรหัสที่มีอยู่หรือไม่ก็ตาม)

โปรดทราบว่าบางคนอาจพิจารณาทำการปะแก้ห้องสมุดด้วยวิธีการที่ไม่ดี ต้องใช้ความระมัดระวังเป็นพิเศษในกรณีที่คุณอาจต้องการขยายใบสมัครของคุณมากกว่าหนึ่งวิธี - เป็นกรณีเช่นนี้ฉันขอแนะนำให้ใช้วิธีแก้ปัญหาโดยราเมนหรือ JT และเลือกส่วนขยาย json ที่เหมาะสมในแต่ละกรณี


6
สิ่งนี้จะกินวัตถุที่ไม่สามารถทำให้เป็นอนุกรมNoneได้ คุณอาจต้องการที่จะโยนข้อยกเว้นแทน
เครื่องปั่น

6

ไม่มากที่จะเพิ่มไปยังคำตอบ wiki ของชุมชนยกเว้นการประทับเวลา !

Javascript ใช้รูปแบบต่อไปนี้:

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

ด้านหลาม (สำหรับ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 out นั้นเฟรมเวิร์กส่วนหน้าเช่น 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"

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

ทางด้านจาวาสคริปต์:

var your_date = new Date(data)

ที่ข้อมูลเป็นผลมาจากหลาม



0

เห็นได้ชัดว่ารูปแบบวันที่“ ถูกต้อง” ของ JSON (well 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 ได้เช่นกันในขณะที่ Date ใหม่อาจทำให้เกิดข้อยกเว้น

ในกรณีที่คุณมีวัตถุ DateTime โดยไม่มีมิลลิวินาทีจำเป็นต้องพิจารณาสิ่งต่อไปนี้ :

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

วันที่ตัวอย่างอาจเป็นตัวแปรในวัตถุ result.data หลังจากการเรียก API

สำหรับตัวเลือกที่จะแสดงวันที่ในรูปแบบที่ต้องการ (เช่นเพื่อแสดงวันธรรมดายาว) ตรวจสอบเอกสาร MDN

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.