Skip to content

Commit

Permalink
Added group_separator feature in number formatting (#726)
Browse files Browse the repository at this point in the history
  • Loading branch information
Abdullahjavednesar committed Sep 22, 2020
1 parent e7e4265 commit e0e6aa6
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 17 deletions.
58 changes: 41 additions & 17 deletions babel/numbers.py
Expand Up @@ -373,7 +373,7 @@ def get_decimal_quantum(precision):


def format_decimal(
number, format=None, locale=LC_NUMERIC, decimal_quantization=True):
number, format=None, locale=LC_NUMERIC, decimal_quantization=True, group_separator=True):
u"""Return the given decimal number formatted for a specific locale.
>>> format_decimal(1.2345, locale='en_US')
Expand Down Expand Up @@ -401,19 +401,25 @@ def format_decimal(
u'1.235'
>>> format_decimal(1.2346, locale='en_US', decimal_quantization=False)
u'1.2346'
>>> format_decimal(12345.67, locale='fr_CA', group_separator=False)
u'12345,67'
>>> format_decimal(12345.67, locale='en_US', group_separator=True)
u'12,345.67'
:param number: the number to format
:param format:
:param locale: the `Locale` object or locale identifier
:param decimal_quantization: Truncate and round high-precision numbers to
the format pattern. Defaults to `True`.
:param group_separator: Boolean to switch group separator on/off in a locale's
number format.
"""
locale = Locale.parse(locale)
if not format:
format = locale.decimal_formats.get(format)
pattern = parse_pattern(format)
return pattern.apply(
number, locale, decimal_quantization=decimal_quantization)
number, locale, decimal_quantization=decimal_quantization, group_separator=group_separator)


class UnknownCurrencyFormatError(KeyError):
Expand All @@ -422,7 +428,7 @@ class UnknownCurrencyFormatError(KeyError):

def format_currency(
number, currency, format=None, locale=LC_NUMERIC, currency_digits=True,
format_type='standard', decimal_quantization=True):
format_type='standard', decimal_quantization=True, group_separator=True):
u"""Return formatted currency value.
>>> format_currency(1099.98, 'USD', locale='en_US')
Expand Down Expand Up @@ -472,6 +478,12 @@ def format_currency(
...
UnknownCurrencyFormatError: "'unknown' is not a known currency format type"
>>> format_currency(101299.98, 'USD', locale='en_US', group_separator=False)
u'$101299.98'
>>> format_currency(101299.98, 'USD', locale='en_US', group_separator=True)
u'$101,299.98'
You can also pass format_type='name' to use long display names. The order of
the number and currency name, along with the correct localized plural form
of the currency name, is chosen according to locale:
Expand Down Expand Up @@ -500,12 +512,14 @@ def format_currency(
:param format_type: the currency format type to use
:param decimal_quantization: Truncate and round high-precision numbers to
the format pattern. Defaults to `True`.
:param group_separator: Boolean to switch group separator on/off in a locale's
number format.
"""
if format_type == 'name':
return _format_currency_long_name(number, currency, format=format,
locale=locale, currency_digits=currency_digits,
decimal_quantization=decimal_quantization)
decimal_quantization=decimal_quantization, group_separator=group_separator)
locale = Locale.parse(locale)
if format:
pattern = parse_pattern(format)
Expand All @@ -518,12 +532,12 @@ def format_currency(

return pattern.apply(
number, locale, currency=currency, currency_digits=currency_digits,
decimal_quantization=decimal_quantization)
decimal_quantization=decimal_quantization, group_separator=group_separator)


def _format_currency_long_name(
number, currency, format=None, locale=LC_NUMERIC, currency_digits=True,
format_type='standard', decimal_quantization=True):
format_type='standard', decimal_quantization=True, group_separator=True):
# Algorithm described here:
# https://www.unicode.org/reports/tr35/tr35-numbers.html#Currencies
locale = Locale.parse(locale)
Expand Down Expand Up @@ -552,13 +566,13 @@ def _format_currency_long_name(

number_part = pattern.apply(
number, locale, currency=currency, currency_digits=currency_digits,
decimal_quantization=decimal_quantization)
decimal_quantization=decimal_quantization, group_separator=group_separator)

return unit_pattern.format(number_part, display_name)


def format_percent(
number, format=None, locale=LC_NUMERIC, decimal_quantization=True):
number, format=None, locale=LC_NUMERIC, decimal_quantization=True, group_separator=True):
"""Return formatted percent value for a specific locale.
>>> format_percent(0.34, locale='en_US')
Expand All @@ -582,18 +596,26 @@ def format_percent(
>>> format_percent(23.9876, locale='en_US', decimal_quantization=False)
u'2,398.76%'
>>> format_percent(229291.1234, locale='pt_BR', group_separator=False)
u'22929112%'
>>> format_percent(229291.1234, locale='pt_BR', group_separator=True)
u'22.929.112%'
:param number: the percent number to format
:param format:
:param locale: the `Locale` object or locale identifier
:param decimal_quantization: Truncate and round high-precision numbers to
the format pattern. Defaults to `True`.
:param group_separator: Boolean to switch group separator on/off in a locale's
number format.
"""
locale = Locale.parse(locale)
if not format:
format = locale.percent_formats.get(format)
pattern = parse_pattern(format)
return pattern.apply(
number, locale, decimal_quantization=decimal_quantization)
number, locale, decimal_quantization=decimal_quantization, group_separator=group_separator)


