From e0621871ab8aa72d23491bf67de07a7b688fd769 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 4 Oct 2024 17:45:58 +0300 Subject: [PATCH 1/3] gh-124969: 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. --- Doc/library/locale.rst | 6 +-- Lib/test/test__locale.py | 37 ++++++++++++++++++- ...-10-08-12-09-09.gh-issue-124969._VBQLq.rst | 3 ++ Modules/_localemodule.c | 29 ++++++++++++++- 4 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-10-08-12-09-09.gh-issue-124969._VBQLq.rst diff --git a/Doc/library/locale.rst b/Doc/library/locale.rst index 04035b33d0ed48..5f3c4840b5cc70 100644 --- a/Doc/library/locale.rst +++ b/Doc/library/locale.rst @@ -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. @@ -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. The function temporarily sets the ``LC_CTYPE`` locale to the locale of the category that determines the requested value (``LC_TIME``, diff --git a/Lib/test/test__locale.py b/Lib/test/test__locale.py index ba2d31f9c1ee9d..81e59e5117a458 100644 --- a/Lib/test/test__locale.py +++ b/Lib/test/test__locale.py @@ -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: @@ -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'] @@ -179,6 +190,30 @@ 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(): + try: + setlocale(LC_TIME, loc) + except Error: + continue + with self.subTest(locale=loc): + alt_digits = nl_langinfo(locale.ALT_DIGITS) + self.assertIsInstance(alt_digits, tuple) + 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. diff --git a/Misc/NEWS.d/next/Library/2024-10-08-12-09-09.gh-issue-124969._VBQLq.rst b/Misc/NEWS.d/next/Library/2024-10-08-12-09-09.gh-issue-124969._VBQLq.rst new file mode 100644 index 00000000000000..b5082b90721d42 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-08-12-09-09.gh-issue-124969._VBQLq.rst @@ -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. diff --git a/Modules/_localemodule.c b/Modules/_localemodule.c index ce77c4072a6aea..b888b218129c6c 100644 --- a/Modules/_localemodule.c +++ b/Modules/_localemodule.c @@ -666,9 +666,34 @@ _locale_nl_langinfo_impl(PyObject *module, int item) { return NULL; } - PyObject *unicode = 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; + } + } + } +#endif + else { + pyresult = PyUnicode_DecodeLocale(result, NULL); + } restore_locale(oldloc); - return unicode; + return pyresult; } } PyErr_SetString(PyExc_ValueError, "unsupported langinfo constant"); From 157c23efffaad089ea5b6384449ba4f9c142f472 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 8 Oct 2024 13:12:32 +0300 Subject: [PATCH 2/3] Fix preprocessor error. --- Modules/_localemodule.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/_localemodule.c b/Modules/_localemodule.c index b888b218129c6c..0daec646605775 100644 --- a/Modules/_localemodule.c +++ b/Modules/_localemodule.c @@ -688,8 +688,9 @@ _locale_nl_langinfo_impl(PyObject *module, int item) } } } + else #endif - else { + { pyresult = PyUnicode_DecodeLocale(result, NULL); } restore_locale(oldloc); From b6c9113fe34ad21dfa82964e7067144640f74dd5 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 9 Oct 2024 09:35:03 +0300 Subject: [PATCH 3/3] Skip tests on macOS. --- Lib/test/test__locale.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/Lib/test/test__locale.py b/Lib/test/test__locale.py index 81e59e5117a458..02d2acc6d1c417 100644 --- a/Lib/test/test__locale.py +++ b/Lib/test/test__locale.py @@ -200,17 +200,21 @@ def test_alt_digits_nl_langinfo(self): # Test nl_langinfo(ALT_DIGITS) tested = False for loc, (count, samples) in known_alt_digits.items(): - try: - setlocale(LC_TIME, loc) - except Error: - continue with self.subTest(locale=loc): - alt_digits = nl_langinfo(locale.ALT_DIGITS) - self.assertIsInstance(alt_digits, tuple) - self.assertEqual(len(alt_digits), count) - for i in samples: - self.assertEqual(alt_digits[i], samples[i]) - tested = True + 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 sys.platform == 'darwin': + self.skipTest(f'ALT_DIGITS is not set for locale {loc!r} on macOS') + 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')