Skip to content

Commit 5640880

Browse files
pythongh-124969: Fix locale.nl_langinfo(locale.ALT_DIGITS)
1 parent 5e9e506 commit 5640880

File tree

3 files changed

+64
-4
lines changed

3 files changed

+64
-4
lines changed

Doc/library/locale.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,8 @@ The :mod:`locale` module defines the following exception and functions:
158158

159159
.. function:: nl_langinfo(option)
160160

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

312313
.. data:: ALT_DIGITS
313314

314-
Get a representation of up to 100 values used to represent the values
315-
0 to 99.
315+
Get a tuple of up to 100 strings used to represent the values 0 to 99.
316316

317317

318318
.. function:: getdefaultlocale([envvars])

Lib/test/test__locale.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from _locale import (setlocale, LC_ALL, LC_CTYPE, LC_NUMERIC, localeconv, Error)
1+
from _locale import (setlocale, LC_ALL, LC_CTYPE, LC_NUMERIC, LC_TIME, localeconv, Error)
22
try:
33
from _locale import (RADIXCHAR, THOUSEP, nl_langinfo)
44
except ImportError:
@@ -74,6 +74,17 @@ def accept(loc):
7474
'ps_AF': ('\u066b', '\u066c'),
7575
}
7676

77+
known_alt_digits = {
78+
'C': (0, {}),
79+
'en_US': (0, {}),
80+
'fa_IR': (100, {0: '\u06f0\u06f0', 10: '\u06f1\u06f0', 99: '\u06f9\u06f9'}),
81+
'ja_JP': (100, {0: '\u3007', 10: '\u5341', 99: '\u4e5d\u5341\u4e5d'}),
82+
'lzh_TW': (32, {0: '\u3007', 10: '\u5341', 31: '\u5345\u4e00'}),
83+
'my_MM': (100, {0: '\u1040\u1040', 10: '\u1041\u1040', 99: '\u1049\u1049'}),
84+
'or_IN': (100, {0: '\u0b66', 10: '\u0b67\u0b66', 99: '\u0b6f\u0b6f'}),
85+
'shn_MM': (100, {0: '\u1090\u1090', 10: '\u1091\u1090', 99: '\u1099\u1099'}),
86+
}
87+
7788
if sys.platform == 'win32':
7889
# ps_AF doesn't work on Windows: see bpo-38324 (msg361830)
7990
del known_numerics['ps_AF']
@@ -176,6 +187,31 @@ def test_lc_numeric_basic(self):
176187
if not tested:
177188
self.skipTest('no suitable locales')
178189

190+
@unittest.skipUnless(nl_langinfo, "nl_langinfo is not available")
191+
@unittest.skipUnless(hasattr(locale, 'ALT_DIGITS'), "requires locale.ALT_DIGITS")
192+
@unittest.skipIf(
193+
support.is_emscripten or support.is_wasi,
194+
"musl libc issue on Emscripten, bpo-46390"
195+
)
196+
def test_alt_digits_nl_langinfo(self):
197+
# Test nl_langinfo(ALT_DIGITS)
198+
tested = False
199+
for loc, (count, samples) in known_alt_digits.items():
200+
try:
201+
setlocale(LC_TIME, loc)
202+
setlocale(LC_CTYPE, loc)
203+
except Error:
204+
continue
205+
with self.subTest(locale=loc):
206+
alt_digits = nl_langinfo(locale.ALT_DIGITS)
207+
self.assertIsInstance(alt_digits, tuple)
208+
self.assertEqual(len(alt_digits), count)
209+
for i in samples:
210+
self.assertEqual(alt_digits[i], samples[i])
211+
tested = True
212+
if not tested:
213+
self.skipTest('no suitable locales')
214+
179215
def test_float_parsing(self):
180216
# Bug #1391872: Test whether float parsing is okay on European
181217
# locales.

Modules/_localemodule.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,30 @@ _locale_nl_langinfo_impl(PyObject *module, int item)
608608
instead of an empty string for nl_langinfo(ERA). */
609609
const char *result = nl_langinfo(item);
610610
result = result != NULL ? result : "";
611+
#ifdef ALT_DIGITS
612+
if (item == ALT_DIGITS) {
613+
/* The result is a sequence of up to 100 NUL-separated strings. */
614+
const char *s = result;
615+
int count = 0;
616+
for (; count < 100 && *s; count++) {
617+
s += strlen(s) + 1;
618+
}
619+
PyObject *tuple = PyTuple_New(count);
620+
if (tuple == NULL) {
621+
return NULL;
622+
}
623+
for (int i = 0; i < count; i++) {
624+
PyObject *unicode = PyUnicode_DecodeLocale(result, NULL);
625+
if (unicode == NULL) {
626+
Py_DECREF(tuple);
627+
return NULL;
628+
}
629+
PyTuple_SET_ITEM(tuple, i, unicode);
630+
result += strlen(result) + 1;
631+
}
632+
return tuple;
633+
}
634+
#endif
611635
return PyUnicode_DecodeLocale(result, NULL);
612636
}
613637
PyErr_SetString(PyExc_ValueError, "unsupported langinfo constant");

0 commit comments

Comments
 (0)