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

1""" 

2 babel.numbers 

3 ~~~~~~~~~~~~~ 

4 

5 Locale dependent formatting and parsing of numeric data. 

6 

7 The default locale for the functions in this module is determined by the 

8 following environment variables, in that order: 

9 

10 * ``LC_NUMERIC``, 

11 * ``LC_ALL``, and 

12 * ``LANG`` 

13 

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 

24 

25from babel.core import default_locale, Locale, get_global 

26 

27try: 

28 # Python 2 

29 long 

30except NameError: 

31 # Python 3 

32 long = int 

33 

34 

35LC_NUMERIC = default_locale('LC_NUMERIC') 

36 

37 

38class UnknownCurrencyError(Exception): 

39 """Exception thrown when a currency is requested for which no data is available. 

40 """ 

41 

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) 

47 

48 #: The identifier of the locale that could not be found. 

49 self.identifier = identifier 

50 

51 

52def list_currencies(locale=None): 

53 """ Return a `set` of normalized currency codes. 

54 

55 .. versionadded:: 2.5.0 

56 

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) 

68 

69 

70def validate_currency(currency, locale=None): 

71 """ Check the currency code is recognized by Babel. 

72 

73 Accepts a ``locale`` parameter for fined-grained validation, working as 

74 the one defined above in ``list_currencies()`` method. 

75 

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) 

80 

81 

82def is_currency(currency, locale=None): 

83 """ Returns `True` only if a currency is recognized by Babel. 

84 

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 

94 

95 

96def normalize_currency(currency, locale=None): 

97 """Returns the normalized sting of any currency code. 

98 

99 Accepts a ``locale`` parameter for fined-grained validation, working as 

100 the one defined above in ``list_currencies()`` method. 

101 

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 

109 

110 

111def get_currency_name(currency, count=None, locale=LC_NUMERIC): 

112 """Return the name used by the locale for the specified currency. 

113 

114 >>> get_currency_name('USD', locale='en_US') 

115 u'US Dollar' 

116 

117 .. versionadded:: 0.9.4 

118 

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) 

135 

136 

137def get_currency_symbol(currency, locale=LC_NUMERIC): 

138 """Return the symbol used by the locale for the specified currency. 

139 

140 >>> get_currency_symbol('USD', locale='en_US') 

141 u'$' 

142 

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) 

147 

148 

149def get_currency_precision(currency): 

150 """Return currency's precision. 

151 

152 Precision is the number of decimals found after the decimal point in the 

153 currency's format pattern. 

154 

155 .. versionadded:: 2.5.0 

156 

157 :param currency: the currency code. 

158 """ 

159 precisions = get_global('currency_fractions') 

160 return precisions.get(currency, precisions['DEFAULT'])[0] 

161 

162 

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. 

170 

171 >>> get_currency_unit_pattern('USD', locale='en_US', count=10) 

172 u'{0} {1}' 

173 

174 .. versionadded:: 2.7.0 

175 

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 

189 

190 return loc._data['currency_unit_patterns']['other'] 

191 

192 

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. 

200 

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. 

204 

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: 

209 

210 >>> from datetime import date 

211 >>> get_territory_currencies('AT', date(1995, 1, 1), date(2011, 1, 1)) 

212 ['ATS', 'EUR'] 

213 

214 Likewise it's also possible to find all the currencies in use on a 

215 single date: 

216 

217 >>> get_territory_currencies('AT', date(1995, 1, 1)) 

218 ['ATS'] 

219 >>> get_territory_currencies('AT', date(2011, 1, 1)) 

220 ['EUR'] 

221 

222 By default the return value only includes tender currencies. This 

223 however can be changed: 

224 

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'] 

230 

231 .. versionadded:: 2.0 

232 

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() 

254 

255 curs = currencies.get(territory.upper(), ()) 

256 # TODO: validate that the territory exists 

257 

258 def _is_active(start, end): 

259 return (start is None or start <= end_date) and \ 

260 (end is None or end >= start_date) 

261 

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) 

279 

280 return result 

281 

282 

283def get_decimal_symbol(locale=LC_NUMERIC): 

284 """Return the symbol used by the locale to separate decimal fractions. 

285 

286 >>> get_decimal_symbol('en_US') 

287 u'.' 

288 

289 :param locale: the `Locale` object or locale identifier 

290 """ 

291 return Locale.parse(locale).number_symbols.get('decimal', u'.') 

292 

293 

294def get_plus_sign_symbol(locale=LC_NUMERIC): 

295 """Return the plus sign symbol used by the current locale. 

