Chapter 20 · Content types, static files and the other contrib apps
20.13 Humanize: naturaltime, intcomma and the other filters
django is one template library, "humanize", of filters that write a value as a person would say it: ordinal, intcomma, intword and apnumber for numbers, and naturalday and naturaltime, with its NaturalTimeFormatter, for dates. Every word they write is looked up in the active language.
The application has no models and no middleware, and HumanizeConfig names it and overrides nothing, AppConfig.ready included. Being installed does two things. The DjangoTemplates backend finds the library in the application’s templatetags package, so {% load humanize %} knows its name (Libraries: Library, custom tags and {% load %}); and the application’s locale directory joins the catalogues DjangoTranslation merges, with the translations of the filters’ words (Message catalogues: DjangoTranslation).
Each filter reads its value as the kind it works on; a value it cannot read, a string of letters or None, it gives back unchanged, and intcomma as text; an infinite float raises OverflowError out of the three that read an integer.
| filter | reads the value as | registered with |
|---|---|---|
ordinal |
reads the value as an integer | registered with is_safe=True |
intcomma |
reads the value as a decimal, unless it is a float | registered with is_safe=True |
intword |
reads the value as an integer | registered with is_safe=False |
apnumber |
reads the value as an integer | registered with is_safe=True |
naturalday |
reads the value as a date | registered with expects_ |
naturaltime |
reads the value as a datetime | registered with no flag |
is_safe=True carries a safe input’s safety to the result and creates none (Autoescaping and safe strings). A value given back unchanged keeps its own safety, flag or none; the flag matters where a filter makes a new string of a safe input: in a run, intcomma of the safe string "1234<b>" wrote "1,234<b>", and with the flag taken off "1,234<b>".
The numbers
ordinal chooses a format, “{}st”, “{}nd”, “{}rd” or “{}th”, by django with a context: “ordinal is 1” for 1, “ordinal 11, 12, 13” when the last two digits are 11 to 13, and otherwise “ordinal 0” to “ordinal 9” by the last digit. Where English needs four messages, a translator has one for each case, and French writes 1 otherwise than 21. A negative number comes back as text. The ordinal is marked safe so that, the comment there says, citing ticket 19988, a translation holding "<sup>" or "<sub>" is not broken.
intcomma, with its argument use_l10n true, the default, hands the value to number_format with use_l10n=True and force_grouping=True: the active language’s separators, grouped whatever USE_THOUSAND_SEPARATOR says, unless the language’s grouping is 0, as the default settings make it for one with no format module: Afrikaans got “45000” (Writing numbers, dates and durations). A value that cannot be made a decimal, and any value with the argument false, is written as its text, its leading digits grouped in threes by commas: the code’s commas, in every language.
intword gives back a number under a million in absolute value. Otherwise it takes from intword_converters the first of a million, a billion and so on to a decillion, then a googol, whose thousandfold exceeds the absolute value, divides by it, and writes the quotient to one place with floatformat into a message chosen by django. The count ngettext is given is round_away_from_one of the quotient, which rounds any number but 1.0 away from one, 1.2 to 2 and 0.5 to 0, so only a quotient of exactly 1.0 has the count 1; the documentation says this may be wrong for some languages. The quotient is rounded only as it is written, so 999,999,999 is “1000.0 million”; from a thousand decillions a number is counted in googols, and from a thousand googols it is given back.
apnumber writes 1 to 9 as words and gives back any other number.
# a run of the example: {{ v|filter }} in English, then in French; \xa0 is a non-breaking space
ordinal 1 '1st' '1<sup>er</sup>'
ordinal 21 '21st' '21<sup>e</sup>'
intcomma 4500.2 '4,500.2' '4\xa0500,2'
intcomma "1234abc" '1,234abc' '1,234abc'
intword 1000000 '1.0 million' '1,0\xa0million'
intword 1200000 '1.2 million' '1,2\xa0millions'
intword 999999999 '1000.0 million' '1000,0\xa0millions'
intword 10**50 '0.0 googol' '0,0 gogol'
apnumber 7 'seven' 'sept'
The dates
naturalday has expects_localtime=True, so in a template an aware datetime is first put in the current time zone, unless time zones are off there or the |timezone filter has converted it already (Time zones). It keeps the year, month and day, compares them with today, taken in the value’s zone or, for a value without one, in local time, and gives “today”, “tomorrow” or “yesterday”; any other day goes to the |date filter with the argument, or the active language’s DATE_FORMAT. In a run at 16:42 in Malta on 16 October, 22:30 UTC on the 15th, half past midnight in Malta, was “today” in a template and “yesterday” inside {% localtime off %}.
naturaltime is registered without expects_localtime=True: the comment above it says it deals properly with naive and aware datetimes, so the cost of converting is avoided. It hands the value to NaturalTimeFormatter, which compares it with now, taken in UTC for an aware value and naive for a naive one. A difference of a day or more goes to django, or timeuntil for the future, which write up to two adjacent units, set in “%(delta)s ago” or “%(delta)s from now”; a smaller one is written in whole hours, minutes or seconds, the largest unit that fits, and less than a second is “now”.
NaturalTimeFormatter holds the phrases; NaturalTimeFormatter and NaturalTimeFormatter hold the units given to timesince in place of its TIME_STRINGS, made by django with a context, “naturaltime-past” or “naturaltime-future”, that sets them apart from each other and from timesince’s own (Translating a message). A count and its unit are joined by a non-breaking space, in the English phrases and, by avoid_wrapping, in timesince’s units; translators’ comments ask every language to keep it.
# a run of the example
now 2026-10-16T16:42+02:00
naturaltime now 'now' 'maintenant'
naturaltime now - 3 h '3\xa0hours ago' 'il y a 3\xa0heures'
naturaltime now - 26 h '1\xa0day, 2\xa0hours ago' 'il y a 1\xa0jour, 2\xa0heures'
naturaltime now + 9 days '1\xa0week, 2\xa0days from now' 'dans 1\xa0semaine, 2\xa0jours'