locale.nl_langinfo() returns the LC_TIME text items — month and day names, the date/time formats, AM/PM strings, alternative digits — decoded with the current LC_CTYPE encoding (PyUnicode_DecodeLocale). This has two drawbacks.
First, it only works if the LC_TIME encoding has a matching Python codec. If a locale's data is in an encoding Python cannot decode, the call fails or returns mojibake, even though the information is available from the C library.
Second, to make the decoding correct when LC_TIME and LC_CTYPE differ, the implementation temporarily switches the global LC_CTYPE locale (see the change_locale() path, added in gh-133740). That global change affects other threads and is the kind of thread-unsafe setlocale() use tracked in gh-127081.
glibc already stores every LC_TIME text item a second time in wide form, exposed through the _NL_W* constants (_NL_WMON_1, _NL_WDAY_1, _NL_WD_T_FMT, _NL_WALT_DIGITS, …). nl_langinfo() returns these as a wchar_t*, i.e. as Unicode code points independent of the locale byte encoding. Reading them needs neither a Python codec nor a temporary LC_CTYPE change.
Proposal: on glibc, map each narrow LC_TIME constant to its wide counterpart and decode the result with PyUnicode_FromWideChar(). The observable result is unchanged for locales that already round-trip today; it becomes correct (and thread-safe) for the cases above.
Scope / notes:
- glibc-only. The
_NL_W*symbols are glibc-internal (reserved-underscore enum values, no#define, absent in musl / *BSD / macOS), so they can't be feature-detected with#ifdefand are gated on__GLIBC__— matching the existing glibc-specific code in_localemodule.c. ERAis excluded: it has no bare wide counterpart (_NL_WERAdoes not exist, only sub-fields like_NL_WERA_D_FMT), so it keeps the narrow path.ALT_DIGITSis aNUL-separated multi-string item and is decoded with a wide analogue of the existingdecode_strings()helper.- This also unblocks using
nl_langinfo()from_strptimefor locale-aware parsing without the encoding fragility.