Skip to content

[3.13] gh-124969: Fix locale.nl_langinfo(locale.ALT_DIGITS) (GH-124974) #125232

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Doc/library/locale.rst
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,8 @@ The :mod:`locale` module defines the following exception and functions:

.. function:: nl_langinfo(option)

Return some locale-specific information as a string. This function is not
Return some locale-specific information as a string (or a tuple for
``ALT_DIGITS``). This function is not
available on all systems, and the set of possible options might also vary
across platforms. The possible argument values are numbers, for which
symbolic constants are available in the locale module.
Expand Down Expand Up @@ -311,8 +312,7 @@ The :mod:`locale` module defines the following exception and functions:

.. data:: ALT_DIGITS

Get a representation of up to 100 values used to represent the values
0 to 99.
Get a tuple of up to 100 strings used to represent the values 0 to 99.


.. function:: getdefaultlocale([envvars])
Expand Down
42 changes: 41 additions & 1 deletion Lib/test/test__locale.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from _locale import (setlocale, LC_ALL, LC_CTYPE, LC_NUMERIC, localeconv, Error)
from _locale import (setlocale, LC_ALL, LC_CTYPE, LC_NUMERIC, LC_TIME, localeconv, Error)
try:
from _locale import (RADIXCHAR, THOUSEP, nl_langinfo)
except ImportError:
Expand Down Expand Up @@ -74,6 +74,17 @@ def accept(loc):
'ps_AF': ('\u066b', '\u066c'),
}

known_alt_digits = {
'C': (0, {}),
'en_US': (0, {}),
'fa_IR': (100, {0: '\u06f0\u06f0', 10: '\u06f1\u06f0', 99: '\u06f9\u06f9'}),
'ja_JP': (100, {0: '\u3007', 10: '\u5341', 99: '\u4e5d\u5341\u4e5d'}),
'lzh_TW': (32, {0: '\u3007', 10: '\u5341', 31: '\u5345\u4e00'}),
'my_MM': (100, {0: '\u1040\u1040', 10: '\u1041\u1040', 99: '\u1049\u1049'}),
'or_IN': (100, {0: '\u0b66', 10: '\u0b67\u0b66', 99: '\u0b6f\u0b6f'}),
'shn_MM': (100, {0: '\u1090\u1090', 10: '\u1091\u1090', 99: '\u1099\u1099'}),
}

if sys.platform == 'win32':
# ps_AF doesn't work on Windows: see bpo-38324 (msg361830)
del known_numerics['ps_AF']
Expand Down Expand Up @@ -176,6 +187,35 @@ def test_lc_numeric_basic(self):
if not tested:
self.skipTest('no suitable locales')

@unittest.skipUnless(nl_langinfo, "nl_langinfo is not available")
@unittest.skipUnless(hasattr(locale, 'ALT_DIGITS'), "requires locale.ALT_DIGITS")
@unittest.skipIf(
support.is_emscripten or support.is_wasi,
"musl libc issue on Emscripten, bpo-46390"
)
def test_alt_digits_nl_langinfo(self):
# Test nl_langinfo(ALT_DIGITS)
tested = False
for loc, (count, samples) in known_alt_digits.items():
with self.subTest(locale=loc):
try:
setlocale(LC_TIME, loc)
setlocale(LC_CTYPE, loc)
except Error:
self.skipTest(f'no locale {loc!r}')
continue
with self.subTest(locale=loc):
alt_digits = nl_langinfo(locale.ALT_DIGITS)
self.assertIsInstance(alt_digits, tuple)
if count and not alt_digits and support.is_apple:
self.skipTest(f'ALT_DIGITS is not set for locale {loc!r} on Apple platforms')
self.assertEqual(len(alt_digits), count)
for i in samples:
self.assertEqual(alt_digits[i], samples[i])
tested = True
if not tested:
self.skipTest('no suitable locales')

def test_float_parsing(self):
# Bug #1391872: Test whether float parsing is okay on European
# locales.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix ``locale.nl_langinfo(locale.ALT_DIGITS)``. Now it returns a tuple of up
to 100 strings (an empty tuple on most locales). Previously it returned the
first item of that tuple or an empty string.
29 changes: 28 additions & 1 deletion Modules/_localemodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,34 @@ _locale_nl_langinfo_impl(PyObject *module, int item)
instead of an empty string for nl_langinfo(ERA). */
const char *result = nl_langinfo(item);
result = result != NULL ? result : "";
return PyUnicode_DecodeLocale(result, NULL);
PyObject *pyresult;
#ifdef ALT_DIGITS
if (item == ALT_DIGITS) {
/* The result is a sequence of up to 100 NUL-separated strings. */
const char *s = result;
int count = 0;
for (; count < 100 && *s; count++) {
s += strlen(s) + 1;
}
pyresult = PyTuple_New(count);
if (pyresult != NULL) {
for (int i = 0; i < count; i++) {
PyObject *unicode = PyUnicode_DecodeLocale(result, NULL);
if (unicode == NULL) {
Py_CLEAR(pyresult);
break;
}
PyTuple_SET_ITEM(pyresult, i, unicode);
result += strlen(result) + 1;
}
}
}
else
#endif
{
pyresult = PyUnicode_DecodeLocale(result, NULL);
}
return pyresult;
}
PyErr_SetString(PyExc_ValueError, "unsupported langinfo constant");
return NULL;
Expand Down
Loading