From 2b5219e5fbd785455cedfa91bae1a518365075c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Wed, 14 Aug 2019 13:21:44 +0200 Subject: [PATCH 1/2] bpo-34311: Add locale.localize This allow to format Decimal without losing precision by using the new format syntax and than localize the result. --- Doc/library/locale.rst | 8 ++++++ Lib/locale.py | 16 +++++++++-- Lib/test/test_locale.py | 28 +++++++++++++++++++ .../2019-08-14-13-19-50.bpo-33731.9esS0d.rst | 2 ++ 4 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2019-08-14-13-19-50.bpo-33731.9esS0d.rst diff --git a/Doc/library/locale.rst b/Doc/library/locale.rst index bf57a083559168..8e8a54f1a91c74 100644 --- a/Doc/library/locale.rst +++ b/Doc/library/locale.rst @@ -423,6 +423,14 @@ The :mod:`locale` module defines the following exception and functions: .. versionadded:: 3.5 +.. function:: localize(string, grouping=False, monetary=False) + + Converts a normalized number string into a formatted string following the + :const:`LC_NUMERIC` settings. + + .. versionadded:: 3.9 + + .. function:: atof(string) Converts a string to a floating point number, following the :const:`LC_NUMERIC` diff --git a/Lib/locale.py b/Lib/locale.py index 1a4e9f694f3096..3427b4aa2dbdcb 100644 --- a/Lib/locale.py +++ b/Lib/locale.py @@ -185,8 +185,14 @@ def _format(percent, value, grouping=False, monetary=False, *additional): formatted = percent % ((value,) + additional) else: formatted = percent % value + if percent[-1] in 'eEfFgGdiu': + formatted = _localize(formatted, grouping, monetary) + return formatted + +# Transform formatted as locale number according to the locale settings +def _localize(formatted, grouping=False, monetary=False): # floats and decimal ints need special action! - if percent[-1] in 'eEfFgG': + if '.' in formatted: seps = 0 parts = formatted.split('.') if grouping: @@ -196,7 +202,7 @@ def _format(percent, value, grouping=False, monetary=False, *additional): formatted = decimal_point.join(parts) if seps: formatted = _strip_padding(formatted, seps) - elif percent[-1] in 'diu': + else: seps = 0 if grouping: formatted, seps = _group(formatted, monetary=monetary) @@ -267,7 +273,7 @@ def currency(val, symbol=True, grouping=False, international=False): raise ValueError("Currency formatting is not possible using " "the 'C' locale.") - s = _format('%%.%if' % digits, abs(val), grouping, monetary=True) + s = _localize(f'{abs(val):.{digits}f}', grouping, monetary=True) # '<' and '>' are markers if the sign must be inserted between symbol and value s = '<' + s + '>' @@ -323,6 +329,10 @@ def delocalize(string): string = string.replace(dd, '.') return string +def localize(string, grouping=False, monetary=False): + """Parses a string as locale number according to the locale settings.""" + return _localize(string, grouping, monetary) + def atof(string, func=float): "Parses a string as a float according to the locale settings." return func(delocalize(string)) diff --git a/Lib/test/test_locale.py b/Lib/test/test_locale.py index 2863d200e25c2e..22c87cec4febf2 100644 --- a/Lib/test/test_locale.py +++ b/Lib/test/test_locale.py @@ -1,3 +1,4 @@ +from decimal import Decimal from test.support import verbose, is_android, check_warnings import unittest import locale @@ -629,5 +630,32 @@ def test_atoi(self): self._test_atoi('50 000', 50000) +class BaseLocalizeTest(BaseLocalizedTest): + + def _test_localize(self, value, out, grouping=False): + self.assertEqual(locale.localize(value, grouping=grouping), out) + + +class TestEnUSLocalize(EnUSCookedTest, BaseLocalizeTest): + + def test_localize(self): + self._test_localize('50000.00', '50000.00') + self._test_localize( + '{0:.16f}'.format(Decimal('1.15')), '1.1500000000000000') + + +class TestCLocalize(CCookedTest, BaseLocalizeTest): + + def test_localize(self): + self._test_localize('50000.00', '50000.00') + + +class TestfrFRLocalize(FrFRCookedTest, BaseLocalizeTest): + + def test_localize(self): + self._test_localize('50000.00', '50000,00') + self._test_localize('50000.00', '50 000,00', grouping=True) + + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Library/2019-08-14-13-19-50.bpo-33731.9esS0d.rst b/Misc/NEWS.d/next/Library/2019-08-14-13-19-50.bpo-33731.9esS0d.rst new file mode 100644 index 00000000000000..2fedb0696c3266 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-08-14-13-19-50.bpo-33731.9esS0d.rst @@ -0,0 +1,2 @@ +Provide a locale.localize() function, which converts a normalized number string +into a locale format. From 08558601314d5ec17f1cbfc66639034e55f07d0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Sat, 27 Mar 2021 12:12:04 +0100 Subject: [PATCH 2/2] Update versionadded --- Doc/library/locale.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/locale.rst b/Doc/library/locale.rst index 368ab60e7a4081..60d0c59d017c73 100644 --- a/Doc/library/locale.rst +++ b/Doc/library/locale.rst @@ -432,7 +432,7 @@ The :mod:`locale` module defines the following exception and functions: Converts a normalized number string into a formatted string following the :const:`LC_NUMERIC` settings. - .. versionadded:: 3.9 + .. versionadded:: 3.10 .. function:: atof(string)