def format_scientific(
Expand Down Expand Up @@ -913,6 +935,7 @@ def apply(
currency_digits=True,
decimal_quantization=True,
force_frac=None,
group_separator=True,
):
"""Renders into a string a number following the defined pattern.
Expand Down Expand Up @@ -952,8 +975,8 @@ def apply(
if self.exp_prec:
value, exp, exp_sign = self.scientific_notation_elements(value, locale)

# Adjust the precision of the fractionnal part and force it to the
# currency's if neccessary.
# Adjust the precision of the fractional part and force it to the
# currency's if necessary.
if force_frac:
# TODO (3.x?): Remove this parameter
warnings.warn('The force_frac parameter to NumberPattern.apply() is deprecated.', DeprecationWarning)
Expand All @@ -975,7 +998,7 @@ def apply(
# Render scientific notation.
if self.exp_prec:
number = ''.join([
self._quantize_value(value, locale, frac_prec),
self._quantize_value(value, locale, frac_prec, group_separator),
get_exponential_symbol(locale),
exp_sign,
self._format_int(
Expand All @@ -993,7 +1016,7 @@ def apply(

# A normal number pattern.
else:
number = self._quantize_value(value, locale, frac_prec)
number = self._quantize_value(value, locale, frac_prec, group_separator)

retval = ''.join([
self.prefix[is_negative],
Expand Down Expand Up @@ -1060,13 +1083,14 @@ def _format_int(self, value, min, max, locale):
gsize = self.grouping[1]
return value + ret

def _quantize_value(self, value, locale, frac_prec):
def _quantize_value(self, value, locale, frac_prec, group_separator):
quantum = get_decimal_quantum(frac_prec[1])
rounded = value.quantize(quantum)
a, sep, b = "{:f}".format(rounded).partition(".")
number = (self._format_int(a, self.int_prec[0],
self.int_prec[1], locale) +
self._format_frac(b or '0', locale, frac_prec))
integer_part = a
if group_separator:
integer_part = self._format_int(a, self.int_prec[0], self.int_prec[1], locale)
number = integer_part + self._format_frac(b or '0', locale, frac_prec)
return number

def _format_frac(self, value, locale, force_frac=None):
Expand Down
30 changes: 30 additions & 0 deletions tests/test_numbers.py
Expand Up @@ -153,6 +153,36 @@ def test_formatting_of_very_small_decimals(self):
fmt = numbers.format_decimal(number, format="@@@", locale='en_US')
self.assertEqual('0.000000700', fmt)

def test_group_separator(self):
self.assertEqual('29567.12', numbers.format_decimal(29567.12,
locale='en_US', group_separator=False))
self.assertEqual('29567,12', numbers.format_decimal(29567.12,
locale='fr_CA', group_separator=False))
self.assertEqual('29567,12', numbers.format_decimal(29567.12,
locale='pt_BR', group_separator=False))
self.assertEqual(u'$1099.98', numbers.format_currency(1099.98, 'USD',
locale='en_US', group_separator=False))
self.assertEqual(u'101299,98\xa0€', numbers.format_currency(101299.98, 'EUR',
locale='fr_CA', group_separator=False))
self.assertEqual('101299.98 euros', numbers.format_currency(101299.98, 'EUR',
locale='en_US', group_separator=False, format_type='name'))
self.assertEqual(u'25123412\xa0%', numbers.format_percent(251234.1234, locale='sv_SE', group_separator=False))

self.assertEqual(u'29,567.12', numbers.format_decimal(29567.12,
locale='en_US', group_separator=True))
self.assertEqual(u'29\u202f567,12', numbers.format_decimal(29567.12,
locale='fr_CA', group_separator=True))
self.assertEqual(u'29.567,12', numbers.format_decimal(29567.12,
locale='pt_BR', group_separator=True))
self.assertEqual(u'$1,099.98', numbers.format_currency(1099.98, 'USD',
locale='en_US', group_separator=True))
self.assertEqual(u'101\u202f299,98\xa0\u20ac', numbers.format_currency(101299.98, 'EUR',
locale='fr_CA', group_separator=True))
self.assertEqual(u'101,299.98 euros', numbers.format_currency(101299.98, 'EUR',
locale='en_US', group_separator=True,
format_type='name'))
self.assertEqual(u'25\xa0123\xa0412\xa0%', numbers.format_percent(251234.1234, locale='sv_SE', group_separator=True))


class NumberParsingTestCase(unittest.TestCase):

Expand Down

0 comments on commit e0e6aa6

Please sign in to comment.