296 

297 >>> get_plus_sign_symbol('en_US') 

298 u'+' 

299 

300 :param locale: the `Locale` object or locale identifier 

301 """ 

302 return Locale.parse(locale).number_symbols.get('plusSign', u'+') 

303 

304 

305def get_minus_sign_symbol(locale=LC_NUMERIC): 

306 """Return the plus sign symbol used by the current locale. 

307 

308 >>> get_minus_sign_symbol('en_US') 

309 u'-' 

310 

311 :param locale: the `Locale` object or locale identifier 

312 """ 

313 return Locale.parse(locale).number_symbols.get('minusSign', u'-') 

314 

315 

316def get_exponential_symbol(locale=LC_NUMERIC): 

317 """Return the symbol used by the locale to separate mantissa and exponent. 

318 

319 >>> get_exponential_symbol('en_US') 

320 u'E' 

321 

322 :param locale: the `Locale` object or locale identifier 

323 """ 

324 return Locale.parse(locale).number_symbols.get('exponential', u'E') 

325 

326 

327def get_group_symbol(locale=LC_NUMERIC): 

328 """Return the symbol used by the locale to separate groups of thousands. 

329 

330 >>> get_group_symbol('en_US') 

331 u',' 

332 

333 :param locale: the `Locale` object or locale identifier 

334 """ 

335 return Locale.parse(locale).number_symbols.get('group', u',') 

336 

337 

338def format_number(number, locale=LC_NUMERIC): 

339 u"""Return the given number formatted for a specific locale. 

340 

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' 

345 

346 .. deprecated:: 2.6.0 

347 

348 Use babel.numbers.format_decimal() instead. 

349 

350 :param number: the number to format 

351 :param locale: the `Locale` object or locale identifier 

352 

353 

354 """ 

355 warnings.warn('Use babel.numbers.format_decimal() instead.', DeprecationWarning) 

356 return format_decimal(number, locale=locale) 

357 

358 

359def get_decimal_precision(number): 

360 """Return maximum precision of a decimal instance's fractional part. 

361 

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) 

370 

371 

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) 

376 

377 

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. 

381 

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' 

392 

393 The appropriate thousands grouping and the decimal separator are used for 

394 each locale: 

395 

396 >>> format_decimal(12345.5, locale='en_US') 

397 u'12,345.5' 

398 

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: 

402 

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' 

411 

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) 

426 

427 

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. 

430 

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 милион' 

443 

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) 

453 

454 

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 

483 

484 

485class UnknownCurrencyFormatError(KeyError): 

486 """Exception raised when an unknown currency format is requested.""" 

487 

488 

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. 

493 

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' 

500 

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): 

505 

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' 

510 

511 Currencies usually have a specific number of decimal digits. This function 

512 favours that information over the given format: 

513 

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' 

518 

519 However, the number of decimal digits can be overriden from the currency 

520 information, by setting the last parameter to ``False``: 

521 

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' 

526 

527 If a format is not specified the type of currency format to use 

528 from the locale can be specified: 

529 

530 >>> format_currency(1099.98, 'EUR', locale='en_US', format_type='standard') 

531 u'\\u20ac1,099.98' 

532 

533 When the given currency format type is not available, an exception is 

534 raised: 

535 

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" 

540 

541 >>> format_currency(101299.98, 'USD', locale='en_US', group_separator=False) 

542 u'$101299.98' 

543 

544 >>> format_currency(101299.98, 'USD', locale='en_US', group_separator=True) 

545 u'$101,299.98' 

546 

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: 

550 

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' 

557 

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: 

561 

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' 

566 

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. 

577 

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) 

592 

593 return pattern.apply( 

594 number, locale, currency=currency, currency_digits=currency_digits, 

595 decimal_quantization=decimal_quantization, group_separator=group_separator) 

596 

597 

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. 

608 

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 

614 

615 # Step 3. 

616 unit_pattern = get_currency_unit_pattern(currency, count=number_n, locale=locale) 

617 

618 # Step 4. 

619 display_name = get_currency_name(currency, count=number_n, locale=locale) 

620 

621 # Step 5. 

622 if not format: 

623 format = locale.decimal_formats.get(format) 

624 

625 pattern = parse_pattern(format) 

626 

627 number_part = pattern.apply( 

628 number, locale, currency=currency, currency_digits=currency_digits, 

629 decimal_quantization=decimal_quantization, group_separator=group_separator) 

630 

631 return unit_pattern.format(number_part, display_name) 

632 

633 

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. 

