Skip to content

Commit

Permalink
pythongh-124969: Make locale.nl_langinfo(locale.ALT_DIGITS) returning…
Browse files Browse the repository at this point in the history
… a string again

This is a follow up of pythonGH-124974. Only Glibc needed a fix.
Now the returned value is a string consisting of semicolon-separated
symbols on all Posix platforms.
  • Loading branch information
serhiy-storchaka committed Oct 21, 2024
1 parent ed24702 commit 97ed17c
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 27 deletions.
7 changes: 4 additions & 3 deletions Doc/library/locale.rst
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,7 @@ The :mod:`locale` module defines the following exception and functions:

.. function:: nl_langinfo(option)

Return some locale-specific information as a string (or a tuple for
``ALT_DIGITS``). This function is not
Return some locale-specific information as a string. 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 @@ -312,7 +311,9 @@ The :mod:`locale` module defines the following exception and functions:

.. data:: ALT_DIGITS

Get a tuple of up to 100 strings used to represent the values 0 to 99.
Get a string consisting of up to 100 semicolon-separated symbols used
to represent the values 0 to 99 in a locale-specific way.
In most locales this is an empty string.

The function temporarily sets the ``LC_CTYPE`` locale to the locale
of the category that determines the requested value (``LC_TIME``,
Expand Down
27 changes: 18 additions & 9 deletions Lib/test/test__locale.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@
'bs_BA', 'fr_LU', 'kl_GL', 'fa_IR', 'de_BE', 'sv_SE', 'it_CH', 'uk_UA',
'eu_ES', 'vi_VN', 'af_ZA', 'nb_NO', 'en_DK', 'tg_TJ', 'ps_AF', 'en_US',
'fr_FR.ISO8859-1', 'fr_FR.UTF-8', 'fr_FR.ISO8859-15@euro',
'ru_RU.KOI8-R', 'ko_KR.eucKR']
'ru_RU.KOI8-R', 'ko_KR.eucKR',
'ja_JP', 'lzh_TW', 'my_MM', 'or_IN', 'shn_MM',
'ar_AE', 'bn_IN', 'ks_IN', 'mr_IN', 'th_TH',
]

def setUpModule():
global candidate_locales
Expand Down Expand Up @@ -78,7 +81,7 @@ def accept(loc):
'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'}),
'ja_JP': (100, {1: '\u4e00', 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'}),
Expand Down Expand Up @@ -199,21 +202,27 @@ def test_lc_numeric_basic(self):
def test_alt_digits_nl_langinfo(self):
# Test nl_langinfo(ALT_DIGITS)
tested = False
for loc, (count, samples) in known_alt_digits.items():
for loc in candidate_locales:
with self.subTest(locale=loc):
try:
setlocale(LC_TIME, 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])
self.assertIsInstance(alt_digits, str)
alt_digits = alt_digits.split(';') if alt_digits else []
if alt_digits:
self.assertGreaterEqual(len(alt_digits), 10, alt_digits)
if loc in known_alt_digits:
count, samples = known_alt_digits[loc]
if count and not alt_digits:
self.skipTest(f'ALT_DIGITS is not set for locale {loc!r} on this platform')
self.assertEqual(len(alt_digits), count, alt_digits)
for i in samples:
self.assertEqual(alt_digits[i], samples[i])
tested = True
if not tested:
self.skipTest('no suitable locales')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
``locale.nl_langinfo(locale.ALT_DIGITS)`` now returns a string again. The
returned value consists of up to 100 semicolon-separated symbols.
38 changes: 23 additions & 15 deletions Modules/_localemodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -667,28 +667,36 @@ _locale_nl_langinfo_impl(PyObject *module, int item)
return NULL;
}
PyObject *pyresult;
#ifdef __GLIBC__
#ifdef ALT_DIGITS
if (item == ALT_DIGITS) {
/* The result is a sequence of up to 100 NUL-separated strings. */
const char *s = result;
if (item == ALT_DIGITS && *result) {
/* According to the POSIX specification the result must be
* a sequence of up to 100 semicolon-separated strings.
* But in Glibc they are NUL-separated. */
Py_ssize_t i = 0;
int count = 0;
for (; count < 100 && *s; count++) {
s += strlen(s) + 1;
for (; count < 100 && result[i]; count++) {
i += strlen(result + i) + 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;
char *buf = PyMem_Malloc(i);
if (buf == NULL) {
PyErr_NoMemory();
pyresult = NULL;
}
else {
memcpy(buf, result, i);
/* Replace all NULs with semicolons. */
i = 0;
while (--count) {
i += strlen(buf + i);
buf[i++] = ';';
}
pyresult = PyUnicode_DecodeLocale(buf, NULL);
PyMem_Free(buf);
}
}
else
#endif
#endif
{
pyresult = PyUnicode_DecodeLocale(result, NULL);
Expand Down

0 comments on commit 97ed17c

Please sign in to comment.