Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions babel/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,18 @@ def decimal_formats(self):
"""
return self._data['decimal_formats']

@property
def compact_decimal_formats(self):
"""Locale patterns for compact decimal number formatting.

.. note:: The format of the value returned may change between
Babel versions.

>>> Locale('en', 'US').compact_decimal_formats["short"]["one"]["1000"]
<NumberPattern u'0K'>
"""
return self._data['compact_decimal_formats']

@property
def currency_formats(self):
"""Locale patterns for currency number formatting.
Expand Down
57 changes: 57 additions & 0 deletions babel/numbers.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,63 @@ def format_decimal(
number, locale, decimal_quantization=decimal_quantization, group_separator=group_separator)


def format_compact_decimal(number, *, format_type="short", locale=LC_NUMERIC, fraction_digits=0):
u"""Return the given decimal number formatted for a specific locale in compact form.

>>> format_compact_decimal(12345, format_type="short", locale='en_US')
u'12K'
>>> format_compact_decimal(12345, format_type="long", locale='en_US')
u'12 thousand'
>>> format_compact_decimal(12345, format_type="short", locale='en_US', fraction_digits=2)
u'12.35K'
>>> format_compact_decimal(1234567, format_type="short", locale="ja_JP")
u'123万'
>>> format_compact_decimal(2345678, format_type="long", locale="mk")
u'2 милиони'
>>> format_compact_decimal(21098765, format_type="long", locale="mk")
u'21 милион'

:param number: the number to format
:param format_type: Compact format to use ("short" or "long")
:param locale: the `Locale` object or locale identifier
:param fraction_digits: Number of digits after the decimal point to use. Defaults to `0`.
"""
locale = Locale.parse(locale)
number, format = _get_compact_format(number, format_type, locale, fraction_digits)
pattern = parse_pattern(format)
return pattern.apply(number, locale, decimal_quantization=False)


def _get_compact_format(number, format_type, locale, fraction_digits=0):
"""Returns the number after dividing by the unit and the format pattern to use.
The algorithm is described here:
https://www.unicode.org/reports/tr35/tr35-45/tr35-numbers.html#Compact_Number_Formats.
"""
format = None
compact_format = locale.compact_decimal_formats[format_type]
for magnitude in sorted([int(m) for m in compact_format["other"]], reverse=True):
if abs(number) >= magnitude:
# check the pattern using "other" as the amount
format = compact_format["other"][str(magnitude)]
pattern = parse_pattern(format).pattern
# if the pattern is "0", we do not divide the number
if pattern == "0":
break
# otherwise, we need to divide the number by the magnitude but remove zeros
# equal to the number of 0's in the pattern minus 1
number = number / (magnitude / (10 ** (pattern.count("0") - 1)))
# round to the number of fraction digits requested
number = round(number, fraction_digits)
# if the remaining number is singular, use the singular format
plural_form = locale.plural_form(abs(number))
plural_form = plural_form if plural_form in compact_format else "other"
format = compact_format[plural_form][str(magnitude)]
break
if format is None: # Did not find a format, fall back.
format = locale.decimal_formats.get(None)
return number, format


class UnknownCurrencyFormatError(KeyError):
"""Exception raised when an unknown currency format is requested."""

Expand Down
2 changes: 2 additions & 0 deletions docs/api/numbers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ Number Formatting

.. autofunction:: format_decimal

.. autofunction:: format_compact_decimal

.. autofunction:: format_currency

.. autofunction:: format_percent
Expand Down
2 changes: 1 addition & 1 deletion docs/numbers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ the ``babel.numbers`` module:

.. code-block:: pycon

>>> from babel.numbers import format_number, format_decimal, format_percent
>>> from babel.numbers import format_number, format_decimal, format_compact_decimal, format_percent

Examples:

Expand Down
2 changes: 0 additions & 2 deletions scripts/import_cldr.py
Original file line number Diff line number Diff line change
Expand Up @@ -770,8 +770,6 @@ def parse_decimal_formats(data, tree):

# These are mapped into a `compact_decimal_formats` dictionary
# with the format {length: {count: {multiplier: pattern}}}.

