]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-124969: Fix locale.nl_langinfo(locale.ALT_DIGITS) (GH-124974)
authorSerhiy Storchaka <storchaka@gmail.com>
Wed, 9 Oct 2024 08:42:08 +0000 (11:42 +0300)
committerGitHub <noreply@github.com>
Wed, 9 Oct 2024 08:42:08 +0000 (11:42 +0300)
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
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 04035b33d0ed484786994b213e177abf227423d5..5f3c4840b5cc70102fefcb82a1d41579d3e6b225 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.
 
    The function temporarily sets the ``LC_CTYPE`` locale to the locale
    of the category that determines the requested value (``LC_TIME``,
index ba2d31f9c1ee9d1b95113cadac2a8abd865ae830..02d2acc6d1c417d2b6f5c575097c882414f96a73 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']
@@ -179,6 +190,34 @@ 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)
+                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')
+
     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 ce77c4072a6aead75d525e5ac82d393f7285fa8e..0daec646605775450d6b14ed4db994a863d1a981 100644 (file)
@@ -666,9 +666,35 @@ _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;
+                    }
+                }
+            }
+            else
+#endif
+            {
+                pyresult = PyUnicode_DecodeLocale(result, NULL);
+            }
             restore_locale(oldloc);
-            return unicode;
+            return pyresult;
         }
     }
     PyErr_SetString(PyExc_ValueError, "unsupported langinfo constant");