1.The settings that turn it on, and what each one actually controls
Four settings decide how Django handles language and time, and they are easy to confuse because two of them look like defaults and are not. `USE_TZ` decides whether datetimes are timezone-aware — it is **on by default**, and it is the setting that makes `timezone.now()` return an aware value in UTC. `TIME_ZONE` is the *default display* timezone, not the storage one; storage is UTC whenever `USE_TZ` is on. `LANGUAGE_CODE` is the fallback language for a request whose own preference could not be determined. `USE_I18N` switches the translation machinery on at all.
Key features
- Time zone support is enabled by default — `USE_TZ` must be explicitly set to `False` to disable it
- With `USE_TZ = True`, datetimes are stored in UTC; `TIME_ZONE` is the default *display* zone
- `timezone.now()` returns an aware datetime when `USE_TZ = True` and a naive one when it is `False`
- `LANGUAGE_CODE` is the fallback only — `LocaleMiddleware` resolves per request and sets `request.LANGUAGE_CODE`
- Discovery order: URL prefix → cookie → `Accept-Language` header → `LANGUAGE_CODE`
- Django never guesses a user's timezone — you store it and call `timezone.activate()` yourself
Storage versus display — which setting is on which side
How the active language and the active timezone are resolved
- Whole: USE_TZ = True TIME_ZONE = "Europe/London" USE_I18N = True LANGUAGE_CODE = "en-gb"
- USE_TZ = True — Storage: On by default. Datetimes become aware and are stored in UTC. Turning this off does not simplify anything — it moves the ambiguity into your data.
- TIME_ZONE = "Europe/London" — Display default: What a timestamp renders as when nothing more specific is active. It is not where the value is stored, and naming it "the project timezone" is what causes the confusion.
- USE_I18N = True — Machinery switch: Turns the translation layer on. With it off, `gettext` calls return the original string and `.po` files are never consulted.
- LANGUAGE_CODE = "en-gb" — Fallback only: Used when `LocaleMiddleware` finds no URL prefix, no cookie and no usable `Accept-Language` header. Most real requests never reach it.
Command
USE_TZ = True # aware datetimes in UTC; TIME_ZONE only decides how they renderTime zone support is on by default. `USE_TZ` controls *storage* — aware datetimes, saved in UTC — while `TIME_ZONE` only controls the default *display* zone; calling it "the project timezone" is what makes people think it changes what is stored. `LANGUAGE_CODE` is likewise a fallback: `LocaleMiddleware` resolves per request via URL prefix → cookie → `Accept-Language` → `LANGUAGE_CODE`. There is no equivalent for time, because no header carries a zone — store it on the user and call `timezone.activate()`, then deactivate in a `finally` so it cannot leak into the next request on that thread.