637 

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%' 

644 

645 The format pattern can also be specified explicitly: 

646 

647 >>> format_percent(25.1234, u'#,##0\u2030', locale='en_US') 

648 u'25,123\u2030' 

649 

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: 

653 

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%' 

658 

659 >>> format_percent(229291.1234, locale='pt_BR', group_separator=False) 

660 u'22929112%' 

661 

662 >>> format_percent(229291.1234, locale='pt_BR', group_separator=True) 

663 u'22.929.112%' 

664 

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) 

679 

680 

681def format_scientific( 

682 number, format=None, locale=LC_NUMERIC, decimal_quantization=True): 

683 """Return value formatted in scientific notation for a specific locale. 

684 

685 >>> format_scientific(10000, locale='en_US') 

686 u'1E4' 

687 

688 The format pattern can also be specified explicitly: 

689 

690 >>> format_scientific(1234567, u'##0.##E00', locale='en_US') 

691 u'1.23E06' 

692 

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: 

696 

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' 

701 

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) 

714 

715 

716class NumberFormatError(ValueError): 

717 """Exception raised when a string cannot be parsed into a number.""" 

718 

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 

723 

724 

725def parse_number(string, locale=LC_NUMERIC): 

726 """Parse localized number string into an integer. 

727 

728 >>> parse_number('1,099', locale='en_US') 

729 1099 

730 >>> parse_number('1.099', locale='de_DE') 

731 1099 

732 

733 When the given string cannot be parsed, an exception is raised: 

734 

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 

739 

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) 

749 

750 

751def parse_decimal(string, locale=LC_NUMERIC, strict=False): 

752 """Parse localized decimal string into a decimal. 

753 

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') 

760 

761 When the given string cannot be parsed, an exception is raised: 

762 

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 

767 

768 If `strict` is set to `True` and the given string contains a number 

769 formatted in an irregular way, an exception is raised: 

770 

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'? 

775 

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'? 

780 

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) 

791 

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) 

799 

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 

829 

830 

831PREFIX_END = r'[^0-9@#.,]' 

832NUMBER_TOKEN = r'[0-9@#.,E+]' 

833 

834PREFIX_PATTERN = r"(?P<prefix>(?:'[^']*'|%s)*)" % PREFIX_END 

835NUMBER_PATTERN = r"(?P<number>%s*)" % NUMBER_TOKEN 

836SUFFIX_PATTERN = r"(?P<suffix>.*)" 

837 

838number_re = re.compile(r"%s%s%s" % (PREFIX_PATTERN, NUMBER_PATTERN, 

839 SUFFIX_PATTERN)) 

840 

841 

842def parse_grouping(p): 

843 """Parse primary and secondary digit grouping 

844 

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 

862 

863 

864def parse_pattern(pattern): 

865 """Parse number format patterns""" 

866 if isinstance(pattern, NumberPattern): 

867 return pattern 

868 

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() 

874 

875 pos_pattern = pattern 

876 

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 = '' 

899 

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 

914 

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) 

929 

930 

931class NumberPattern: 

932 

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() 

945 

946 def __repr__(self): 

947 return '<%s %r>' % (type(self).__name__, self.pattern) 

948 

949 def compute_scale(self): 

950 """Return the scaling factor to apply to the number before rendering. 

951 

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 

962 

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 

970 

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) 

977 

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) 

984 

985 # Normalize exponent value now that we have the sign. 

986 exp = abs(exp) 

987 

988 return value, exp, exp_sign 

989 

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. 

1001 

1002 Forced decimal quantization is active by default so we'll produce a 

1003 number string that is strictly following CLDR pattern definitions. 

1004 

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)) 

1027 

1028 value = value.scaleb(self.scale) 

1029 

1030 # Separate the absolute value from its sign. 

1031 is_negative = int(value.is_signed()) 

1032 value = abs(value).normalize() 

1033 

1034 # Prepare scientific notation metadata. 

1035 if self.exp_prec: 

1036 value, exp, exp_sign = self.scientific_notation_elements(value, locale) 

1037 

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 

1048 

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)])) 

1057 

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)]) 

1066 

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 

1076 

1077 # A normal number pattern. 

1078 else: 

1079 number = self._quantize_value(value, locale, frac_prec, group_separator) 

1080 

1081 retval = ''.join([ 

1082 self.prefix[is_negative], 

1083 number, 

1084 self.suffix[is_negative]]) 

1085 

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)) 

1091 

1092 return retval 

1093 

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 

1132 

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 

1145 

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 

1155 

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