这就是 DJango 的做法
[1],
[2]:
DjangoJSONEncoder
class django.core.serializers.json.DjangoJSONEncoder¶
JSON 序列化器使用 DjangoJSONEncoder 进行编码。它是 JSONEncoder 的子类,可以处理以下附加类型:
datetime
一个形如 YYYY-MM-DDTHH:mm:ss.sssZ 或者 YYYY-MM-DDTHH:mm:ss.sss+HH:MM 的字符串,格式定义在 ECMA-262 中。
def default(self, o):
if isinstance(o, datetime.datetime):
r = o.isoformat()
if o.microsecond:
r = r[:23] + r[26:]
if r.endswith('+00:00'):
r = r[:-6] + 'Z'
return r
print(f"aaa{naive_utcnow.isoformat()[:23] = }")
请注意,日期时间对象
datetime
可能包含或不包含时区信息(区分为
naive和
aware日期时间对象)。
在您的示例中,
datetime.utcnow()
将生成一个naive对象,它与django代码无法正常工作。
如果您始终希望在最后加上
Z
(例如与其他系统(如客户端浏览器和节点)进行互操作性),请查看下面的脚本,我将解释如何到达那里以及如何处理使用python处理日期时间的一些常见问题:
from datetime import datetime, timezone
utc = timezone.utc
naive_utcnow = datetime.utcnow()
aware_utcnow = datetime.now(utc)
print(f"{naive_utcnow.isoformat() = }")
print(f"{aware_utcnow.isoformat() = }")
def toECMA262_django(dt: datetime):
s = dt.isoformat()
if dt.microsecond:
s = s[:23] + s[26:]
if s.endswith('+00:00'):
s = s[:-6] + 'Z'
return s
print(f"{toECMA262_django(naive_utcnow) = }")
print(f"{toECMA262_django(aware_utcnow) = }")
def toECMA262_v2(dt: datetime, default_tz=utc):
if not dt.tzinfo:
dt = dt.replace(tzinfo=default_tz)
s = dt.isoformat()
if dt.microsecond:
s = s[:23] + s[26:]
if s.endswith('+00:00'):
s = s[:-6] + 'Z'
return s
print(f"{toECMA262_v2(naive_utcnow) = }")
print(f"{toECMA262_v2(aware_utcnow) = }")
print(f"{toECMA262_v2(datetime.utcnow()) = }")
print(f"{toECMA262_v2(datetime.now()) = }")
def toECMA262_v3(dt: datetime, naive_as_tz=None):
if not dt.tzinfo and naive_as_tz:
dt = dt.replace(tzinfo=naive_as_tz)
s = dt.isoformat()
if dt.microsecond:
s = s[:23] + s[26:]
if s.endswith('+00:00'):
s = s[:-6] + 'Z'
return s
print(f"{toECMA262_v3(naive_utcnow) = }")
print(f"{toECMA262_v3(naive_utcnow, utc) = }")
print(f"{toECMA262_v3(aware_utcnow) = }")
print(f"{toECMA262_v3(datetime.utcnow()) = }")
print(f"{toECMA262_v3(datetime.utcnow(), utc) = }")
print(f"{toECMA262_v3(datetime.now()) = }")
def toECMA262_v4(dt: datetime, naive_as_tz=None):
if not dt.tzinfo:
if not naive_as_tz:
raise ValueError('Aware object or naive_as_tz required')
dt = dt.replace(tzinfo=naive_as_tz)
s = dt.isoformat()
if dt.microsecond:
s = s[:23] + s[26:]
if s.endswith('+00:00'):
s = s[:-6] + 'Z'
return s
def try_print(expr):
'''little helper function to print exceptions in place'''
try:
print(f"{expr} = ", end='')
print(repr(eval(expr)))
except ValueError as exc:
print(repr(exc))
try_print("toECMA262_v4(naive_utcnow, utc)")
try_print("toECMA262_v4(naive_utcnow)")
try_print("toECMA262_v4(aware_utcnow)")
try_print("toECMA262_v4(datetime.utcnow(), utc)")
try_print("toECMA262_v4(datetime.utcnow())")
try_print("toECMA262_v4(datetime.now())")
import dateutil.tz
tzlocal = dateutil.tz.tzlocal()
aware_now = datetime.now(tzlocal)
print(f"{toECMA262_v4(aware_now) = }")
def toECMA262_v5(dt: datetime, naive_as_tz=None):
if not dt.tzinfo:
if not naive_as_tz:
raise ValueError('Aware object or naive_as_tz required')
dt = dt.replace(tzinfo=naive_as_tz)
dt = dt.astimezone(utc)
s = dt.isoformat()
if dt.microsecond:
s = s[:23] + s[26:]
if s.endswith('+00:00'):
s = s[:-6] + 'Z'
return s
try_print("toECMA262_v5(naive_utcnow, utc)")
try_print("toECMA262_v5(naive_utcnow)")
try_print("toECMA262_v5(aware_utcnow)")
try_print("toECMA262_v5(aware_now)")
try_print("toECMA262_v5(datetime.utcnow(), utc)")
try_print("toECMA262_v5(datetime.utcnow())")
try_print("toECMA262_v5(datetime.now())")
try_print("toECMA262_v5(datetime.now(), tzlocal)")
脚本的输出:
naive_utcnow.isoformat() = '2021-05-25T07:45:22.774853'
aware_utcnow.isoformat() = '2021-05-25T07:45:22.774856+00:00'
toECMA262_django(naive_utcnow) = '2021-05-25T07:45:22.774'
toECMA262_django(aware_utcnow) = '2021-05-25T07:45:22.774Z'
toECMA262_v2(naive_utcnow) = '2021-05-25T07:45:22.774Z'
toECMA262_v2(aware_utcnow) = '2021-05-25T07:45:22.774Z'
toECMA262_v2(datetime.utcnow()) = '2021-05-25T07:45:22.774Z'
toECMA262_v2(datetime.now()) = '2021-05-25T04:45:22.774Z'
toECMA262_v3(naive_utcnow) = '2021-05-25T07:45:22.774'
toECMA262_v3(naive_utcnow, utc) = '2021-05-25T07:45:22.774Z'
toECMA262_v3(aware_utcnow) = '2021-05-25T07:45:22.774Z'
toECMA262_v3(datetime.utcnow()) = '2021-05-25T07:45:22.775'
toECMA262_v3(datetime.utcnow(), utc) = '2021-05-25T07:45:22.775Z'
toECMA262_v3(datetime.now()) = '2021-05-25T04:45:22.775'
toECMA262_v4(naive_utcnow, utc) = '2021-05-25T07:45:22.774Z'
toECMA262_v4(naive_utcnow) = ValueError('Aware object or naive_as_tz required')
toECMA262_v4(aware_utcnow) = '2021-05-25T07:45:22.774Z'
toECMA262_v4(datetime.utcnow(), utc) = '2021-05-25T07:45:22.775Z'
toECMA262_v4(datetime.utcnow()) = ValueError('Aware object or naive_as_tz required')
toECMA262_v4(datetime.now()) = ValueError('Aware object or naive_as_tz required')
toECMA262_v4(aware_now) = '2021-05-25T04:45:22.788-03:00'
toECMA262_v5(naive_utcnow, utc) = '2021-05-25T07:45:22.774Z'
toECMA262_v5(naive_utcnow) = ValueError('Aware object or naive_as_tz required')
toECMA262_v5(aware_utcnow) = '2021-05-25T07:45:22.774Z'
toECMA262_v5(aware_now) = '2021-05-25T07:45:22.788Z'
toECMA262_v5(datetime.utcnow(), utc) = '2021-05-25T07:45:22.788Z'
toECMA262_v5(datetime.utcnow()) = ValueError('Aware object or naive_as_tz required')
toECMA262_v5(datetime.now()) = ValueError('Aware object or naive_as_tz required')
toECMA262_v5(datetime.now(), tzlocal) = '2021-05-25T07:45:22.788Z'
版本5及以上总是输出以ECMA-262兼容的字符串结尾的
Z
,可以接受任何时区的日期时间对象。如果传递了无时区意义的日期时间对象,调用者代码必须指定对象是否在UTC、本地或其他任何时区,并自动转换为UTC。
PS:我使用了Python>=3.8的新fstring调试语法,通过使用
=
更友好且简洁地打印输出,除此之外,代码应该可以在Python>=3.2上正常运行。