[Django] #37108: DjangoJSONEncoder encodes time inconsistently depending on microseconds

23 views
Skip to first unread message

Django

unread,
May 19, 2026, 9:46:27 AMMay 19
to django-...@googlegroups.com
#37108: DjangoJSONEncoder encodes time inconsistently depending on microseconds
-------------------------------------+-------------------------------------
Reporter: Roman Donchenko | Type:
| Uncategorized
Status: new | Component: Core
| (Serialization)
Version: 6.0 | Severity: Normal
Keywords: | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Consider this script:

{{{
import json
from datetime import datetime, time, UTC
from django.core.serializers.json import DjangoJSONEncoder

print(json.dumps(datetime.fromtimestamp(0, UTC), cls=DjangoJSONEncoder))
print(json.dumps(datetime.fromtimestamp(0.000001, UTC),
cls=DjangoJSONEncoder))

print(json.dumps(time(), cls=DjangoJSONEncoder))
print(json.dumps(time(microsecond=1), cls=DjangoJSONEncoder))
}}}

It outputs the following:

{{{
"1970-01-01T00:00:00Z"
"1970-01-01T00:00:00.000Z"
"00:00:00"
"00:00:00.000"
}}}

In other words, even though the encoded value is logically the same,
`DjangoJSONEncoder` either adds ".000" or doesn't depending on the number
of microseconds.

This is not a big problem, but it is mildly annoying. Imagine you save a
fixture with `dumpdata`, load it back, then save again. The first time,
the encoder adds ".000", then after loading the number of microseconds is
set to 0, so after the second save, the encoder ''doesn't'' add ".000".
Now there's an unnecessary change in the saved fixture.

Seems like the encoder should either always add ".000" or always omit it.
--
Ticket URL: <https://code.djangoproject.com/ticket/37108>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Django

unread,
May 19, 2026, 4:05:17 PMMay 19
to django-...@googlegroups.com
#37108: DjangoJSONEncoder encodes time inconsistently depending on microseconds
-------------------------------------+-------------------------------------
Reporter: Roman Donchenko | Owner: (none)
Type: Uncategorized | Status: new
Component: Core | Version: 6.0
(Serialization) |
Severity: Normal | Resolution:
Keywords: | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Richard Autry):

To add some more information after looking into this, this behavior is due
to the truncation of microseconds which was added as part of the timezone
effort back in 2011:

https://github.com/django/django/commit/9b1cb755a28f020e27d4268c214b25315d4de42e
#diff-45e847dda304240b8c519e6b57aa635ae806341986b14e0f17367450f1cbe0e9R47

i.e.

{{{
>>> now.isoformat()[:23] + now.isoformat()[26:]
'2026-05-19T15:50:51.060'
}}}

When you specify a timestamp of 0.000001, it is be stepped down to the
logical ".000" you mentioned. However, whether or not microseconds is
included is a function of the standard `datetime` library and not the
actual `DjangoJSONEncoder`.

i.e.

{{{
>>> time().isoformat()
'00:00:00'
>>> time(microsecond=1).isoformat()
'00:00:00.000001'
}}}

I'm not sure `DjangoJSONEncoder` should be responsible for providing
default behavior that might override this.
--
Ticket URL: <https://code.djangoproject.com/ticket/37108#comment:1>

Django

unread,
May 19, 2026, 4:27:14 PMMay 19
to django-...@googlegroups.com
#37108: DjangoJSONEncoder encodes time inconsistently depending on microseconds
-------------------------------------+-------------------------------------
Reporter: Roman Donchenko | Owner: (none)
Type: Uncategorized | Status: new
Component: Core | Version: 6.0
(Serialization) |
Severity: Normal | Resolution:
Keywords: | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Roman Donchenko):

FWIW, nowadays `isoformat` supports a `timespec` parameter that can
control how the microseconds are represented - including an option to
truncate to millisecond precision.
--
Ticket URL: <https://code.djangoproject.com/ticket/37108#comment:2>

Django

unread,
May 21, 2026, 7:05:27 AMMay 21
to django-...@googlegroups.com
#37108: DjangoJSONEncoder encodes time inconsistently depending on microseconds
--------------------------------------+------------------------------------
Reporter: Roman Donchenko | Owner: (none)
Type: Cleanup/optimization | Status: new
Component: Core (Serialization) | Version: 6.0
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
--------------------------------------+------------------------------------
Changes (by Sarah Boyce):

* stage: Unreviewed => Accepted
* type: Uncategorized => Cleanup/optimization

Comment:

