Python locale: Numbers, Currency, and Dates

Published on: August 9, 2026
Reading time: 6 minutes
International keyboard representing numbers, currency, and dates with Python locale

Users in different countries expect decimal separators, currency symbols, month names, and sorting rules that match their conventions. Python locale exposes the operating system’s POSIX locale database so applications can format and parse culturally sensitive values.

The feature requires care because locale is a process-wide property inherited from the C library. Changing it affects unrelated code and is not thread-safe on most systems. This guide explains startup configuration, categories, numbers, money, dates, collation, encodings, deployment failures, and safer alternatives for concurrent servers.

It complements our guides to Decimal, zoneinfo, statistics, platform, and sysconfig.

What locale represents

A locale is a collection of cultural conventions installed on the system. It can define the decimal point, thousands separator, currency symbol, sign placement, month and weekday names, date formats, and string collation.

Available locale names depend on the operating system and installed data. A name available on a developer workstation may be missing in a production container.

The process starts conservatively

C programs normally start in the portable C locale. Python configures parts of LC_CTYPE during startup to establish text encoding, but other categories retain portable behavior until the application requests user preferences.

import locale

locale.setlocale(locale.LC_ALL, "")

An empty locale string asks the operating system for the user’s default settings, often based on environment variables.

Reading the current setting

current = locale.setlocale(locale.LC_ALL)
print(current)

When the locale argument is omitted, setlocale() returns the current setting. The value can later restore the state, but save-and-restore code remains unsafe when other threads run in between.

setlocale is not thread-safe

The setting is global to the process on most platforms. One thread can switch the decimal separator while another formats an invoice.

Set the locale once at startup in a desktop or command-line program and leave it unchanged. A server that formats responses for different users should use a library with independent locale objects per request.

Locale categories

  • LC_NUMERIC: numeric formatting and parsing.
  • LC_MONETARY: currency conventions.
  • LC_TIME: date and time names and formats.
  • LC_COLLATE: string ordering.
  • LC_CTYPE: character environment and encoding.
  • LC_MESSAGES: system messages on POSIX systems.
  • LC_ALL: all categories together.

Changing only one category reduces the surface of side effects, but the state remains global.

Handling an unavailable locale

try:
    locale.setlocale(locale.LC_ALL, "en_US.UTF-8")
except locale.Error:
    locale.setlocale(locale.LC_ALL, "")

Do not assume one locale name works on Windows, Linux, and macOS. Treat locale installation as a deployment dependency or provide an explicit fallback.

Normalizing locale names

normalized = locale.normalize("en_US.UTF-8")

Normalization applies aliases and platform conventions, but it does not install missing data. If normalization fails, the original name may be returned unchanged.

Numeric and monetary conventions

conventions = locale.localeconv()
print(conventions["decimal_point"])
print(conventions["thousands_sep"])
print(conventions["currency_symbol"])

The dictionary also describes grouping, monetary decimal digits, symbol placement, and sign position. Some numeric fields can equal CHAR_MAX when the locale provides no value.

Formatting numbers

text = locale.format_string(
    "%.2f",
    1234567.89,
    grouping=True,
)

The function follows LC_NUMERIC and percent-format conventions. It is not the full formatting mini-language used by f-strings.

Localizing a normalized number

localized = locale.localize(
    "1234567.89",
    grouping=True,
)

localize(), added in Python 3.10, converts a normalized numeric string to the current locale’s separators.

Parsing a localized number

value = locale.atof("1,234.56")

The example assumes a locale where comma groups thousands and dot marks decimals. atof() calls delocalize() and then converts with float by default.

For money, pass Decimal.

from decimal import Decimal

amount = locale.atof("1,234.56", func=Decimal)

Parsing integers

count = locale.atoi("1,234")

Validate ranges and reject ambiguous forms. Text from another culture can be misinterpreted instead of raising an error.

delocalize

normalized = locale.delocalize("1,234.56")

The function removes grouping and converts the decimal separator. It does not validate business rules such as maximum precision, allowed signs, or value limits.

Formatting currency

text = locale.currency(
    1234.50,
    symbol=True,
    grouping=True,
)

currency() does not work in the C locale. A valid monetary locale must be configured first.

A local symbol such as $ is ambiguous. For cross-border documents, consider international=True and always store the ISO currency code separately.

Locale does not choose the currency

Locale determines presentation, not what currency a value represents. A French user can view a US-dollar invoice. Store amount and currency code as explicit data.

Dates and times

time.strftime() and datetime.strftime() use LC_TIME for month and weekday names and cultural formats.

from datetime import datetime

text = datetime.now().strftime("%A, %B %d, %Y")

Locale is not a time zone. Convert the instant with zoneinfo before formatting it.

Reading system format information

On supported platforms, nl_langinfo() can return date patterns, month names, encoding, decimal characters, and other locale data.

if hasattr(locale, "nl_langinfo"):
    date_format = locale.nl_langinfo(locale.D_FMT)

Available constants vary across systems. Guard platform-specific calls and test the real deployment environment.

Cultural string sorting

Unicode code-point order does not always match user expectations.

words = ["ábaco", "action", "zebra"]
sorted_words = sorted(words, key=locale.strxfrm)

