Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/babel/numbers.py: 11%
375 statements
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
1"""
2 babel.numbers
3 ~~~~~~~~~~~~~
5 Locale dependent formatting and parsing of numeric data.
7 The default locale for the functions in this module is determined by the
8 following environment variables, in that order:
10 * ``LC_NUMERIC``,
11 * ``LC_ALL``, and
12 * ``LANG``
14 :copyright: (c) 2013-2022 by the Babel Team.
15 :license: BSD, see LICENSE for more details.
16"""
17# TODO:
18# Padding and rounding increments in pattern:
19# - https://www.unicode.org/reports/tr35/ (Appendix G.6)
20import decimal
21import re
22from datetime import date as date_, datetime as datetime_
23import warnings
25from babel.core import default_locale, Locale, get_global
27try:
28 # Python 2
29 long
30except NameError:
31 # Python 3
32 long = int
35LC_NUMERIC = default_locale('LC_NUMERIC')
38class UnknownCurrencyError(Exception):
39 """Exception thrown when a currency is requested for which no data is available.
40 """
42 def __init__(self, identifier):
43 """Create the exception.
44 :param identifier: the identifier string of the unsupported currency
45 """
46 Exception.__init__(self, 'Unknown currency %r.' % identifier)
48 #: The identifier of the locale that could not be found.
49 self.identifier = identifier
52def list_currencies(locale=None):
53 """ Return a `set` of normalized currency codes.
55 .. versionadded:: 2.5.0
57 :param locale: filters returned currency codes by the provided locale.
58 Expected to be a locale instance or code. If no locale is
59 provided, returns the list of all currencies from all
60 locales.
61 """
62 # Get locale-scoped currencies.
63 if locale:
64 currencies = Locale.parse(locale).currencies.keys()
65 else:
66 currencies = get_global('all_currencies')
67 return set(currencies)
70def validate_currency(currency, locale=None):
71 """ Check the currency code is recognized by Babel.
73 Accepts a ``locale`` parameter for fined-grained validation, working as
74 the one defined above in ``list_currencies()`` method.
76 Raises a `UnknownCurrencyError` exception if the currency is unknown to Babel.
77 """
78 if currency not in list_currencies(locale):
79 raise UnknownCurrencyError(currency)
82def is_currency(currency, locale=None):
83 """ Returns `True` only if a currency is recognized by Babel.
85 This method always return a Boolean and never raise.
86 """
87 if not currency or not isinstance(currency, str):
88 return False
89 try:
90 validate_currency(currency, locale)
91 except UnknownCurrencyError:
92 return False
93 return True
96def normalize_currency(currency, locale=None):
97 """Returns the normalized sting of any currency code.
99 Accepts a ``locale`` parameter for fined-grained validation, working as
100 the one defined above in ``list_currencies()`` method.
102 Returns None if the currency is unknown to Babel.
103 """
104 if isinstance(currency, str):
105 currency = currency.upper()
106 if not is_currency(currency, locale):
107 return
108 return currency
111def get_currency_name(currency, count=None, locale=LC_NUMERIC):
112 """Return the name used by the locale for the specified currency.
114 >>> get_currency_name('USD', locale='en_US')
115 u'US Dollar'
117 .. versionadded:: 0.9.4
119 :param currency: the currency code.
120 :param count: the optional count. If provided the currency name
121 will be pluralized to that number if possible.
122 :param locale: the `Locale` object or locale identifier.
123 """
124 loc = Locale.parse(locale)
125 if count is not None:
126 plural_form = loc.plural_form(count)
127 plural_names = loc._data['currency_names_plural']
128 if currency in plural_names:
129 currency_plural_names = plural_names[currency]
130 if plural_form in currency_plural_names:
131 return currency_plural_names[plural_form]
132 if 'other' in currency_plural_names:
133 return currency_plural_names['other']
134 return loc.currencies.get(currency, currency)
137def get_currency_symbol(currency, locale=LC_NUMERIC):
138 """Return the symbol used by the locale for the specified currency.
140 >>> get_currency_symbol('USD', locale='en_US')
141 u'$'
143 :param currency: the currency code.
144 :param locale: the `Locale` object or locale identifier.
145 """
146 return Locale.parse(locale).currency_symbols.get(currency, currency)
149def get_currency_precision(currency):
150 """Return currency's precision.
152 Precision is the number of decimals found after the decimal point in the
153 currency's format pattern.
155 .. versionadded:: 2.5.0
157 :param currency: the currency code.
158 """
159 precisions = get_global('currency_fractions')
160 return precisions.get(currency, precisions['DEFAULT'])[0]
163def get_currency_unit_pattern(currency, count=None, locale=LC_NUMERIC):
164 """
165 Return the unit pattern used for long display of a currency value
166 for a given locale.
167 This is a string containing ``{0}`` where the numeric part
168 should be substituted and ``{1}`` where the currency long display
169 name should be substituted.
171 >>> get_currency_unit_pattern('USD', locale='en_US', count=10)
172 u'{0} {1}'
174 .. versionadded:: 2.7.0
176 :param currency: the currency code.
177 :param count: the optional count. If provided the unit
178 pattern for that number will be returned.
179 :param locale: the `Locale` object or locale identifier.
180 """
181 loc = Locale.parse(locale)
182 if count is not None:
183 plural_form = loc.plural_form(count)
184 try:
185 return loc._data['currency_unit_patterns'][plural_form]
186 except LookupError:
187 # Fall back to 'other'
188 pass
190 return loc._data['currency_unit_patterns']['other']
193def get_territory_currencies(territory, start_date=None, end_date=None,
194 tender=True, non_tender=False,
195 include_details=False):
196 """Returns the list of currencies for the given territory that are valid for
197 the given date range. In addition to that the currency database
198 distinguishes between tender and non-tender currencies. By default only
199 tender currencies are returned.
201 The return value is a list of all currencies roughly ordered by the time
202 of when the currency became active. The longer the currency is being in
203 use the more to the left of the list it will be.
205 The start date defaults to today. If no end date is given it will be the
206 same as the start date. Otherwise a range can be defined. For instance
207 this can be used to find the currencies in use in Austria between 1995 and
208 2011:
210 >>> from datetime import date
211 >>> get_territory_currencies('AT', date(1995, 1, 1), date(2011, 1, 1))
212 ['ATS', 'EUR']
214 Likewise it's also possible to find all the currencies in use on a
215 single date:
217 >>> get_territory_currencies('AT', date(1995, 1, 1))
218 ['ATS']
219 >>> get_territory_currencies('AT', date(2011, 1, 1))
220 ['EUR']
222 By default the return value only includes tender currencies. This
223 however can be changed:
225 >>> get_territory_currencies('US')
226 ['USD']
227 >>> get_territory_currencies('US', tender=False, non_tender=True,
228 ... start_date=date(2014, 1, 1))
229 ['USN', 'USS']
231 .. versionadded:: 2.0
233 :param territory: the name of the territory to find the currency for.
234 :param start_date: the start date. If not given today is assumed.
235 :param end_date: the end date. If not given the start date is assumed.
236 :param tender: controls whether tender currencies should be included.
237 :param non_tender: controls whether non-tender currencies should be
238 included.
239 :param include_details: if set to `True`, instead of returning currency
240 codes the return value will be dictionaries
241 with detail information. In that case each
242 dictionary will have the keys ``'currency'``,
243 ``'from'``, ``'to'``, and ``'tender'``.
244 """
245 currencies = get_global('territory_currencies')
246 if start_date is None:
247 start_date = date_.today()
248 elif isinstance(start_date, datetime_):
249 start_date = start_date.date()
250 if end_date is None:
251 end_date = start_date
252 elif isinstance(end_date, datetime_):
253 end_date = end_date.date()
255 curs = currencies.get(territory.upper(), ())
256 # TODO: validate that the territory exists
258 def _is_active(start, end):
259 return (start is None or start <= end_date) and \
260 (end is None or end >= start_date)
262 result = []
263 for currency_code, start, end, is_tender in curs:
264 if start:
265 start = date_(*start)
266 if end:
267 end = date_(*end)
268 if ((is_tender and tender) or
269 (not is_tender and non_tender)) and _is_active(start, end):
270 if include_details:
271 result.append({
272 'currency': currency_code,
273 'from': start,
274 'to': end,
275 'tender': is_tender,
276 })
277 else:
278 result.append(currency_code)
280 return result
283def get_decimal_symbol(locale=LC_NUMERIC):
284 """Return the symbol used by the locale to separate decimal fractions.
286 >>> get_decimal_symbol('en_US')
287 u'.'
289 :param locale: the `Locale` object or locale identifier
290 """
291 return Locale.parse(locale).number_symbols.get('decimal', u'.')
294def get_plus_sign_symbol(locale=LC_NUMERIC):
295 """Return the plus sign symbol used by the current locale.
297 >>> get_plus_sign_symbol('en_US')
298 u'+'
300 :param locale: the `Locale` object or locale identifier
301 """
302 return Locale.parse(locale).number_symbols.get('plusSign', u'+')
305def get_minus_sign_symbol(locale=LC_NUMERIC):
306 """Return the plus sign symbol used by the current locale.
308 >>> get_minus_sign_symbol('en_US')
309 u'-'
311 :param locale: the `Locale` object or locale identifier
312 """
313 return Locale.parse(locale).number_symbols.get('minusSign', u'-')
316def get_exponential_symbol(locale=LC_NUMERIC):
317 """Return the symbol used by the locale to separate mantissa and exponent.
319 >>> get_exponential_symbol('en_US')
320 u'E'
322 :param locale: the `Locale` object or locale identifier
323 """
324 return Locale.parse(locale).number_symbols.get('exponential', u'E')
327def get_group_symbol(locale=LC_NUMERIC):
328 """Return the symbol used by the locale to separate groups of thousands.
330 >>> get_group_symbol('en_US')
331 u','
333 :param locale: the `Locale` object or locale identifier
334 """
335 return Locale.parse(locale).number_symbols.get('group', u',')
338def format_number(number, locale=LC_NUMERIC):
339 u"""Return the given number formatted for a specific locale.
341 >>> format_number(1099, locale='en_US') # doctest: +SKIP
342 u'1,099'
343 >>> format_number(1099, locale='de_DE') # doctest: +SKIP
344 u'1.099'
346 .. deprecated:: 2.6.0
348 Use babel.numbers.format_decimal() instead.
350 :param number: the number to format
351 :param locale: the `Locale` object or locale identifier
354 """
355 warnings.warn('Use babel.numbers.format_decimal() instead.', DeprecationWarning)
356 return format_decimal(number, locale=locale)
359def get_decimal_precision(number):
360 """Return maximum precision of a decimal instance's fractional part.
362 Precision is extracted from the fractional part only.
363 """
364 # Copied from: https://github.com/mahmoud/boltons/pull/59
365 assert isinstance(number, decimal.Decimal)
366 decimal_tuple = number.normalize().as_tuple()
367 if decimal_tuple.exponent >= 0:
368 return 0
369 return abs(decimal_tuple.exponent)
372def get_decimal_quantum(precision):
373 """Return minimal quantum of a number, as defined by precision."""
374 assert isinstance(precision, (int, long, decimal.Decimal))
375 return decimal.Decimal(10) ** (-precision)
378def format_decimal(
379 number, format=None, locale=LC_NUMERIC, decimal_quantization=True, group_separator=True):
380 u"""Return the given decimal number formatted for a specific locale.
382 >>> format_decimal(1.2345, locale='en_US')
383 u'1.234'
384 >>> format_decimal(1.2346, locale='en_US')
385 u'1.235'
386 >>> format_decimal(-1.2346, locale='en_US')
387 u'-1.235'
388 >>> format_decimal(1.2345, locale='sv_SE')
389 u'1,234'
390 >>> format_decimal(1.2345, locale='de')
391 u'1,234'
393 The appropriate thousands grouping and the decimal separator are used for
394 each locale:
396 >>> format_decimal(12345.5, locale='en_US')
397 u'12,345.5'
399 By default the locale is allowed to truncate and round a high-precision
400 number by forcing its format pattern onto the decimal part. You can bypass
401 this behavior with the `decimal_quantization` parameter:
403 >>> format_decimal(1.2346, locale='en_US')
404 u'1.235'
405 >>> format_decimal(1.2346, locale='en_US', decimal_quantization=False)
406 u'1.2346'
407 >>> format_decimal(12345.67, locale='fr_CA', group_separator=False)
408 u'12345,67'
409 >>> format_decimal(12345.67, locale='en_US', group_separator=True)
410 u'12,345.67'
412 :param number: the number to format
413 :param format:
414 :param locale: the `Locale` object or locale identifier
415 :param decimal_quantization: Truncate and round high-precision numbers to
416 the format pattern. Defaults to `True`.
417 :param group_separator: Boolean to switch group separator on/off in a locale's
418 number format.
419 """
420 locale = Locale.parse(locale)
421 if not format:
422 format = locale.decimal_formats.get(format)
423 pattern = parse_pattern(format)
424 return pattern.apply(
425 number, locale, decimal_quantization=decimal_quantization, group_separator=group_separator)
428def format_compact_decimal(number, *, format_type="short", locale=LC_NUMERIC, fraction_digits=0):
429 u"""Return the given decimal number formatted for a specific locale in compact form.
431 >>> format_compact_decimal(12345, format_type="short", locale='en_US')
432 u'12K'
433 >>> format_compact_decimal(12345, format_type="long", locale='en_US')
434 u'12 thousand'
435 >>> format_compact_decimal(12345, format_type="short", locale='en_US', fraction_digits=2)
436 u'12.35K'
437 >>> format_compact_decimal(1234567, format_type="short", locale="ja_JP")
438 u'123万'
439 >>> format_compact_decimal(2345678, format_type="long", locale="mk")
440 u'2 милиони'
441 >>> format_compact_decimal(21098765, format_type="long", locale="mk")
442 u'21 милион'
444 :param number: the number to format
445 :param format_type: Compact format to use ("short" or "long")
446 :param locale: the `Locale` object or locale identifier
447 :param fraction_digits: Number of digits after the decimal point to use. Defaults to `0`.
448 """
449 locale = Locale.parse(locale)
450 number, format = _get_compact_format(number, format_type, locale, fraction_digits)
451 pattern = parse_pattern(format)
452 return pattern.apply(number, locale, decimal_quantization=False)
455def _get_compact_format(number, format_type, locale, fraction_digits=0):
456 """Returns the number after dividing by the unit and the format pattern to use.
457 The algorithm is described here:
458 https://www.unicode.org/reports/tr35/tr35-45/tr35-numbers.html#Compact_Number_Formats.
459 """
460 format = None
461 compact_format = locale.compact_decimal_formats[format_type]
462 for magnitude in sorted([int(m) for m in compact_format["other"]], reverse=True):
463 if abs(number) >= magnitude:
464 # check the pattern using "other" as the amount
465 format = compact_format["other"][str(magnitude)]
466 pattern = parse_pattern(format).pattern
467 # if the pattern is "0", we do not divide the number
468 if pattern == "0":
469 break
470 # otherwise, we need to divide the number by the magnitude but remove zeros
471 # equal to the number of 0's in the pattern minus 1
472 number = number / (magnitude / (10 ** (pattern.count("0") - 1)))
473 # round to the number of fraction digits requested
474 number = round(number, fraction_digits)
475 # if the remaining number is singular, use the singular format
476 plural_form = locale.plural_form(abs(number))
477 plural_form = plural_form if plural_form in compact_format else "other"
478 format = compact_format[plural_form][str(magnitude)]
479 break
480 if format is None: # Did not find a format, fall back.
481 format = locale.decimal_formats.get(None)
482 return number, format
485class UnknownCurrencyFormatError(KeyError):
486 """Exception raised when an unknown currency format is requested."""
489def format_currency(
490 number, currency, format=None, locale=LC_NUMERIC, currency_digits=True,
491 format_type='standard', decimal_quantization=True, group_separator=True):
492 u"""Return formatted currency value.
494 >>> format_currency(1099.98, 'USD', locale='en_US')
495 u'$1,099.98'
496 >>> format_currency(1099.98, 'USD', locale='es_CO')
497 u'US$\\xa01.099,98'
498 >>> format_currency(1099.98, 'EUR', locale='de_DE')
499 u'1.099,98\\xa0\\u20ac'
501 The format can also be specified explicitly. The currency is
502 placed with the '¤' sign. As the sign gets repeated the format
503 expands (¤ being the symbol, ¤¤ is the currency abbreviation and
504 ¤¤¤ is the full name of the currency):
506 >>> format_currency(1099.98, 'EUR', u'\xa4\xa4 #,##0.00', locale='en_US')
507 u'EUR 1,099.98'
508 >>> format_currency(1099.98, 'EUR', u'#,##0.00 \xa4\xa4\xa4', locale='en_US')
509 u'1,099.98 euros'
511 Currencies usually have a specific number of decimal digits. This function
512 favours that information over the given format:
514 >>> format_currency(1099.98, 'JPY', locale='en_US')
515 u'\\xa51,100'
516 >>> format_currency(1099.98, 'COP', u'#,##0.00', locale='es_ES')
517 u'1.099,98'
519 However, the number of decimal digits can be overriden from the currency
520 information, by setting the last parameter to ``False``:
522 >>> format_currency(1099.98, 'JPY', locale='en_US', currency_digits=False)
523 u'\\xa51,099.98'
524 >>> format_currency(1099.98, 'COP', u'#,##0.00', locale='es_ES', currency_digits=False)
525 u'1.099,98'
527 If a format is not specified the type of currency format to use
528 from the locale can be specified:
530 >>> format_currency(1099.98, 'EUR', locale='en_US', format_type='standard')
531 u'\\u20ac1,099.98'
533 When the given currency format type is not available, an exception is
534 raised:
536 >>> format_currency('1099.98', 'EUR', locale='root', format_type='unknown')
537 Traceback (most recent call last):
538 ...
539 UnknownCurrencyFormatError: "'unknown' is not a known currency format type"
541 >>> format_currency(101299.98, 'USD', locale='en_US', group_separator=False)
542 u'$101299.98'
544 >>> format_currency(101299.98, 'USD', locale='en_US', group_separator=True)
545 u'$101,299.98'
547 You can also pass format_type='name' to use long display names. The order of
548 the number and currency name, along with the correct localized plural form
549 of the currency name, is chosen according to locale:
551 >>> format_currency(1, 'USD', locale='en_US', format_type='name')
552 u'1.00 US dollar'
553 >>> format_currency(1099.98, 'USD', locale='en_US', format_type='name')
554 u'1,099.98 US dollars'
555 >>> format_currency(1099.98, 'USD', locale='ee', format_type='name')
556 u'us ga dollar 1,099.98'
558 By default the locale is allowed to truncate and round a high-precision
559 number by forcing its format pattern onto the decimal part. You can bypass
560 this behavior with the `decimal_quantization` parameter:
562 >>> format_currency(1099.9876, 'USD', locale='en_US')
563 u'$1,099.99'
564 >>> format_currency(1099.9876, 'USD', locale='en_US', decimal_quantization=False)
565 u'$1,099.9876'
567 :param number: the number to format
568 :param currency: the currency code
569 :param format: the format string to use
570 :param locale: the `Locale` object or locale identifier
571 :param currency_digits: use the currency's natural number of decimal digits
572 :param format_type: the currency format type to use
573 :param decimal_quantization: Truncate and round high-precision numbers to
574 the format pattern. Defaults to `True`.
575 :param group_separator: Boolean to switch group separator on/off in a locale's
576 number format.
578 """
579 if format_type == 'name':
580 return _format_currency_long_name(number, currency, format=format,
581 locale=locale, currency_digits=currency_digits,
582 decimal_quantization=decimal_quantization, group_separator=group_separator)
583 locale = Locale.parse(locale)
584 if format:
585 pattern = parse_pattern(format)
586 else:
587 try:
588 pattern = locale.currency_formats[format_type]
589 except KeyError:
590 raise UnknownCurrencyFormatError(
591 "%r is not a known currency format type" % format_type)
593 return pattern.apply(
594 number, locale, currency=currency, currency_digits=currency_digits,
595 decimal_quantization=decimal_quantization, group_separator=group_separator)
598def _format_currency_long_name(
599 number, currency, format=None, locale=LC_NUMERIC, currency_digits=True,
600 format_type='standard', decimal_quantization=True, group_separator=True):
601 # Algorithm described here:
602 # https://www.unicode.org/reports/tr35/tr35-numbers.html#Currencies
603 locale = Locale.parse(locale)
604 # Step 1.
605 # There are no examples of items with explicit count (0 or 1) in current
606 # locale data. So there is no point implementing that.
607 # Step 2.
609 # Correct number to numeric type, important for looking up plural rules:
610 if isinstance(number, str):
611 number_n = float(number)
612 else:
613 number_n = number
615 # Step 3.
616 unit_pattern = get_currency_unit_pattern(currency, count=number_n, locale=locale)
618 # Step 4.
619 display_name = get_currency_name(currency, count=number_n, locale=locale)
621 # Step 5.
622 if not format:
623 format = locale.decimal_formats.get(format)
625 pattern = parse_pattern(format)
627 number_part = pattern.apply(
628 number, locale, currency=currency, currency_digits=currency_digits,
629 decimal_quantization=decimal_quantization, group_separator=group_separator)
631 return unit_pattern.format(number_part, display_name)
634def format_percent(
635 number, format=None, locale=LC_NUMERIC, decimal_quantization=True, group_separator=True):
636 """Return formatted percent value for a specific locale.
638 >>> format_percent(0.34, locale='en_US')
639 u'34%'
640 >>> format_percent(25.1234, locale='en_US')
641 u'2,512%'
642 >>> format_percent(25.1234, locale='sv_SE')
643 u'2\\xa0512\\xa0%'
645 The format pattern can also be specified explicitly:
647 >>> format_percent(25.1234, u'#,##0\u2030', locale='en_US')
648 u'25,123\u2030'
650 By default the locale is allowed to truncate and round a high-precision
651 number by forcing its format pattern onto the decimal part. You can bypass
652 this behavior with the `decimal_quantization` parameter:
654 >>> format_percent(23.9876, locale='en_US')
655 u'2,399%'
656 >>> format_percent(23.9876, locale='en_US', decimal_quantization=False)
657 u'2,398.76%'
659 >>> format_percent(229291.1234, locale='pt_BR', group_separator=False)
660 u'22929112%'
662 >>> format_percent(229291.1234, locale='pt_BR', group_separator=True)
663 u'22.929.112%'
665 :param number: the percent number to format
666 :param format:
667 :param locale: the `Locale` object or locale identifier
668 :param decimal_quantization: Truncate and round high-precision numbers to
669 the format pattern. Defaults to `True`.
670 :param group_separator: Boolean to switch group separator on/off in a locale's
671 number format.
672 """
673 locale = Locale.parse(locale)
674 if not format:
675 format = locale.percent_formats.get(format)
676 pattern = parse_pattern(format)
677 return pattern.apply(
678 number, locale, decimal_quantization=decimal_quantization, group_separator=group_separator)
681def format_scientific(
682 number, format=None, locale=LC_NUMERIC, decimal_quantization=True):
683 """Return value formatted in scientific notation for a specific locale.
685 >>> format_scientific(10000, locale='en_US')
686 u'1E4'
688 The format pattern can also be specified explicitly:
690 >>> format_scientific(1234567, u'##0.##E00', locale='en_US')
691 u'1.23E06'
693 By default the locale is allowed to truncate and round a high-precision
694 number by forcing its format pattern onto the decimal part. You can bypass
695 this behavior with the `decimal_quantization` parameter:
697 >>> format_scientific(1234.9876, u'#.##E0', locale='en_US')
698 u'1.23E3'
699 >>> format_scientific(1234.9876, u'#.##E0', locale='en_US', decimal_quantization=False)
700 u'1.2349876E3'
702 :param number: the number to format
703 :param format:
704 :param locale: the `Locale` object or locale identifier
705 :param decimal_quantization: Truncate and round high-precision numbers to
706 the format pattern. Defaults to `True`.
707 """
708 locale = Locale.parse(locale)
709 if not format:
710 format = locale.scientific_formats.get(format)
711 pattern = parse_pattern(format)
712 return pattern.apply(
713 number, locale, decimal_quantization=decimal_quantization)
716class NumberFormatError(ValueError):
717 """Exception raised when a string cannot be parsed into a number."""
719 def __init__(self, message, suggestions=None):
720 super().__init__(message)
721 #: a list of properly formatted numbers derived from the invalid input
722 self.suggestions = suggestions
725def parse_number(string, locale=LC_NUMERIC):
726 """Parse localized number string into an integer.
728 >>> parse_number('1,099', locale='en_US')
729 1099
730 >>> parse_number('1.099', locale='de_DE')
731 1099
733 When the given string cannot be parsed, an exception is raised:
735 >>> parse_number('1.099,98', locale='de')
736 Traceback (most recent call last):
737 ...
738 NumberFormatError: '1.099,98' is not a valid number
740 :param string: the string to parse
741 :param locale: the `Locale` object or locale identifier
742 :return: the parsed number
743 :raise `NumberFormatError`: if the string can not be converted to a number
744 """
745 try:
746 return int(string.replace(get_group_symbol(locale), ''))
747 except ValueError:
748 raise NumberFormatError('%r is not a valid number' % string)
751def parse_decimal(string, locale=LC_NUMERIC, strict=False):
752 """Parse localized decimal string into a decimal.
754 >>> parse_decimal('1,099.98', locale='en_US')
755 Decimal('1099.98')
756 >>> parse_decimal('1.099,98', locale='de')
757 Decimal('1099.98')
758 >>> parse_decimal('12 345,123', locale='ru')
759 Decimal('12345.123')
761 When the given string cannot be parsed, an exception is raised:
763 >>> parse_decimal('2,109,998', locale='de')
764 Traceback (most recent call last):
765 ...
766 NumberFormatError: '2,109,998' is not a valid decimal number
768 If `strict` is set to `True` and the given string contains a number
769 formatted in an irregular way, an exception is raised:
771 >>> parse_decimal('30.00', locale='de', strict=True)
772 Traceback (most recent call last):
773 ...
774 NumberFormatError: '30.00' is not a properly formatted decimal number. Did you mean '3.000'? Or maybe '30,00'?
776 >>> parse_decimal('0.00', locale='de', strict=True)
777 Traceback (most recent call last):
778 ...
779 NumberFormatError: '0.00' is not a properly formatted decimal number. Did you mean '0'?
781 :param string: the string to parse
782 :param locale: the `Locale` object or locale identifier
783 :param strict: controls whether numbers formatted in a weird way are
784 accepted or rejected
785 :raise NumberFormatError: if the string can not be converted to a
786 decimal number
787 """
788 locale = Locale.parse(locale)
789 group_symbol = get_group_symbol(locale)
790 decimal_symbol = get_decimal_symbol(locale)
792 if not strict and (
793 group_symbol == u'\xa0' and # if the grouping symbol is U+00A0 NO-BREAK SPACE,
794 group_symbol not in string and # and the string to be parsed does not contain it,
795 ' ' in string # but it does contain a space instead,
796 ):
797 # ... it's reasonable to assume it is taking the place of the grouping symbol.
798 string = string.replace(' ', group_symbol)
800 try:
801 parsed = decimal.Decimal(string.replace(group_symbol, '')
802 .replace(decimal_symbol, '.'))
803 except decimal.InvalidOperation:
804 raise NumberFormatError('%r is not a valid decimal number' % string)
805 if strict and group_symbol in string:
806 proper = format_decimal(parsed, locale=locale, decimal_quantization=False)
807 if string != proper and string.rstrip('0') != (proper + decimal_symbol):
808 try:
809 parsed_alt = decimal.Decimal(string.replace(decimal_symbol, '')
810 .replace(group_symbol, '.'))
811 except decimal.InvalidOperation:
812 raise NumberFormatError((
813 "%r is not a properly formatted decimal number. Did you mean %r?" %
814 (string, proper)
815 ), suggestions=[proper])
816 else:
817 proper_alt = format_decimal(parsed_alt, locale=locale, decimal_quantization=False)
818 if proper_alt == proper:
819 raise NumberFormatError((
820 "%r is not a properly formatted decimal number. Did you mean %r?" %
821 (string, proper)
822 ), suggestions=[proper])
823 else:
824 raise NumberFormatError((
825 "%r is not a properly formatted decimal number. Did you mean %r? Or maybe %r?" %
826 (string, proper, proper_alt)
827 ), suggestions=[proper, proper_alt])
828 return parsed
831PREFIX_END = r'[^0-9@#.,]'
832NUMBER_TOKEN = r'[0-9@#.,E+]'
834PREFIX_PATTERN = r"(?P<prefix>(?:'[^']*'|%s)*)" % PREFIX_END
835NUMBER_PATTERN = r"(?P<number>%s*)" % NUMBER_TOKEN
836SUFFIX_PATTERN = r"(?P<suffix>.*)"
838number_re = re.compile(r"%s%s%s" % (PREFIX_PATTERN, NUMBER_PATTERN,
839 SUFFIX_PATTERN))
842def parse_grouping(p):
843 """Parse primary and secondary digit grouping
845 >>> parse_grouping('##')
846 (1000, 1000)
847 >>> parse_grouping('#,###')
848 (3, 3)
849 >>> parse_grouping('#,####,###')
850 (3, 4)
851 """
852 width = len(p)
853 g1 = p.rfind(',')
854 if g1 == -1:
855 return 1000, 1000
856 g1 = width - g1 - 1
857 g2 = p[:-g1 - 1].rfind(',')
858 if g2 == -1:
859 return g1, g1
860 g2 = width - g1 - g2 - 2
861 return g1, g2
864def parse_pattern(pattern):
865 """Parse number format patterns"""
866 if isinstance(pattern, NumberPattern):
867 return pattern
869 def _match_number(pattern):
870 rv = number_re.search(pattern)
871 if rv is None:
872 raise ValueError('Invalid number pattern %r' % pattern)
873 return rv.groups()
875 pos_pattern = pattern
877 # Do we have a negative subpattern?
878 if ';' in pattern:
879 pos_pattern, neg_pattern = pattern.split(';', 1)
880 pos_prefix, number, pos_suffix = _match_number(pos_pattern)
881 neg_prefix, _, neg_suffix = _match_number(neg_pattern)
882 else:
883 pos_prefix, number, pos_suffix = _match_number(pos_pattern)
884 neg_prefix = '-' + pos_prefix
885 neg_suffix = pos_suffix
886 if 'E' in number:
887 number, exp = number.split('E', 1)
888 else:
889 exp = None
890 if '@' in number:
891 if '.' in number and '0' in number:
892 raise ValueError('Significant digit patterns can not contain '
893 '"@" or "0"')
894 if '.' in number:
895 integer, fraction = number.rsplit('.', 1)
896 else:
897 integer = number
898 fraction = ''
900 def parse_precision(p):
901 """Calculate the min and max allowed digits"""
902 min = max = 0
903 for c in p:
904 if c in '@0':
905 min += 1
906 max += 1
907 elif c == '#':
908 max += 1
909 elif c == ',':
910 continue
911 else:
912 break
913 return min, max
915 int_prec = parse_precision(integer)
916 frac_prec = parse_precision(fraction)
917 if exp:
918 exp_plus = exp.startswith('+')
919 exp = exp.lstrip('+')
920 exp_prec = parse_precision(exp)
921 else:
922 exp_plus = None
923 exp_prec = None
924 grouping = parse_grouping(integer)
925 return NumberPattern(pattern, (pos_prefix, neg_prefix),
926 (pos_suffix, neg_suffix), grouping,
927 int_prec, frac_prec,
928 exp_prec, exp_plus)
931class NumberPattern:
933 def __init__(self, pattern, prefix, suffix, grouping,
934 int_prec, frac_prec, exp_prec, exp_plus):
935 # Metadata of the decomposed parsed pattern.
936 self.pattern = pattern
937 self.prefix = prefix
938 self.suffix = suffix
939 self.grouping = grouping
940 self.int_prec = int_prec
941 self.frac_prec = frac_prec
942 self.exp_prec = exp_prec
943 self.exp_plus = exp_plus
944 self.scale = self.compute_scale()
946 def __repr__(self):
947 return '<%s %r>' % (type(self).__name__, self.pattern)
949 def compute_scale(self):
950 """Return the scaling factor to apply to the number before rendering.
952 Auto-set to a factor of 2 or 3 if presence of a ``%`` or ``‰`` sign is
953 detected in the prefix or suffix of the pattern. Default is to not mess
954 with the scale at all and keep it to 0.
955 """
956 scale = 0
957 if '%' in ''.join(self.prefix + self.suffix):
958 scale = 2
959 elif u'‰' in ''.join(self.prefix + self.suffix):
960 scale = 3
961 return scale
963 def scientific_notation_elements(self, value, locale):
964 """ Returns normalized scientific notation components of a value.
965 """
966 # Normalize value to only have one lead digit.
967 exp = value.adjusted()
968 value = value * get_decimal_quantum(exp)
969 assert value.adjusted() == 0
971 # Shift exponent and value by the minimum number of leading digits
972 # imposed by the rendering pattern. And always make that number
973 # greater or equal to 1.
974 lead_shift = max([1, min(self.int_prec)]) - 1
975 exp = exp - lead_shift
976 value = value * get_decimal_quantum(-lead_shift)
978 # Get exponent sign symbol.
979 exp_sign = ''
980 if exp < 0:
981 exp_sign = get_minus_sign_symbol(locale)
982 elif self.exp_plus:
983 exp_sign = get_plus_sign_symbol(locale)
985 # Normalize exponent value now that we have the sign.
986 exp = abs(exp)
988 return value, exp, exp_sign
990 def apply(
991 self,
992 value,
993 locale,
994 currency=None,
995 currency_digits=True,
996 decimal_quantization=True,
997 force_frac=None,
998 group_separator=True,
999 ):
1000 """Renders into a string a number following the defined pattern.
1002 Forced decimal quantization is active by default so we'll produce a
1003 number string that is strictly following CLDR pattern definitions.
1005 :param value: The value to format. If this is not a Decimal object,
1006 it will be cast to one.
1007 :type value: decimal.Decimal|float|int
1008 :param locale: The locale to use for formatting.
1009 :type locale: str|babel.core.Locale
1010 :param currency: Which currency, if any, to format as.
1011 :type currency: str|None
1012 :param currency_digits: Whether or not to use the currency's precision.
1013 If false, the pattern's precision is used.
1014 :type currency_digits: bool
1015 :param decimal_quantization: Whether decimal numbers should be forcibly
1016 quantized to produce a formatted output
1017 strictly matching the CLDR definition for
1018 the locale.
1019 :type decimal_quantization: bool
1020 :param force_frac: DEPRECATED - a forced override for `self.frac_prec`
1021 for a single formatting invocation.
1022 :return: Formatted decimal string.
1023 :rtype: str
1024 """
1025 if not isinstance(value, decimal.Decimal):
1026 value = decimal.Decimal(str(value))
1028 value = value.scaleb(self.scale)
1030 # Separate the absolute value from its sign.
1031 is_negative = int(value.is_signed())
1032 value = abs(value).normalize()
1034 # Prepare scientific notation metadata.
1035 if self.exp_prec:
1036 value, exp, exp_sign = self.scientific_notation_elements(value, locale)
1038 # Adjust the precision of the fractional part and force it to the
1039 # currency's if necessary.
1040 if force_frac:
1041 # TODO (3.x?): Remove this parameter
1042 warnings.warn('The force_frac parameter to NumberPattern.apply() is deprecated.', DeprecationWarning)
1043 frac_prec = force_frac
1044 elif currency and currency_digits:
1045 frac_prec = (get_currency_precision(currency), ) * 2
1046 else:
1047 frac_prec = self.frac_prec
1049 # Bump decimal precision to the natural precision of the number if it
1050 # exceeds the one we're about to use. This adaptative precision is only
1051 # triggered if the decimal quantization is disabled or if a scientific
1052 # notation pattern has a missing mandatory fractional part (as in the
1053 # default '#E0' pattern). This special case has been extensively
1054 # discussed at https://github.com/python-babel/babel/pull/494#issuecomment-307649969 .
1055 if not decimal_quantization or (self.exp_prec and frac_prec == (0, 0)):
1056 frac_prec = (frac_prec[0], max([frac_prec[1], get_decimal_precision(value)]))
1058 # Render scientific notation.
1059 if self.exp_prec:
1060 number = ''.join([
1061 self._quantize_value(value, locale, frac_prec, group_separator),
1062 get_exponential_symbol(locale),
1063 exp_sign,
1064 self._format_int(
1065 str(exp), self.exp_prec[0], self.exp_prec[1], locale)])
1067 # Is it a siginificant digits pattern?
1068 elif '@' in self.pattern:
1069 text = self._format_significant(value,
1070 self.int_prec[0],
1071 self.int_prec[1])
1072 a, sep, b = text.partition(".")
1073 number = self._format_int(a, 0, 1000, locale)
1074 if sep:
1075 number += get_decimal_symbol(locale) + b
1077 # A normal number pattern.
1078 else:
1079 number = self._quantize_value(value, locale, frac_prec, group_separator)
1081 retval = ''.join([
1082 self.prefix[is_negative],
1083 number,
1084 self.suffix[is_negative]])
1086 if u'¤' in retval:
1087 retval = retval.replace(u'¤¤¤',
1088 get_currency_name(currency, value, locale))
1089 retval = retval.replace(u'¤¤', currency.upper())
1090 retval = retval.replace(u'¤', get_currency_symbol(currency, locale))
1092 return retval
1094 #
1095 # This is one tricky piece of code. The idea is to rely as much as possible
1096 # on the decimal module to minimize the amount of code.
1097 #
1098 # Conceptually, the implementation of this method can be summarized in the
1099 # following steps:
1100 #
1101 # - Move or shift the decimal point (i.e. the exponent) so the maximum
1102 # amount of significant digits fall into the integer part (i.e. to the
1103 # left of the decimal point)
1104 #
1105 # - Round the number to the nearest integer, discarding all the fractional
1106 # part which contained extra digits to be eliminated
1107 #
1108 # - Convert the rounded integer to a string, that will contain the final
1109 # sequence of significant digits already trimmed to the maximum
1110 #
1111 # - Restore the original position of the decimal point, potentially
1112 # padding with zeroes on either side
1113 #
1114 def _format_significant(self, value, minimum, maximum):
1115 exp = value.adjusted()
1116 scale = maximum - 1 - exp
1117 digits = str(value.scaleb(scale).quantize(decimal.Decimal(1)))
1118 if scale <= 0:
1119 result = digits + '0' * -scale
1120 else:
1121 intpart = digits[:-scale]
1122 i = len(intpart)
1123 j = i + max(minimum - i, 0)
1124 result = "{intpart}.{pad:0<{fill}}{fracpart}{fracextra}".format(
1125 intpart=intpart or '0',
1126 pad='',
1127 fill=-min(exp + 1, 0),
1128 fracpart=digits[i:j],
1129 fracextra=digits[j:].rstrip('0'),
1130 ).rstrip('.')
1131 return result
1133 def _format_int(self, value, min, max, locale):
1134 width = len(value)
1135 if width < min:
1136 value = '0' * (min - width) + value
1137 gsize = self.grouping[0]
1138 ret = ''
1139 symbol = get_group_symbol(locale)
1140 while len(value) > gsize:
1141 ret = symbol + value[-gsize:] + ret
1142 value = value[:-gsize]
1143 gsize = self.grouping[1]
1144 return value + ret
1146 def _quantize_value(self, value, locale, frac_prec, group_separator):
1147 quantum = get_decimal_quantum(frac_prec[1])
1148 rounded = value.quantize(quantum)
1149 a, sep, b = "{:f}".format(rounded).partition(".")
1150 integer_part = a
1151 if group_separator:
1152 integer_part = self._format_int(a, self.int_prec[0], self.int_prec[1], locale)
1153 number = integer_part + self._format_frac(b or '0', locale, frac_prec)
1154 return number
1156 def _format_frac(self, value, locale, force_frac=None):
1157 min, max = force_frac or self.frac_prec
1158 if len(value) < min:
1159 value += ('0' * (min - len(value)))
1160 if max == 0 or (min == 0 and int(value) == 0):
1161 return ''
1162 while len(value) > min and value[-1] == '0':
1163 value = value[:-1]
1164 return get_decimal_symbol(locale) + value