diff --git a/django/core/serializers/json.py b/django/core/serializers/json.py index b955939e0d04..4b4f7f30c032 100644 --- a/django/core/serializers/json.py +++ b/django/core/serializers/json.py @@ -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 @@ -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) diff --git a/docs/releases/6.2.txt b/docs/releases/6.2.txt index 1193f7a84d89..3bafa4f583e0 100644 --- a/docs/releases/6.2.txt +++ b/docs/releases/6.2.txt @@ -256,6 +256,12 @@ Miscellaneous pollution vulnerability in the ``Array`` prototype was fixed in `ES5 `_. +* :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 diff --git a/tests/serializers/test_json.py b/tests/serializers/test_json.py index 2c8ad5708e64..6f527fa4a064 100644 --- a/tests/serializers/test_json.py +++ b/tests/serializers/test_json.py @@ -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)