I think we should always omit `.000`, something like:
{{{#!diff
diff --git a/django/core/serializers/json.py
b/django/core/serializers/json.py
index b955939e0d..1cf24a1211 100644
--- a/django/core/serializers/json.py
+++ b/django/core/serializers/json.py
@@ -90,9 +90,8 @@ 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")
+ r = r.replace(".000", "")
if r.endswith("+00:00"):
r = r.removesuffix("+00:00") + "Z"
}}}

This is a slight breaking change and so I would recommend a release note
--
Ticket URL: <https://code.djangoproject.com/ticket/37108#comment:3>

Django

unread,
May 21, 2026, 3:22:53 PMMay 21
to django-...@googlegroups.com
#37108: DjangoJSONEncoder encodes time inconsistently depending on microseconds
-------------------------------------+-------------------------------------
Reporter: Roman Donchenko | Owner:
Type: | SnippyCodes
Cleanup/optimization | Status: assigned
Component: Core | Version: 6.0
(Serialization) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by SnippyCodes):

* has_patch: 0 => 1
* owner: (none) => SnippyCodes
* status: new => assigned

--
Ticket URL: <https://code.djangoproject.com/ticket/37108#comment:4>

Django

unread,
May 22, 2026, 9:31:38 AMMay 22
to django-...@googlegroups.com
#37108: DjangoJSONEncoder encodes time inconsistently depending on microseconds
-------------------------------------+-------------------------------------
Reporter: Roman Donchenko | Owner:
Type: | SnippyCodes
Cleanup/optimization | Status: assigned
Component: Core | Version: 6.0
(Serialization) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0
Needs tests: 1 | Patch needs improvement: 1
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Sarah Boyce):

* needs_better_patch: 0 => 1
* needs_tests: 0 => 1

--
Ticket URL: <https://code.djangoproject.com/ticket/37108#comment:5>

Django

unread,
May 22, 2026, 11:06:42 AMMay 22
to django-...@googlegroups.com
#37108: DjangoJSONEncoder encodes time inconsistently depending on microseconds
-------------------------------------+-------------------------------------
Reporter: Roman Donchenko | Owner:
Type: | SnippyCodes
Cleanup/optimization | Status: assigned
Component: Core | Version: 6.0
(Serialization) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 1
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Sarah Boyce):

* needs_better_patch: 1 => 0
* needs_docs: 0 => 1
* needs_tests: 1 => 0

--
Ticket URL: <https://code.djangoproject.com/ticket/37108#comment:6>

Django

unread,
May 26, 2026, 7:43:37 AMMay 26
to django-...@googlegroups.com
#37108: DjangoJSONEncoder encodes time inconsistently depending on microseconds
-------------------------------------+-------------------------------------
Reporter: Roman Donchenko | Owner:
Type: | SnippyCodes
Cleanup/optimization | Status: assigned
Component: Core | Version: 6.0
(Serialization) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 1
Needs tests: 0 | Patch needs improvement: 1
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Sarah Boyce):

* needs_better_patch: 0 => 1

--
Ticket URL: <https://code.djangoproject.com/ticket/37108#comment:7>

Django

unread,
May 27, 2026, 4:40:07 AMMay 27
to django-...@googlegroups.com
#37108: DjangoJSONEncoder encodes time inconsistently depending on microseconds
-------------------------------------+-------------------------------------
Reporter: Roman Donchenko | Owner:
Type: | SnippyCodes
Cleanup/optimization | Status: assigned
Component: Core | Version: 6.0
(Serialization) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Ready for
| checkin
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Sarah Boyce):

* needs_better_patch: 1 => 0
* needs_docs: 1 => 0
* stage: Accepted => Ready for checkin

--
Ticket URL: <https://code.djangoproject.com/ticket/37108#comment:8>

Django

unread,
Jun 1, 2026, 4:34:50 AMJun 1
to django-...@googlegroups.com
#37108: DjangoJSONEncoder encodes time inconsistently depending on microseconds
-------------------------------------+-------------------------------------
Reporter: Roman Donchenko | Owner:
Type: | SnippyCodes
Cleanup/optimization | Status: closed
Component: Core | Version: 6.0
(Serialization) |
Severity: Normal | Resolution: fixed
Keywords: | Triage Stage: Ready for
| checkin
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Sarah Boyce <42296566+sarahboyce@…>):

* resolution: => fixed
* status: assigned => closed

Comment:

In [changeset:"ea9742c6d02edf64fc0969f21506f2048c976051" ea9742c]:
{{{#!CommitTicketReference repository=""
revision="ea9742c6d02edf64fc0969f21506f2048c976051"
Fixed #37108 -- Made DjangoJSONEncoder consistently omit .000
microseconds.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/37108#comment:9>
Reply all
Reply to author
Forward
0 new messages