Skip to content

Commit

Permalink
Fixed #25920 -- Added support for non-uniform NUMBER_GROUPING.
Browse files Browse the repository at this point in the history
  • Loading branch information
jasisz authored and timgraham committed Jun 22, 2016
1 parent 4633829 commit b5a1c3a
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 5 deletions.
23 changes: 19 additions & 4 deletions django/utils/numberformat.py
Expand Up @@ -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))
Expand All @@ -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
16 changes: 16 additions & 0 deletions docs/ref/settings.txt
Expand Up @@ -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``
Expand Down
3 changes: 2 additions & 1 deletion docs/releases/1.11.txt
Expand Up @@ -166,7 +166,8 @@ Generic Views
Internationalization
~~~~~~~~~~~~~~~~~~~~

* ...
* Number formatting and the :setting:`NUMBER_GROUPING` setting support
non-uniform digit grouping.

Management Commands
~~~~~~~~~~~~~~~~~~~
Expand Down
12 changes: 12 additions & 0 deletions tests/i18n/tests.py
Expand Up @@ -576,6 +576,18 @@ def test_locale_independent(self):
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))

Expand Down

0 comments on commit b5a1c3a

Please sign in to comment.