skip to content

Time Zones & Formats

With USE_TZ Django stores aware datetimes in UTC and converts them to the current time zone for display, alongside locale-aware number and date formats. Interviewers probe naive datetimes.

part ofDjangooverview, primer and where to startread it →
on this pageshow

explore

questions

5

In a Django project with USE_TZ enabled, why use django.utils.timezone.now() instead of datetime.datetime.now()?

level: juniorimportance: must knowfreq 60%

answer

  1. aware versus naive
  2. what the database stores
  3. follows the USE_TZ setting
  4. RuntimeWarning on save

basics

~20 s

With USE_TZ on, timezone.now() returns an aware datetime in UTC, matching how Django stores datetimes. datetime.now() is naive local time: saving it makes Django warn and assume TIME_ZONE, and comparing it with aware values raises TypeError.

solid answer

~40 s

`USE_TZ` is `True` by default in current Django, which means datetimes are stored in UTC and handled as aware objects. `timezone.now()` is `datetime.now(tz=UTC)` in that case, and a naive value when `USE_TZ` is off, so it always matches the project's setting. `datetime.now()` returns a naive wall-clock reading. Saving it to a `DateTimeField` emits a `RuntimeWarning` and Django interprets it in the default time zone, `TIME_ZONE`, which is ambiguous in the DST fall-back hour. Comparing it with an aware value loaded from the database raises `TypeError`. So I use `timezone.now()` for the current instant and `localtime()`/`localdate()` when I need the user's wall clock.

code

python · 8 lines
python
from django.utils import timezone

from appointments.models import Appointment

upcoming = Appointment.objects.filter(starts_at__gte=timezone.now())

# datetime.datetime.now() here would be naive: RuntimeWarning, interpreted in TIME_ZONE,
# and `appointment.starts_at < datetime.now()` would raise TypeError.

go deeper

for a junior

Recall that USE_TZ is on by default, that timezone.now() gives an aware UTC datetime, and that datetime.now() gives a naive one.

for a middle

Explain what Django does with a naive value on save: the RuntimeWarning, make_aware with TIME_ZONE, and the TypeError when comparing with aware values.

for a senior

Show how naive values sneak in from external data and tests, and how to make the RuntimeWarning fail loudly so they are caught early.

for a principal

Treat UTC-aware datetimes as a codebase-wide invariant, enforced by review, warnings-as-errors in tests and clear conversion points at the edges.

