Skip to content

Commit e0e6aa6

Browse files
Added group_separator feature in number formatting (#726)
1 parent e7e4265 commit e0e6aa6

File tree

2 files changed

+71
-17
lines changed

2 files changed

+71
-17
lines changed

babel/numbers.py

+41-17
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ def get_decimal_quantum(precision):
373373

374374

375375
def format_decimal(
376-
number, format=None, locale=LC_NUMERIC, decimal_quantization=True):
376+
number, format=None, locale=LC_NUMERIC, decimal_quantization=True, group_separator=True):
377377
u"""Return the given decimal number formatted for a specific locale.
378378
379379
>>> format_decimal(1.2345, locale='en_US')
@@ -401,19 +401,25 @@ def format_decimal(
401401
u'1.235'
402402
>>> format_decimal(1.2346, locale='en_US', decimal_quantization=False)
403403
u'1.2346'
404+
>>> format_decimal(12345.67, locale='fr_CA', group_separator=False)
405+
u'12345,67'
406+
>>> format_decimal(12345.67, locale='en_US', group_separator=True)
407+
u'12,345.67'
404408
405409
:param number: the number to format
406410
:param format:
407411
:param locale: the `Locale` object or locale identifier
408412
:param decimal_quantization: Truncate and round high-precision numbers to
409413
the format pattern. Defaults to `True`.
414+
:param group_separator: Boolean to switch group separator on/off in a locale's
415+
number format.
410416
"""
411417
locale = Locale.parse(locale)
412418
if not format:
413419
format = locale.decimal_formats.get(format)
414420
pattern = parse_pattern(format)
415421
return pattern.apply(
416-
number, locale, decimal_quantization=decimal_quantization)
422+
number, locale, decimal_quantization=decimal_quantization, group_separator=group_separator)
417423

418424

419425
class UnknownCurrencyFormatError(KeyError):
@@ -422,7 +428,7 @@ class UnknownCurrencyFormatError(KeyError):
422428

423429
def format_currency(
424430
number, currency, format=None, locale=LC_NUMERIC, currency_digits=True,
425-
format_type='standard', decimal_quantization=True):
431+
format_type='standard', decimal_quantization=True, group_separator=True):
426432
u"""Return formatted currency value.
427433
428434
>>> format_currency(1099.98, 'USD', locale='en_US')
@@ -472,6 +478,12 @@ def format_currency(
472478
...
473479
UnknownCurrencyFormatError: "'unknown' is not a known currency format type"
474480
481+
>>> format_currency(101299.98, 'USD', locale='en_US', group_separator=False)
482+
u'$101299.98'
483+
484+
>>> format_currency(101299.98, 'USD', locale='en_US', group_separator=True)
485+
u'$101,299.98'
486+
475487
You can also pass format_type='name' to use long display names. The order of
476488
the number and currency name, along with the correct localized plural form
477489
of the currency name, is chosen according to locale:
@@ -500,12 +512,14 @@ def format_currency(
500512
:param format_type: the currency format type to use
501513
:param decimal_quantization: Truncate and round high-precision numbers to
502514
the format pattern. Defaults to `True`.
515+
:param group_separator: Boolean to switch group separator on/off in a locale's
516+
number format.
503517
504518
"""
505519
if format_type == 'name':
506520
return _format_currency_long_name(number, currency, format=format,
507521
locale=locale, currency_digits=currency_digits,
508-
decimal_quantization=decimal_quantization)
522+
decimal_quantization=decimal_quantization, group_separator=group_separator)
509523
locale = Locale.parse(locale)
510524
if format:
511525
pattern = parse_pattern(format)
@@ -518,12 +532,12 @@ def format_currency(
518532

519533
return pattern.apply(
520534
number, locale, currency=currency, currency_digits=currency_digits,
521-
decimal_quantization=decimal_quantization)
535+
decimal_quantization=decimal_quantization, group_separator=group_separator)
522536

523537

524538
def _format_currency_long_name(
525539
number, currency, format=None, locale=LC_NUMERIC, currency_digits=True,
526-
format_type='standard', decimal_quantization=True):
540+
format_type='standard', decimal_quantization=True, group_separator=True):
527541
# Algorithm described here:
528542
# https://www.unicode.org/reports/tr35/tr35-numbers.html#Currencies
529543
locale = Locale.parse(locale)
@@ -552,13 +566,13 @@ def _format_currency_long_name(
552566

553567
number_part = pattern.apply(
554568
number, locale, currency=currency, currency_digits=currency_digits,
555-
decimal_quantization=decimal_quantization)
569+
decimal_quantization=decimal_quantization, group_separator=group_separator)
556570

557571
return unit_pattern.format(number_part, display_name)
558572

559573

560574
def format_percent(
561-
number, format=None, locale=LC_NUMERIC, decimal_quantization=True):
575+
number, format=None, locale=LC_NUMERIC, decimal_quantization=True, group_separator=True):
562576
"""Return formatted percent value for a specific locale.
563577
564578
>>> format_percent(0.34, locale='en_US')
@@ -582,18 +596,26 @@ def format_percent(
582596
>>> format_percent(23.9876, locale='en_US', decimal_quantization=False)
583597
u'2,398.76%'
584598
599+
>>> format_percent(229291.1234, locale='pt_BR', group_separator=False)
600+
u'22929112%'
601+
602+
>>> format_percent(229291.1234, locale='pt_BR', group_separator=True)
603+
u'22.929.112%'
604+
585605
:param number: the percent number to format
586606
:param format:
587607
:param locale: the `Locale` object or locale identifier
588608
:param decimal_quantization: Truncate and round high-precision numbers to
589609
the format pattern. Defaults to `True`.
610+
:param group_separator: Boolean to switch group separator on/off in a locale's
611+
number format.
590612
"""
591613
locale = Locale.parse(locale)
592614
if not format:
593615
format = locale.percent_formats.get(format)
594616
pattern = parse_pattern(format)
595617
return pattern.apply(
596-
number, locale, decimal_quantization=decimal_quantization)
618+
number, locale, decimal_quantization=decimal_quantization, group_separator=group_separator)
597619

