Django and PostgreSQL: naive datetime conversion at the application-database boundary
0 reputation · 31 Jul 2020, 21:01 UTC
0 reputation · 31 Jul 2020, 21:01 UTC
Determine how Django converts a naive datetime submitted through a model form into a PostgreSQL timestamptz value when USE_TZ is True.
Django interprets naive input as local time in settings.TIME_ZONE, while PostgreSQL stores timestamptz internally in UTC. It is unclear whether Django's database adapter localizes the naive value before transmission or relies on the backend to interpret it, and how DST gaps or folds are handled at that boundary.
Assumes Django 4.2 or later with zoneinfo; older pytz projects and non-PostgreSQL backends may behave differently.
34775 reputation · 01 Aug 2020, 01:15 UTC
When USE_TZ=True, Django treats a naive datetime submitted via a model form as local time in the active TIME_ZONE, makes it timezone‑aware, converts it to UTC, and passes that UTC value to the psycopg2 adapter, which stores it as a timestamptz in PostgreSQL.
timezone.make_aware(value, timezone.get_current_timezone()).timezone.localtime(value, timezone.utc) before being handed to the database adapter.make_aware raises a ValueError unless the caller supplies an explicit is_dst flag, so the form validation will reject such input.settings.TIME_ZONE after data is stored does not alter existing UTC values; newly submitted naive datetimes are interpreted with the new zone, while existing rows are displayed according to the current active zone when read.datetime object.settings.TIME_ZONE as the current timezone.timezone.make_aware(naive_dt, timezone.get_current_timezone()) → aware datetime in the local zone.timezone.localtime(aware_dt, timezone.utc).Use comments to ask for clarification. Post a solution as an answer.
34,775 reputation · 31 Jul 2020, 21:21 UTC
In Django 4.2+, the naive datetime from a form is first passed to timezone.make_aware() with settings.TIME_ZONE. The resulting aware datetime is then explicitly converted to UTC via timezone.localtime(aware_dt, timezone.utc). The UTC value is what the PostgreSQL backend receives – the adapter (psycopg2) simply forwards the string representation; PostgreSQL never re‑interprets the zone.
Because timestamptz stores UTC internally, PostgreSQL’s own timezone setting has no effect on storage. It only influences how the value is displayed when queried without conversion.
Zoneinfo resolves ambiguous times to the earlier (standard‑time) offset by default; nonexistent times raise a ValueError unless is_dst is supplied to make_aware(). This logic is exercised at the ORM layer, not by the database.
Changing settings.TIME_ZONE after a row is stored does not alter the stored UTC value – new saves will use the updated zone, and reads will format the UTC instant into the current active zone.