# TODO: Add support for formatting them.
compact_decimal_formats = data.setdefault('compact_decimal_formats', {})
length_map = compact_decimal_formats.setdefault(length_type, {})
length_count_map = length_map.setdefault(pattern_el.attrib['count'], {})
Expand Down
33 changes: 33 additions & 0 deletions tests/test_numbers.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,39 @@ def test_group_separator(self):
assert numbers.format_currency(101299.98, 'EUR', locale='en_US', group_separator=True, format_type='name') == u'101,299.98 euros'
assert numbers.format_percent(251234.1234, locale='sv_SE', group_separator=True) == u'25\xa0123\xa0412\xa0%'

def test_compact(self):
assert numbers.format_compact_decimal(1, locale='en_US', format_type="short") == u'1'
assert numbers.format_compact_decimal(999, locale='en_US', format_type="short") == u'999'
assert numbers.format_compact_decimal(1000, locale='en_US', format_type="short") == u'1K'
assert numbers.format_compact_decimal(9000, locale='en_US', format_type="short") == u'9K'
assert numbers.format_compact_decimal(9123, locale='en_US', format_type="short", fraction_digits=2) == u'9.12K'
assert numbers.format_compact_decimal(10000, locale='en_US', format_type="short") == u'10K'
assert numbers.format_compact_decimal(10000, locale='en_US', format_type="short", fraction_digits=2) == u'10K'
assert numbers.format_compact_decimal(1000000, locale='en_US', format_type="short") == u'1M'
assert numbers.format_compact_decimal(9000999, locale='en_US', format_type="short") == u'9M'
assert numbers.format_compact_decimal(9000900099, locale='en_US', format_type="short", fraction_digits=5) == u'9.0009B'
assert numbers.format_compact_decimal(1, locale='en_US', format_type="long") == u'1'
assert numbers.format_compact_decimal(999, locale='en_US', format_type="long") == u'999'
assert numbers.format_compact_decimal(1000, locale='en_US', format_type="long") == u'1 thousand'
assert numbers.format_compact_decimal(9000, locale='en_US', format_type="long") == u'9 thousand'
assert numbers.format_compact_decimal(9000, locale='en_US', format_type="long", fraction_digits=2) == u'9 thousand'
assert numbers.format_compact_decimal(10000, locale='en_US', format_type="long") == u'10 thousand'
assert numbers.format_compact_decimal(10000, locale='en_US', format_type="long", fraction_digits=2) == u'10 thousand'
assert numbers.format_compact_decimal(1000000, locale='en_US', format_type="long") == u'1 million'
assert numbers.format_compact_decimal(9999999, locale='en_US', format_type="long") == u'10 million'
assert numbers.format_compact_decimal(9999999999, locale='en_US', format_type="long", fraction_digits=5) == u'10 billion'
assert numbers.format_compact_decimal(1, locale='ja_JP', format_type="short") == u'1'
assert numbers.format_compact_decimal(999, locale='ja_JP', format_type="short") == u'999'
assert numbers.format_compact_decimal(1000, locale='ja_JP', format_type="short") == u'1000'
assert numbers.format_compact_decimal(9123, locale='ja_JP', format_type="short") == u'9123'
assert numbers.format_compact_decimal(10000, locale='ja_JP', format_type="short") == u'1万'
assert numbers.format_compact_decimal(1234567, locale='ja_JP', format_type="long") == u'123万'
assert numbers.format_compact_decimal(-1, locale='en_US', format_type="short") == u'-1'
assert numbers.format_compact_decimal(-1234, locale='en_US', format_type="short", fraction_digits=2) == u'-1.23K'
assert numbers.format_compact_decimal(-123456789, format_type='short', locale='en_US') == u'-123M'
assert numbers.format_compact_decimal(-123456789, format_type='long', locale='en_US') == u'-123 million'
assert numbers.format_compact_decimal(2345678, locale='mk', format_type='long') == u'2 милиони'
assert numbers.format_compact_decimal(21098765, locale='mk', format_type='long') == u'21 милион'

class NumberParsingTestCase(unittest.TestCase):

Expand Down