598620

599621
def format_scientific(
@@ -913,6 +935,7 @@ def apply(
913935
currency_digits=True,
914936
decimal_quantization=True,
915937
force_frac=None,
938+
group_separator=True,
916939
):
917940
"""Renders into a string a number following the defined pattern.
918941
@@ -952,8 +975,8 @@ def apply(
952975
if self.exp_prec:
953976
value, exp, exp_sign = self.scientific_notation_elements(value, locale)
954977

955-
# Adjust the precision of the fractionnal part and force it to the
956-
# currency's if neccessary.
978+
# Adjust the precision of the fractional part and force it to the
979+
# currency's if necessary.
957980
if force_frac:
958981
# TODO (3.x?): Remove this parameter
959982
warnings.warn('The force_frac parameter to NumberPattern.apply() is deprecated.', DeprecationWarning)
@@ -975,7 +998,7 @@ def apply(
975998
# Render scientific notation.
976999
if self.exp_prec:
9771000
number = ''.join([
978-
self._quantize_value(value, locale, frac_prec),
1001+
self._quantize_value(value, locale, frac_prec, group_separator),
9791002
get_exponential_symbol(locale),
9801003
exp_sign,
9811004
self._format_int(
@@ -993,7 +1016,7 @@ def apply(
9931016

9941017
# A normal number pattern.
9951018
else:
996-
number = self._quantize_value(value, locale, frac_prec)
1019+
number = self._quantize_value(value, locale, frac_prec, group_separator)
9971020

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

1063-
def _quantize_value(self, value, locale, frac_prec):
1086+
def _quantize_value(self, value, locale, frac_prec, group_separator):
10641087
quantum = get_decimal_quantum(frac_prec[1])
10651088
rounded = value.quantize(quantum)
10661089
a, sep, b = "{:f}".format(rounded).partition(".")
1067-
number = (self._format_int(a, self.int_prec[0],
1068-
self.int_prec[1], locale) +
1069-
self._format_frac(b or '0', locale, frac_prec))
1090+
integer_part = a
1091+
if group_separator:
1092+
integer_part = self._format_int(a, self.int_prec[0], self.int_prec[1], locale)
1093+
number = integer_part + self._format_frac(b or '0', locale, frac_prec)
10701094
return number
10711095

10721096
def _format_frac(self, value, locale, force_frac=None):

tests/test_numbers.py

+30
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,36 @@ def test_formatting_of_very_small_decimals(self):
153153
fmt = numbers.format_decimal(number, format="@@@", locale='en_US')
154154
self.assertEqual('0.000000700', fmt)
155155

156+
def test_group_separator(self):
157+
self.assertEqual('29567.12', numbers.format_decimal(29567.12,
158+
locale='en_US', group_separator=False))
159+
self.assertEqual('29567,12', numbers.format_decimal(29567.12,
160+
locale='fr_CA', group_separator=False))
161+
self.assertEqual('29567,12', numbers.format_decimal(29567.12,
162+
locale='pt_BR', group_separator=False))
163+
self.assertEqual(u'$1099.98', numbers.format_currency(1099.98, 'USD',
164+
locale='en_US', group_separator=False))
165+
self.assertEqual(u'101299,98\xa0€', numbers.format_currency(101299.98, 'EUR',
166+
locale='fr_CA', group_separator=False))
167+
self.assertEqual('101299.98 euros', numbers.format_currency(101299.98, 'EUR',
168+
locale='en_US', group_separator=False, format_type='name'))
169+
self.assertEqual(u'25123412\xa0%', numbers.format_percent(251234.1234, locale='sv_SE', group_separator=False))
170+
171+
self.assertEqual(u'29,567.12', numbers.format_decimal(29567.12,
172+
locale='en_US', group_separator=True))
173+
self.assertEqual(u'29\u202f567,12', numbers.format_decimal(29567.12,
174+
locale='fr_CA', group_separator=True))
175+
self.assertEqual(u'29.567,12', numbers.format_decimal(29567.12,
176+
locale='pt_BR', group_separator=True))
177+
self.assertEqual(u'$1,099.98', numbers.format_currency(1099.98, 'USD',
178+
locale='en_US', group_separator=True))
179+
self.assertEqual(u'101\u202f299,98\xa0\u20ac', numbers.format_currency(101299.98, 'EUR',
180+
locale='fr_CA', group_separator=True))
181+
self.assertEqual(u'101,299.98 euros', numbers.format_currency(101299.98, 'EUR',
182+
locale='en_US', group_separator=True,
183+
format_type='name'))
184+
self.assertEqual(u'25\xa0123\xa0412\xa0%', numbers.format_percent(251234.1234, locale='sv_SE', group_separator=True))
185+
156186

157187
class NumberParsingTestCase(unittest.TestCase):
158188

0 commit comments

Comments
 (0)