## The setting behind the question `USE_TZ` switches on Django's **time zone support**. It defaults to `True` in `global_settings.py` (since Django 5.0) and `startproject` writes `USE_TZ = True` as well. With it on, Django's contract is: - datetimes are **stored in UTC** in the database; - code works with **aware** datetimes (ones that carry a `tzinfo` and therefore name an exact instant); - values are converted to a human's local time only at the edges: template rendering and form input. A **naive** datetime has no `tzinfo`: it is a wall-clock reading that does not say which clock. What exactly makes a Python datetime aware is a Python-language topic; here the question is what Django does with each kind. ## What the two calls return | Call | With `USE_TZ = True` | With `USE_TZ = False` | |---|---|---| | `django.utils.timezone.now()` | aware, in UTC | naive, local time | | `datetime.datetime.now()` | naive, the process's local wall time | naive, local time | `timezone.now()` is literally `datetime.now(tz=UTC if settings.USE_TZ else None)`, so it follows the project's setting instead of hard-coding one behaviour. That is the first reason to prefer it: the same code is correct under either setting. ## What goes wrong with datetime.now() When a naive value reaches a `DateTimeField` while `USE_TZ` is on, Django: 1. emits `RuntimeWarning: DateTimeField Appointment.starts_at received a naive datetime (...) while time zone support is active.`; 2. **guesses** its zone by attaching the **default time zone** (`TIME_ZONE`) with `make_aware()`; 3. stores the resulting instant in UTC. Django also sets the process's `TZ` environment variable to `TIME_ZONE`, so on most servers `datetime.now()` *is* wall time in that zone and the guess lands on the right instant. The problems are at the edges: - **DST fall-back.** One wall-clock hour happens twice a year; a naive reading from that hour cannot say which one, and Django's guess may be an hour off. - **Comparisons.** Values read back from the database are aware. Comparing or subtracting them against a naive `datetime.now()` raises `TypeError`, so `if appointment.starts_at < datetime.now():` crashes. - **Hidden coupling.** The code is only right as long as the process zone and `TIME_ZONE` agree, which is not true in every environment. - **Noise.** The `RuntimeWarning` floods logs and test output, hiding real problems. ## The right tools - `timezone.now()` for "the current instant". - `timezone.localtime()` to view an aware value in the **current time zone** (see per-request activation), and `timezone.localdate()` for "today" in that zone. - `timezone.make_aware(naive, tz)` when a naive value from an external source must be interpreted in a known zone. - For a model default, pass the callable `default=timezone.now`, not `timezone.now()`, so it is evaluated per row; field options themselves belong to the model-field topic. ## Naive values from outside the project Not every naive datetime comes from `datetime.now()`. They also arrive from CSV imports, legacy databases, third-party APIs that send local timestamps, and hand-written test data. The rule is the same for all of them: decide which zone the value was recorded in and make it aware **before** it reaches the ORM. - A clinic's export in Lisbon local time: `timezone.make_aware(value, zoneinfo.ZoneInfo("Europe/Lisbon"))`. - A Unix timestamp: `datetime.datetime.fromtimestamp(ts, tz=datetime.UTC)`, which the Django docs recommend over the naive form. - Test fixtures: write aware values, or the suite fills with the same `RuntimeWarning`. Converting once at the boundary keeps every value inside the project aware, so the rest of the code never has to guess. ## Worked example: appointment reminders ```python from datetime import timedelta from django.utils import timezone from appointments.models import Appointment soon = timezone.now() + timedelta(hours=24) due = Appointment.objects.filter(starts_at__lte=soon, reminded=False) ``` Every value here is aware UTC, so the comparison happens between exact instants no matter where the patient, the clinic or the server is. Swapping in `datetime.now()` would still run, but with a warning per query parameter and a one-hour error window around DST changes. ## What an interviewer listens for - That `USE_TZ` is on by default in current Django and that storage is UTC. - That `timezone.now()` returns an aware UTC value, not the user's local time. - That Django's fallback for naive values is the **default** zone (`TIME_ZONE`), not the visitor's zone.

  • Which time zone does Django assume for a naive datetime saved to a DateTimeField while USE_TZ is on?
    The default time zone from the `TIME_ZONE` setting, attached with `make_aware()` after a `RuntimeWarning`. It is not the currently activated zone of the request, so a naive value built from a user's local input is misread whenever the user's zone differs from `TIME_ZONE`.
  • What does timezone.now() return when USE_TZ is False?
    A naive datetime in local time, because it calls `datetime.now(tz=None)` in that case. That is why code written against `timezone.now()` keeps working if a legacy project still runs with time zone support off.

saying these in an interview costs you the question

  • timezone.now() returns the current user's local time
  • USE_TZ is off by default in new Django projects
  • Naive datetimes are interpreted in the request's current time zone
  • datetime.now() and timezone.now() are interchangeable once USE_TZ is on
  • Django stores datetimes in TIME_ZONE rather than UTC
open as a page

With USE_TZ on, what is the difference between Django's TIME_ZONE setting and the current time zone, and what uses each?

level: middleimportance: must knowfreq 50%

basics

~20 s

Django stores datetimes in UTC. TIME_ZONE is the default zone: the fallback and the assumption for naive values. The current time zone, set with timezone.activate() per request, is what templates render in and forms parse input in.

open as a page

How do you make a Django appointments site show and accept times in each signed-in user's own time zone?

level: middleimportance: should knowfreq 42%

basics

~10 s

Store each user's zone name, then in a middleware call timezone.activate(ZoneInfo(name)), or deactivate() when unknown. Django then renders aware datetimes and parses form input in that zone, while storage stays UTC.

open as a page

A Django clinic dashboard filters today's appointments with naive midnight bounds and misses early bookings for Singapore users; what went wrong and how do you fix it?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Naive bounds in a DateTimeField lookup trigger a RuntimeWarning and are interpreted in TIME_ZONE, not the user's activated zone, and date.today() is the server's date. Build aware bounds from timezone.localdate(), or filter with __date, which uses the current zone.

open as a page

Why does setting DATE_FORMAT in a Django project's settings often change nothing, and how does FORMAT_MODULE_PATH help?

level: middleimportance: nice to knowfreq 22%

basics

~20 s

Django always formats dates through the active locale's formats module, which defines DATE_FORMAT, so the setting only applies when the locale has none. FORMAT_MODULE_PATH points to your own per-locale formats modules, which take precedence over Django's.

open as a page