Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions django/core/serializers/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,10 @@ class DjangoJSONEncoder(json.JSONEncoder):
def default(self, o):
# See "Date Time String Format" in the ECMA-262 specification.
if isinstance(o, datetime.datetime):
r = o.isoformat()
if o.microsecond:
r = r[:23] + r[26:]
r = o.isoformat(
sep="T",
timespec="milliseconds" if o.microsecond // 1000 else "seconds",
)
if r.endswith("+00:00"):
r = r.removesuffix("+00:00") + "Z"
return r
Expand All @@ -101,9 +102,9 @@ def default(self, o):
elif isinstance(o, datetime.time):
if is_aware(o):
raise ValueError("JSON can't represent timezone-aware times.")
r = o.isoformat()
if o.microsecond:
r = r[:12]
r = o.isoformat(
timespec="milliseconds" if o.microsecond // 1000 else "seconds"
)
return r
elif isinstance(o, datetime.timedelta):
return duration_iso_string(o)
Expand Down
6 changes: 6 additions & 0 deletions docs/releases/6.2.txt
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,12 @@ Miscellaneous
pollution vulnerability in the ``Array`` prototype was fixed in
`ES5 <https://262.ecma-international.org/5.1/#sec-11.1.4>`_.

* :class:`~django.core.serializers.json.DjangoJSONEncoder` now omits the
millisecond component of serialized ``datetime.datetime`` and
``datetime.time`` objects if they have zero milliseconds. For example,
``datetime.datetime(2000, 1, 1, 0, 0, 0, 1)`` now serializes to
``"2000-01-01T00:00:00"`` rather than ``"2000-01-01T00:00:00.000"``.

.. _deprecated-features-6.2:

Features deprecated in 6.2
Expand Down
25 changes: 25 additions & 0 deletions tests/serializers/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,3 +329,28 @@ def test_timedelta(self):
json.dumps({"duration": duration}, cls=DjangoJSONEncoder),
'{"duration": "P0DT00H00M00S"}',
)

def test_datetime_and_time_microseconds(self):
tests = [
(datetime.datetime(2000, 1, 1, 0, 0, 0, 0), '"2000-01-01T00:00:00"'),
(datetime.datetime(2000, 1, 1, 0, 0, 0, 1), '"2000-01-01T00:00:00"'),
(
datetime.datetime(2000, 1, 1, 0, 0, 0, 1000),
'"2000-01-01T00:00:00.001"',
),
(
datetime.datetime(2000, 1, 1, 0, 0, 0, 1001),
'"2000-01-01T00:00:00.001"',
),
(
datetime.datetime(2000, 1, 1, 0, 0, 0, 123000),
'"2000-01-01T00:00:00.123"',
),
(datetime.time(0, 0, 0, 0), '"00:00:00"'),
(datetime.time(0, 0, 0, 1), '"00:00:00"'),
(datetime.time(0, 0, 0, 1000), '"00:00:00.001"'),
(datetime.time(0, 0, 0, 123000), '"00:00:00.123"'),
]
for value, expected in tests:
with self.subTest(value=value):
self.assertEqual(json.dumps(value, cls=DjangoJSONEncoder), expected)
Loading