Skip to content

Commit

Permalink
add a strict mode to parse_decimal()
Browse files Browse the repository at this point in the history
Fixes #589
  • Loading branch information
Changaco committed Jun 16, 2018
1 parent a3ef3c0 commit ff24ed9
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 3 deletions.
25 changes: 22 additions & 3 deletions babel/numbers.py
Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,7 @@ def parse_number(string, locale=LC_NUMERIC):
raise NumberFormatError('%r is not a valid number' % string)


def parse_decimal(string, locale=LC_NUMERIC):
def parse_decimal(string, locale=LC_NUMERIC, strict=False):
"""Parse localized decimal string into a decimal.
>>> parse_decimal('1,099.98', locale='en_US')
Expand All @@ -676,17 +676,36 @@ def parse_decimal(string, locale=LC_NUMERIC):
...
NumberFormatError: '2,109,998' is not a valid decimal number
If `strict` is set to `True` and the given string contains a number
formatted in an irregular way, an exception is raised:
>>> parse_decimal('30.00', locale='de', strict=True)
Traceback (most recent call last):
...
NumberFormatError: '30.00' is not a properly formatted decimal number
:param string: the string to parse
:param locale: the `Locale` object or locale identifier
:param strict: controls whether numbers formatted in a weird way are
accepted or rejected
:raise NumberFormatError: if the string can not be converted to a
decimal number
"""
locale = Locale.parse(locale)
group_symbol = get_group_symbol(locale)
decimal_symbol = get_decimal_symbol(locale)
try:
return decimal.Decimal(string.replace(get_group_symbol(locale), '')
.replace(get_decimal_symbol(locale), '.'))
r = decimal.Decimal(string.replace(group_symbol, '')
.replace(decimal_symbol, '.'))
except decimal.InvalidOperation:
raise NumberFormatError('%r is not a valid decimal number' % string)
if strict and group_symbol in string:
proper = format_decimal(r, locale=locale, decimal_quantization=False)
if string != proper and string.rstrip('0') != (proper + decimal_symbol):
raise NumberFormatError(
"%r is not a properly formatted decimal number" % string
)
return r


PREFIX_END = r'[^0-9@#.,]'
Expand Down
19 changes: 19 additions & 0 deletions tests/test_numbers.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,25 @@ def test_can_parse_decimals(self):
self.assertRaises(numbers.NumberFormatError,
lambda: numbers.parse_decimal('2,109,998', locale='de'))

def test_parse_decimal_strict_mode(self):
# Numbers with a misplaced grouping symbol should be rejected
with self.assertRaises(numbers.NumberFormatError):
numbers.parse_decimal('11.11', locale='de', strict=True)
# Partially grouped numbers should be rejected
with self.assertRaises(numbers.NumberFormatError):
numbers.parse_decimal('2000,000', locale='en_US', strict=True)
# Numbers with duplicate grouping symbols should be rejected
with self.assertRaises(numbers.NumberFormatError):
numbers.parse_decimal('0,,000', locale='en_US', strict=True)
# Properly formatted numbers should be accepted
assert str(numbers.parse_decimal('1.001', locale='de', strict=True)) == '1001'
# Trailing zeroes should be accepted
assert str(numbers.parse_decimal('3.00', locale='en_US', strict=True)) == '3.00'
# Numbers without any grouping symbol should be accepted
assert str(numbers.parse_decimal('2000.1', locale='en_US', strict=True)) == '2000.1'
# High precision numbers should be accepted
assert str(numbers.parse_decimal('5,000001', locale='fr', strict=True)) == '5.000001'


def test_list_currencies():
assert isinstance(list_currencies(), set)
Expand Down

0 comments on commit ff24ed9

Please sign in to comment.