strxfrm() creates keys suitable for repeated comparisons under the current LC_COLLATE. It is more efficient than repeatedly invoking a comparator during sorting.

strcoll

result = locale.strcoll("fan", "foo")

A negative, zero, or positive result represents ordering. The behavior depends on the active collation locale.

Collation is not identity

Strings that sort together are not necessarily equal for login names, keys, or deduplication. Identity needs separate Unicode normalization and domain rules.

Preferred text encoding

encoding = locale.getpreferredencoding(False)

The function estimates the user’s preferred encoding. In Python UTF-8 Mode and on Android, it returns UTF-8.

New persistent files and protocols should declare UTF-8 explicitly instead of inheriting the machine’s locale.

getencoding

Since Python 3.11, getencoding() reports the current locale encoding while ignoring Python UTF-8 Mode.

current_encoding = locale.getencoding()

Choose the function according to the question: user preference or effective LC_CTYPE encoding.

getlocale

language, encoding = locale.getlocale(locale.LC_NUMERIC)

The C locale is represented as (None, None). LC_ALL is not a valid category for getlocale().

Environment variables

On POSIX systems, LC_ALL, category-specific variables, and LANG influence the locale. Containers often fail because an environment variable names a locale that was never installed.

Use UTF-8-capable images and test with the exact production environment.

C and C.UTF-8

The C locale is portable and deterministic. C.UTF-8 is common on Linux but not guaranteed on every system.

Python includes locale-coercion and UTF-8 Mode mechanisms to reduce encoding failures in containers and remote sessions.

Libraries should not change locale

A reusable library does not know which threads or components share the process. Calling setlocale() inside a public function creates an unexpected global side effect.

Accept normalized values, expose configuration, or let the caller provide a formatter.

Web servers

Never switch the process locale for every request. Concurrent requests can mix separators, currency symbols, and month names.

Use an internationalization library with independent locale objects, such as Babel, or deterministic formatters that accept locale data as an explicit argument.

gettext and translated messages

Locale handles cultural conventions, not full application translation. Use the gettext module for message catalogs.

The C-library gettext functions exposed by locale mainly support integration with native libraries.

Testing

Tests should verify only locales installed in the environment and skip unavailable ones explicitly. Avoid changing global locale in parallel.

def try_locale(name):
    try:
        locale.setlocale(locale.LC_ALL, name)
    except locale.Error:
        return False
    return True

Use separate processes when a suite must test multiple cultures reliably.

Security

Localized strings remain external input. Limit their size, validate the accepted characters, and never use a parsed value directly as SQL, a shell command, or a path.

Cultural validity is not business validity. Negative, infinite, or over-precise amounts can still be forbidden.

Common mistakes

  • Changing locale per request.
  • Assuming one locale name exists everywhere.
  • Using float for money.
  • Confusing locale with time zone.
  • Confusing locale with currency code.
  • Comparing localized values as strings.
  • Using collation as identity.
  • Letting a library mutate global locale.

Best practices

  • Configure locale once at startup in simple programs.
  • Use specific categories when possible.
  • Handle locale.Error.
  • Use Decimal for amounts.
  • Keep currency and time zone explicit.
  • Use strxfrm() for repeated sorting.
  • Prefer UTF-8 for persistent formats.
  • Use independent formatters in concurrent servers.

Conclusion

Python locale connects an application to operating-system cultural conventions for numbers, money, dates, encodings, and sorting.

Its main limitation is global, non-thread-safe state. It works well for local tools configured once, while multi-user servers should use independent per-request formatters. Consult the official locale documentation and Unicode CLDR for broader international applications.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Monitor and network representing system information with Python platform
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python platform: System Information

    Learn Python platform to identify operating systems, architecture, Linux distributions, Python versions, and runtime environments safely.

    Ler mais

    Tempo de leitura: 6 minutos
    09/08/2026
    Source code and compiler representing build paths and variables with Python sysconfig
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python sysconfig: Paths and Build Info

    Learn Python sysconfig to discover installation paths, build variables, headers, virtual environments, and platform tags safely.

    Ler mais

    Tempo de leitura: 7 minutos
    09/08/2026
    Hard drive representing memory-mapped files with Python mmap
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python mmap: Memory-Mapped Files

    Learn Python mmap to map files into memory, search bytes, share data, and choose read, write, or copy-on-write access safely.

    Ler mais

    Tempo de leitura: 6 minutos
    09/08/2026
    Source code representing parser tokens and constants with the Python token module
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python token: Parser Constants

    Learn Python token constants for lexical types, exact operators, indentation, f-strings, t-strings, and version-aware parsers.

    Ler mais

    Tempo de leitura: 7 minutos
    07/08/2026
    Source code representing reserved words and soft keywords in Python
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python keyword: Reserved Words

    Learn Python keyword to validate identifiers, reserved words, and soft keywords for the target interpreter version.

    Ler mais

    Tempo de leitura: 5 minutos
    07/08/2026
    Software architecture representing abstract base classes with Python abc
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python abc: Abstract Base Classes

    Learn Python abc to create abstract classes, required methods, virtual subclasses, and stable runtime contracts.

    Ler mais

    Tempo de leitura: 5 minutos
    06/08/2026