diff --git a/django/utils/numberformat.py b/django/utils/numberformat.py index 6667d823a5..ae5a3b5474 100644 --- a/django/utils/numberformat.py +++ b/django/utils/numberformat.py @@ -15,12 +15,15 @@ def format(number, decimal_sep, decimal_pos=None, grouping=0, thousand_sep='', * decimal_sep: Decimal separator symbol (for example ".") * decimal_pos: Number of decimal positions - * grouping: Number of digits in every group limited by thousand separator + * grouping: Number of digits in every group limited by thousand separator. + For non-uniform digit grouping, it can be a sequence with the number + of digit group sizes following the format used by the Python locale + module in locale.localeconv() LC_NUMERIC grouping (e.g. (3, 2, 0)). * thousand_sep: Thousand separator symbol (for example ",") """ use_grouping = settings.USE_L10N and settings.USE_THOUSAND_SEPARATOR use_grouping = use_grouping or force_grouping - use_grouping = use_grouping and grouping > 0 + use_grouping = use_grouping and grouping != 0 # Make the common case fast if isinstance(number, int) and not use_grouping and not decimal_pos: return mark_safe(six.text_type(number)) @@ -46,10 +49,22 @@ def format(number, decimal_sep, decimal_pos=None, grouping=0, thousand_sep='', dec_part = decimal_sep + dec_part # grouping if use_grouping: + try: + # if grouping is a sequence + intervals = list(grouping) + except TypeError: + # grouping is a single value + intervals = [grouping, 0] + active_interval = intervals.pop(0) int_part_gd = '' - for cnt, digit in enumerate(int_part[::-1]): - if cnt and not cnt % grouping: + cnt = 0 + for digit in int_part[::-1]: + if cnt and cnt == active_interval: + if intervals: + active_interval = intervals.pop(0) or active_interval int_part_gd += thousand_sep[::-1] + cnt = 0 int_part_gd += digit + cnt += 1 int_part = int_part_gd[::-1] return sign + int_part + dec_part diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 7b9c8e89fb..ab4f9c6efe 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -1943,12 +1943,28 @@ no grouping will be applied to the number. If this setting is greater than ``0``, then :setting:`THOUSAND_SEPARATOR` will be used as the separator between those groups. +Some locales use non-uniform digit grouping, e.g. ``10,00,00,000`` in +``en_IN``. For this case, you can provide a sequence with the number of digit +group sizes to be applied. The first number defines the size of the group +preceding the decimal delimiter, and each number that follows defines the size +of preceding groups. If the sequence is terminated with ``-1``, no further +grouping is performed. If the sequence terminates with a ``0``, the last group +size is used for the remainder of the number. + +Example tuple for ``en_IN``:: + + NUMBER_GROUPING = (3, 2, 0) + Note that if :setting:`USE_L10N` is set to ``True``, then the locale-dictated format has higher precedence and will be applied instead. See also :setting:`DECIMAL_SEPARATOR`, :setting:`THOUSAND_SEPARATOR` and :setting:`USE_THOUSAND_SEPARATOR`. +.. versionchanged:: 1.11 + + Support for non-uniform digit grouping was added. + .. setting:: PREPEND_WWW ``PREPEND_WWW`` diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt index e776b44da2..9106614d9c 100644 --- a/docs/releases/1.11.txt +++ b/docs/releases/1.11.txt @@ -166,7 +166,8 @@ Generic Views Internationalization ~~~~~~~~~~~~~~~~~~~~ -* ... +* Number formatting and the :setting:`NUMBER_GROUPING` setting support + non-uniform digit grouping. Management Commands ~~~~~~~~~~~~~~~~~~~ diff --git a/tests/i18n/tests.py b/tests/i18n/tests.py index e71082633c..0e4a002007 100644 --- a/tests/i18n/tests.py +++ b/tests/i18n/tests.py @@ -576,6 +576,18 @@ class FormattingTests(SimpleTestCase): self.assertEqual('-66666.6', nformat(-66666.666, decimal_sep='.', decimal_pos=1)) self.assertEqual('-66666.0', nformat(int('-66666'), decimal_sep='.', decimal_pos=1)) self.assertEqual('10000.0', nformat(self.l, decimal_sep='.', decimal_pos=1)) + self.assertEqual( + '10,00,00,000.00', + nformat(100000000.00, decimal_sep='.', decimal_pos=2, grouping=(3, 2, 0), thousand_sep=',') + ) + self.assertEqual( + '1,0,00,000,0000.00', + nformat(10000000000.00, decimal_sep='.', decimal_pos=2, grouping=(4, 3, 2, 1, 0), thousand_sep=',') + ) + self.assertEqual( + '10000,00,000.00', + nformat(1000000000.00, decimal_sep='.', decimal_pos=2, grouping=(3, 2, -1), thousand_sep=',') + ) # This unusual grouping/force_grouping combination may be triggered by the intcomma filter (#17414) self.assertEqual('10000', nformat(self.l, decimal_sep='.', decimal_pos=0, grouping=0, force_grouping=True))