]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.12] [3.13] gh-124969: Fix locale.nl_langinfo(locale.ALT_DIGITS) (GH-124974) (GH...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Fri, 11 Oct 2024 06:37:55 +0000 (08:37 +0200)
committerGitHub <noreply@github.com>
Fri, 11 Oct 2024 06:37:55 +0000 (09:37 +0300)
(cherry picked from commit 26a93189e4c3674a9e0acbd7923b1f27ff01419e)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Returns a tuple of up to 100 strings for ALT_DIGITS lookup (an empty tuple on most locales).
Previously it returned the first item of that tuple or an empty string.
(cherry picked from commit 21c04e1a972bd1b6285e0ea41fa107d635bbe43a)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Doc/library/locale.rst
Lib/test/test__locale.py
Misc/NEWS.d/next/Library/2024-10-08-12-09-09.gh-issue-124969._VBQLq.rst [new file with mode: 0644]
Modules/_localemodule.c

index 60975bf9177dadf649445e7532383d94606544d2..f0553d51fedf146c203eeedbf91847e64c17874e 100644 (file)
@@ -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.
 
 
 .. function:: getdefaultlocale([envvars])
index 0947464bb8c04e49ade3f868453cadf32b1e7367..5041def72161974700bd77f53237d2f7c4d78dc1 100644 (file)
@@ -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 @@ known_numerics = {
     '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']
@@ -176,6 +187,35 @@ class _LocaleTests(unittest.TestCase):
         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.
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 (file)
index 0000000..b5082b9
--- /dev/null
@@ -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.
index f080b97034c0c06d339feb5e04dbdee5edff4367..3dea764fdaad27be9ccf995119211bcc3799f3a6 100644 (file)
@@ -618,7 +618,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;