Why does setting DATE_FORMAT in a Django project's settings often change nothing, and how does FORMAT_MODULE_PATH help?
answer
- the locale answers first
- formats modules per language
- USE_L10N is gone
- your own package, one folder per locale
basics
~20 sDjango 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.
solid answer
~40 s`get_format()` looks up a name like `DATE_FORMAT` first in the modules under `FORMAT_MODULE_PATH` for the active locale, then in Django's `django.conf.locale.<locale>.formats`, and only then in the setting. Django ships a formats module for English and most languages, so a `DATE_FORMAT` in `settings.py` is shadowed. `USE_L10N`, which once let you switch the locale lookup off, was removed in Django 5.0, so localization is always on. To change a locale's formats I set `FORMAT_MODULE_PATH = ["clinic.formats"]` and add `clinic/formats/en/formats.py` with only the names I override. For a single value I pass an explicit format to the `date` filter, and `{% localize off %}` or `|unlocalize` for machine-readable output.
code
python · 6 lines# settings.py
FORMAT_MODULE_PATH = ["clinic.formats"]
# clinic/formats/en/formats.py
DATE_FORMAT = "d/m/Y"
DATETIME_FORMAT = "d/m/Y H:i"go deeper
Recall that Django formats dates per active language and that DATE_FORMAT in settings is only a fallback.
Explain get_format()'s lookup order, the FORMAT_MODULE_PATH directory layout, and the l10n tools for turning localization off locally.
Spot upgrade fallout from the USE_L10N removal and keep machine-readable output out of localized rendering.
Decide which formats are product decisions per market and who owns them, instead of one global format for every language.
## The surprise A developer adds `DATE_FORMAT = "d/m/Y"` to `settings.py`, reloads a page rendered in English, and dates still read `Oct. 1, 2026`. Nothing is broken: the setting is working exactly as documented. ## Why the setting loses Django formats dates, times and numbers through `django.utils.formats.get_format()`. For each format name, such as `DATE_FORMAT`, it looks in this order: 1. The modules on `FORMAT_MODULE_PATH`, in the directory for the **active language's locale** (for `en-us`, first `en_US`, then `en`), each as `<package>.<locale>.formats`. 2. Django's own `django.conf.locale.<locale>.formats` module. 3. Only if no module defines that name, the **setting** of the same name in `settings.py`. Django ships a formats module for English that defines `DATE_FORMAT = "N j, Y"`, so step 2 answers before your setting is ever read. The settings docs say it plainly: the locale-dictated format has higher precedence. ## Localization is always on - Before Django 5.0, `USE_L10N = False` made Django skip the locale modules and use the settings. That setting was **removed in 5.0**; localized formatting is now always on. - Per-call control remains: the `l10n` template library offers `{% localize off %} … {% endlocalize %}` and the `|unlocalize` filter, useful for machine-readable values such as coordinates in a `data-` attribute. - `USE_THOUSAND_SEPARATOR` (default `False`) still decides whether numbers get grouping separators in templates. ## Overriding a locale with FORMAT_MODULE_PATH `FORMAT_MODULE_PATH` (default `None`) names one package, or a list of packages, holding your own per-locale format modules: ```text clinic/ formats/ __init__.py en/ __init__.py formats.py # DATE_FORMAT = "d/m/Y" de/ __init__.py formats.py ``` ```python FORMAT_MODULE_PATH = ["clinic.formats"] ``` Your module only needs the names it changes; anything it omits falls through to Django's module for that locale, then to the setting. ## What these formats cover | Kind | Examples of names | Used by | |---|---|---| | Display | `DATE_FORMAT`, `DATETIME_FORMAT`, `SHORT_DATE_FORMAT`, `TIME_FORMAT` | the `date` and `time` filters, default rendering of dates in templates | | Input | `DATE_INPUT_FORMATS`, `DATETIME_INPUT_FORMATS` | form fields parsing user input | | Numbers | `DECIMAL_SEPARATOR`, `THOUSAND_SEPARATOR`, `NUMBER_GROUPING` | number rendering and localized number inputs | | Calendar | `FIRST_DAY_OF_WEEK` | calendar widgets and the admin | ## Input formats follow the locale too Parsing works the same way as display. A form field reads `DATE_INPUT_FORMATS` and `DATETIME_INPUT_FORMATS` through `get_format()`, so the accepted input depends on the active language: | Active language | `01/10/2026` typed in a date field | `01.10.2026` | |---|---|---| | English | 10 January 2026 (`%m/%d/%Y`) | rejected | | German | rejected | 1 October 2026 (`%d.%m.%Y`) | - When a locale module supplies an input-format list, Django appends the ISO formats such as `%Y-%m-%d`, so ISO input from a date picker is accepted in every language. - An override in your `FORMAT_MODULE_PATH` package can add formats a market expects, for example day-first slashes for a British audience. ## Relation to time zones Formatting and time zones are separate steps. For an aware datetime a template first **converts** it to the current time zone and then **formats** it with the active locale's format. A German speaker in Singapore sees Singapore wall time in German date format; changing one never changes the other. ## Practical guidance - Pick formats per locale, not per site; a single `DATE_FORMAT` for every language is rarely what users want. - Keep a formats module small and reviewed: the docs warn that values in it can be exposed through `get_format()`, which the `date` filter uses, so never put secrets there. - For one-off output, pass an explicit format to the filter: `{{ appt.starts_at|date:"d/m/Y H:i" }}` bypasses the locale lookup entirely.
- How do you print a number without locale separators inside a Django template, for example in a data attribute?Load the `l10n` library and use `{{ value|unlocalize }}`, or wrap the section in `{% localize off %}` … `{% endlocalize %}`. Both render the raw value, so a decimal stays `12.5` rather than `12,5` under a German locale.
saying these in an interview costs you the question
- Setting USE_L10N = False restores the settings-based formats
- DATE_FORMAT in settings always overrides the locale
- FORMAT_MODULE_PATH files replace Django's whole locale module
- Formatting a date also converts it to UTC