Mercurial > babel > old > mirror
annotate babel/numbers.py @ 387:88e3589ca8df
Improve CLDR import of currency-related data to ignore unsupported features such as symbol choice patterns and pluralized display names. See #93.
author | cmlenz |
---|---|
date | Fri, 11 Jul 2008 13:47:28 +0000 |
parents | 841858d5b567 |
children | f03cc3bed4e1 |
rev | line source |
---|---|
3 | 1 # -*- coding: utf-8 -*- |
2 # | |
3 # Copyright (C) 2007 Edgewall Software | |
4 # All rights reserved. | |
5 # | |
247 | 6 # This software is licensed as described in the file COPYING, which |
3 | 7 # you should have received as part of this distribution. The terms |
8 # are also available at http://babel.edgewall.org/wiki/License. | |
9 # | |
10 # This software consists of voluntary contributions made by many | |
11 # individuals. For the exact contribution history, see the revision | |
12 # history and logs, available at http://babel.edgewall.org/log/. | |
13 | |
14 """Locale dependent formatting and parsing of numeric data. | |
15 | |
16 The default locale for the functions in this module is determined by the | |
17 following environment variables, in that order: | |
18 | |
19 * ``LC_NUMERIC``, | |
20 * ``LC_ALL``, and | |
21 * ``LANG`` | |
22 """ | |
248 | 23 # TODO: |
24 # Padding and rounding increments in pattern: | |
25 # - http://www.unicode.org/reports/tr35/ (Appendix G.6) | |
245
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
26 import math |
3 | 27 import re |
220 | 28 try: |
29 from decimal import Decimal | |
30 have_decimal = True | |
31 except ImportError: | |
32 have_decimal = False | |
3 | 33 |
74
d9c34d2f3d1d
More explicit module-level function names in `babel.core`. Added `Locale.negotiate` class method.
cmlenz
parents:
52
diff
changeset
|
34 from babel.core import default_locale, Locale |
3 | 35 |
36 __all__ = ['format_number', 'format_decimal', 'format_currency', | |
37 'format_percent', 'format_scientific', 'parse_number', | |
34 | 38 'parse_decimal', 'NumberFormatError'] |
3 | 39 __docformat__ = 'restructuredtext en' |
40 | |
74
d9c34d2f3d1d
More explicit module-level function names in `babel.core`. Added `Locale.negotiate` class method.
cmlenz
parents:
52
diff
changeset
|
41 LC_NUMERIC = default_locale('LC_NUMERIC') |
3 | 42 |
387
88e3589ca8df
Improve CLDR import of currency-related data to ignore unsupported features such as symbol choice patterns and pluralized display names. See #93.
cmlenz
parents:
377
diff
changeset
|
43 def get_currency_name(currency, locale=LC_NUMERIC): |
88e3589ca8df
Improve CLDR import of currency-related data to ignore unsupported features such as symbol choice patterns and pluralized display names. See #93.
cmlenz
parents:
377
diff
changeset
|
44 """Return the name used by the locale for the specified currency. |
88e3589ca8df
Improve CLDR import of currency-related data to ignore unsupported features such as symbol choice patterns and pluralized display names. See #93.
cmlenz
parents:
377
diff
changeset
|
45 |
88e3589ca8df
Improve CLDR import of currency-related data to ignore unsupported features such as symbol choice patterns and pluralized display names. See #93.
cmlenz
parents:
377
diff
changeset
|
46 >>> get_currency_name('USD', 'en_US') |
88e3589ca8df
Improve CLDR import of currency-related data to ignore unsupported features such as symbol choice patterns and pluralized display names. See #93.
cmlenz
parents:
377
diff
changeset
|
47 u'US Dollar' |
88e3589ca8df
Improve CLDR import of currency-related data to ignore unsupported features such as symbol choice patterns and pluralized display names. See #93.
cmlenz
parents:
377
diff
changeset
|
48 |
88e3589ca8df
Improve CLDR import of currency-related data to ignore unsupported features such as symbol choice patterns and pluralized display names. See #93.
cmlenz
parents:
377
diff
changeset
|
49 :param currency: the currency code |
88e3589ca8df
Improve CLDR import of currency-related data to ignore unsupported features such as symbol choice patterns and pluralized display names. See #93.
cmlenz
parents:
377
diff
changeset
|
50 :param locale: the `Locale` object or locale identifier |
88e3589ca8df
Improve CLDR import of currency-related data to ignore unsupported features such as symbol choice patterns and pluralized display names. See #93.
cmlenz
parents:
377
diff
changeset
|
51 :return: the currency symbol |
88e3589ca8df
Improve CLDR import of currency-related data to ignore unsupported features such as symbol choice patterns and pluralized display names. See #93.
cmlenz
parents:
377
diff
changeset
|
52 :rtype: `unicode` |
88e3589ca8df
Improve CLDR import of currency-related data to ignore unsupported features such as symbol choice patterns and pluralized display names. See #93.
cmlenz
parents:
377
diff
changeset
|
53 :since: version 0.9.4 |
88e3589ca8df
Improve CLDR import of currency-related data to ignore unsupported features such as symbol choice patterns and pluralized display names. See #93.
cmlenz
parents:
377
diff
changeset
|
54 """ |
88e3589ca8df
Improve CLDR import of currency-related data to ignore unsupported features such as symbol choice patterns and pluralized display names. See #93.
cmlenz
parents:
377
diff
changeset
|
55 return Locale.parse(locale).currencies.get(currency, currency) |
88e3589ca8df
Improve CLDR import of currency-related data to ignore unsupported features such as symbol choice patterns and pluralized display names. See #93.
cmlenz
parents:
377
diff
changeset
|
56 |
127 | 57 def get_currency_symbol(currency, locale=LC_NUMERIC): |
58 """Return the symbol used by the locale for the specified currency. | |
59 | |
60 >>> get_currency_symbol('USD', 'en_US') | |
61 u'$' | |
62 | |
63 :param currency: the currency code | |
64 :param locale: the `Locale` object or locale identifier | |
65 :return: the currency symbol | |
66 :rtype: `unicode` | |
67 """ | |
68 return Locale.parse(locale).currency_symbols.get(currency, currency) | |
69 | |
3 | 70 def get_decimal_symbol(locale=LC_NUMERIC): |
71 """Return the symbol used by the locale to separate decimal fractions. | |
72 | |
73 >>> get_decimal_symbol('en_US') | |
74 u'.' | |
75 | |
76 :param locale: the `Locale` object or locale identifier | |
77 :return: the decimal symbol | |
78 :rtype: `unicode` | |
79 """ | |
80 return Locale.parse(locale).number_symbols.get('decimal', u'.') | |
81 | |
246
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
82 def get_plus_sign_symbol(locale=LC_NUMERIC): |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
83 """Return the plus sign symbol used by the current locale. |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
84 |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
85 >>> get_plus_sign_symbol('en_US') |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
86 u'+' |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
87 |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
88 :param locale: the `Locale` object or locale identifier |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
89 :return: the plus sign symbol |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
90 :rtype: `unicode` |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
91 """ |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
92 return Locale.parse(locale).number_symbols.get('plusSign', u'+') |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
93 |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
94 def get_minus_sign_symbol(locale=LC_NUMERIC): |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
95 """Return the plus sign symbol used by the current locale. |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
96 |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
97 >>> get_minus_sign_symbol('en_US') |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
98 u'-' |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
99 |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
100 :param locale: the `Locale` object or locale identifier |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
101 :return: the plus sign symbol |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
102 :rtype: `unicode` |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
103 """ |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
104 return Locale.parse(locale).number_symbols.get('minusSign', u'-') |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
105 |
245
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
106 def get_exponential_symbol(locale=LC_NUMERIC): |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
107 """Return the symbol used by the locale to separate mantissa and exponent. |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
108 |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
109 >>> get_exponential_symbol('en_US') |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
110 u'E' |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
111 |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
112 :param locale: the `Locale` object or locale identifier |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
113 :return: the exponential symbol |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
114 :rtype: `unicode` |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
115 """ |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
116 return Locale.parse(locale).number_symbols.get('exponential', u'E') |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
117 |
3 | 118 def get_group_symbol(locale=LC_NUMERIC): |
119 """Return the symbol used by the locale to separate groups of thousands. | |
120 | |
121 >>> get_group_symbol('en_US') | |
122 u',' | |
123 | |
124 :param locale: the `Locale` object or locale identifier | |
125 :return: the group symbol | |
126 :rtype: `unicode` | |
127 """ | |
11 | 128 return Locale.parse(locale).number_symbols.get('group', u',') |
3 | 129 |
130 def format_number(number, locale=LC_NUMERIC): | |
103
1ba215a5774d
Add wrapper class bundling the various formatting functions bound to a specific locale and time-zone.
cmlenz
parents:
74
diff
changeset
|
131 """Return the given number formatted for a specific locale. |
3 | 132 |
133 >>> format_number(1099, locale='en_US') | |
134 u'1,099' | |
135 | |
136 :param number: the number to format | |
137 :param locale: the `Locale` object or locale identifier | |
138 :return: the formatted number | |
139 :rtype: `unicode` | |
140 """ | |
11 | 141 # Do we really need this one? |
142 return format_decimal(number, locale=locale) | |
3 | 143 |
11 | 144 def format_decimal(number, format=None, locale=LC_NUMERIC): |
103
1ba215a5774d
Add wrapper class bundling the various formatting functions bound to a specific locale and time-zone.
cmlenz
parents:
74
diff
changeset
|
145 """Return the given decimal number formatted for a specific locale. |
3 | 146 |
11 | 147 >>> format_decimal(1.2345, locale='en_US') |
148 u'1.234' | |
52 | 149 >>> format_decimal(1.2346, locale='en_US') |
150 u'1.235' | |
151 >>> format_decimal(-1.2346, locale='en_US') | |
152 u'-1.235' | |
11 | 153 >>> format_decimal(1.2345, locale='sv_SE') |
154 u'1,234' | |
52 | 155 >>> format_decimal(12345, locale='de') |
11 | 156 u'12.345' |
157 | |
3 | 158 The appropriate thousands grouping and the decimal separator are used for |
159 each locale: | |
160 | |
127 | 161 >>> format_decimal(12345.5, locale='en_US') |
162 u'12,345.5' | |
11 | 163 |
3 | 164 :param number: the number to format |
11 | 165 :param format: |
3 | 166 :param locale: the `Locale` object or locale identifier |
167 :return: the formatted decimal number | |
168 :rtype: `unicode` | |
169 """ | |
170 locale = Locale.parse(locale) | |
127 | 171 if not format: |
172 format = locale.decimal_formats.get(format) | |
173 pattern = parse_pattern(format) | |
11 | 174 return pattern.apply(number, locale) |
3 | 175 |
127 | 176 def format_currency(number, currency, format=None, locale=LC_NUMERIC): |
135 | 177 u"""Return formatted currency value. |
3 | 178 |
34 | 179 >>> format_currency(1099.98, 'USD', locale='en_US') |
127 | 180 u'$1,099.98' |
181 >>> format_currency(1099.98, 'USD', locale='es_CO') | |
377
841858d5b567
Implement support for aliases in the CLDR data. Closes #68. Also, update to CLDR 1.6, and a much improved `dump_data` script.
cmlenz
parents:
359
diff
changeset
|
182 u'US$\\xa01.099,98' |
127 | 183 >>> format_currency(1099.98, 'EUR', locale='de_DE') |
377
841858d5b567
Implement support for aliases in the CLDR data. Closes #68. Also, update to CLDR 1.6, and a much improved `dump_data` script.
cmlenz
parents:
359
diff
changeset
|
184 u'1.099,98\\xa0\\u20ac' |
127 | 185 |
186 The pattern can also be specified explicitly: | |
187 | |
188 >>> format_currency(1099.98, 'EUR', u'\xa4\xa4 #,##0.00', locale='en_US') | |
189 u'EUR 1,099.98' | |
3 | 190 |
28
695884591af6
* Reduce size of locale data pickles by only storing the data provided by each locale itself, and merging inherited data at runtime.
cmlenz
parents:
24
diff
changeset
|
191 :param number: the number to format |
34 | 192 :param currency: the currency code |
3 | 193 :param locale: the `Locale` object or locale identifier |
194 :return: the formatted currency value | |
195 :rtype: `unicode` | |
196 """ | |
127 | 197 locale = Locale.parse(locale) |
198 if not format: | |
199 format = locale.currency_formats.get(format) | |
200 pattern = parse_pattern(format) | |
201 return pattern.apply(number, locale, currency=currency) | |
3 | 202 |
28
695884591af6
* Reduce size of locale data pickles by only storing the data provided by each locale itself, and merging inherited data at runtime.
cmlenz
parents:
24
diff
changeset
|
203 def format_percent(number, format=None, locale=LC_NUMERIC): |
103
1ba215a5774d
Add wrapper class bundling the various formatting functions bound to a specific locale and time-zone.
cmlenz
parents:
74
diff
changeset
|
204 """Return formatted percent value for a specific locale. |
24 | 205 |
206 >>> format_percent(0.34, locale='en_US') | |
207 u'34%' | |
208 >>> format_percent(25.1234, locale='en_US') | |
209 u'2,512%' | |
210 >>> format_percent(25.1234, locale='sv_SE') | |
235
d0cd235ede46
Upgraded to CLDR 1.5 and improved timezone formatting.
cmlenz
parents:
220
diff
changeset
|
211 u'2\\xa0512\\xa0%' |
24 | 212 |
128 | 213 The format pattern can also be specified explicitly: |
214 | |
215 >>> format_percent(25.1234, u'#,##0\u2030', locale='en_US') | |
216 u'25,123\u2030' | |
217 | |
24 | 218 :param number: the percent number to format |
219 :param format: | |
220 :param locale: the `Locale` object or locale identifier | |
221 :return: the formatted percent number | |
222 :rtype: `unicode` | |
223 """ | |
224 locale = Locale.parse(locale) | |
127 | 225 if not format: |
226 format = locale.percent_formats.get(format) | |
227 pattern = parse_pattern(format) | |
28
695884591af6
* Reduce size of locale data pickles by only storing the data provided by each locale itself, and merging inherited data at runtime.
cmlenz
parents:
24
diff
changeset
|
228 return pattern.apply(number, locale) |
3 | 229 |
245
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
230 def format_scientific(number, format=None, locale=LC_NUMERIC): |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
231 """Return value formatted in scientific notation for a specific locale. |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
232 |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
233 >>> format_scientific(10000, locale='en_US') |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
234 u'1E4' |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
235 |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
236 The format pattern can also be specified explicitly: |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
237 |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
238 >>> format_scientific(1234567, u'##0E00', locale='en_US') |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
239 u'1.23E06' |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
240 |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
241 :param number: the number to format |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
242 :param format: |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
243 :param locale: the `Locale` object or locale identifier |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
244 :return: value formatted in scientific notation. |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
245 :rtype: `unicode` |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
246 """ |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
247 locale = Locale.parse(locale) |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
248 if not format: |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
249 format = locale.scientific_formats.get(format) |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
250 pattern = parse_pattern(format) |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
251 return pattern.apply(number, locale) |
3 | 252 |
34 | 253 |
254 class NumberFormatError(ValueError): | |
255 """Exception raised when a string cannot be parsed into a number.""" | |
256 | |
257 | |
3 | 258 def parse_number(string, locale=LC_NUMERIC): |
259 """Parse localized number string into a long integer. | |
260 | |
261 >>> parse_number('1,099', locale='en_US') | |
262 1099L | |
263 >>> parse_number('1.099', locale='de_DE') | |
264 1099L | |
265 | |
34 | 266 When the given string cannot be parsed, an exception is raised: |
267 | |
268 >>> parse_number('1.099,98', locale='de') | |
269 Traceback (most recent call last): | |
270 ... | |
271 NumberFormatError: '1.099,98' is not a valid number | |
272 | |
3 | 273 :param string: the string to parse |
274 :param locale: the `Locale` object or locale identifier | |
275 :return: the parsed number | |
276 :rtype: `long` | |
34 | 277 :raise `NumberFormatError`: if the string can not be converted to a number |
3 | 278 """ |
34 | 279 try: |
280 return long(string.replace(get_group_symbol(locale), '')) | |
281 except ValueError: | |
282 raise NumberFormatError('%r is not a valid number' % string) | |
3 | 283 |
284 def parse_decimal(string, locale=LC_NUMERIC): | |
285 """Parse localized decimal string into a float. | |
286 | |
287 >>> parse_decimal('1,099.98', locale='en_US') | |
288 1099.98 | |
34 | 289 >>> parse_decimal('1.099,98', locale='de') |
3 | 290 1099.98 |
291 | |
34 | 292 When the given string cannot be parsed, an exception is raised: |
293 | |
294 >>> parse_decimal('2,109,998', locale='de') | |
295 Traceback (most recent call last): | |
296 ... | |
297 NumberFormatError: '2,109,998' is not a valid decimal number | |
298 | |
3 | 299 :param string: the string to parse |
300 :param locale: the `Locale` object or locale identifier | |
301 :return: the parsed decimal number | |
302 :rtype: `float` | |
34 | 303 :raise `NumberFormatError`: if the string can not be converted to a |
304 decimal number | |
3 | 305 """ |
306 locale = Locale.parse(locale) | |
34 | 307 try: |
308 return float(string.replace(get_group_symbol(locale), '') | |
309 .replace(get_decimal_symbol(locale), '.')) | |
310 except ValueError: | |
311 raise NumberFormatError('%r is not a valid decimal number' % string) | |
11 | 312 |
313 | |
314 PREFIX_END = r'[^0-9@#.,]' | |
245
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
315 NUMBER_TOKEN = r'[0-9@#.\-,E+]' |
11 | 316 |
317 PREFIX_PATTERN = r"(?P<prefix>(?:'[^']*'|%s)*)" % PREFIX_END | |
318 NUMBER_PATTERN = r"(?P<number>%s+)" % NUMBER_TOKEN | |
319 SUFFIX_PATTERN = r"(?P<suffix>.*)" | |
320 | |
129 | 321 number_re = re.compile(r"%s%s%s" % (PREFIX_PATTERN, NUMBER_PATTERN, |
11 | 322 SUFFIX_PATTERN)) |
323 | |
220 | 324 def split_number(value): |
325 """Convert a number into a (intasstring, fractionasstring) tuple""" | |
326 if have_decimal and isinstance(value, Decimal): | |
327 text = str(value) | |
328 else: | |
329 text = ('%.9f' % value).rstrip('0') | |
330 if '.' in text: | |
331 a, b = text.split('.', 1) | |
332 if b == '0': | |
333 b = '' | |
334 else: | |
335 a, b = text, '' | |
336 return a, b | |
337 | |
214 | 338 def bankersround(value, ndigits=0): |
339 """Round a number to a given precision. | |
340 | |
341 Works like round() except that the round-half-even (banker's rounding) | |
342 algorithm is used instead of round-half-up. | |
343 | |
344 >>> bankersround(5.5, 0) | |
345 6.0 | |
346 >>> bankersround(6.5, 0) | |
347 6.0 | |
348 >>> bankersround(-6.5, 0) | |
349 -6.0 | |
220 | 350 >>> bankersround(1234.0, -2) |
214 | 351 1200.0 |
352 """ | |
353 sign = int(value < 0) and -1 or 1 | |
354 value = abs(value) | |
220 | 355 a, b = split_number(value) |
356 digits = a + b | |
214 | 357 add = 0 |
358 i = len(a) + ndigits | |
359 if i < 0 or i >= len(digits): | |
360 pass | |
361 elif digits[i] > '5': | |
362 add = 1 | |
363 elif digits[i] == '5' and digits[i-1] in '13579': | |
364 add = 1 | |
220 | 365 scale = 10**ndigits |
366 if have_decimal and isinstance(value, Decimal): | |
367 return Decimal(int(value * scale + add)) / scale * sign | |
368 else: | |
369 return float(int(value * scale + add)) / scale * sign | |
214 | 370 |
11 | 371 def parse_pattern(pattern): |
372 """Parse number format patterns""" | |
373 if isinstance(pattern, NumberPattern): | |
374 return pattern | |
375 | |
376 # Do we have a negative subpattern? | |
377 if ';' in pattern: | |
378 pattern, neg_pattern = pattern.split(';', 1) | |
379 pos_prefix, number, pos_suffix = number_re.search(pattern).groups() | |
380 neg_prefix, _, neg_suffix = number_re.search(neg_pattern).groups() | |
381 else: | |
382 pos_prefix, number, pos_suffix = number_re.search(pattern).groups() | |
383 neg_prefix = '-' + pos_prefix | |
384 neg_suffix = pos_suffix | |
245
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
385 if 'E' in number: |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
386 number, exp = number.split('E', 1) |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
387 else: |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
388 exp = None |
213
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
389 if '@' in number: |
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
390 if '.' in number and '0' in number: |
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
391 raise ValueError('Significant digit patterns can not contain ' |
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
392 '"@" or "0"') |
24 | 393 if '.' in number: |
394 integer, fraction = number.rsplit('.', 1) | |
395 else: | |
396 integer = number | |
397 fraction = '' | |
11 | 398 min_frac = max_frac = 0 |
399 | |
400 def parse_precision(p): | |
401 """Calculate the min and max allowed digits""" | |
402 min = max = 0 | |
403 for c in p: | |
213
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
404 if c in '@0': |
11 | 405 min += 1 |
406 max += 1 | |
407 elif c == '#': | |
408 max += 1 | |
213
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
409 elif c == ',': |
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
410 continue |
11 | 411 else: |
412 break | |
413 return min, max | |
414 | |
415 def parse_grouping(p): | |
416 """Parse primary and secondary digit grouping | |
417 | |
418 >>> parse_grouping('##') | |
419 0, 0 | |
420 >>> parse_grouping('#,###') | |
421 3, 3 | |
422 >>> parse_grouping('#,####,###') | |
423 3, 4 | |
424 """ | |
425 width = len(p) | |
426 g1 = p.rfind(',') | |
427 if g1 == -1: | |
428 return 1000, 1000 | |
429 g1 = width - g1 - 1 | |
430 g2 = p[:-g1 - 1].rfind(',') | |
431 if g2 == -1: | |
432 return g1, g1 | |
433 g2 = width - g1 - g2 - 2 | |
434 return g1, g2 | |
435 | |
246
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
436 int_prec = parse_precision(integer) |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
437 frac_prec = parse_precision(fraction) |
245
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
438 if exp: |
246
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
439 frac_prec = parse_precision(integer+fraction) |
245
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
440 exp_plus = exp.startswith('+') |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
441 exp = exp.lstrip('+') |
246
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
442 exp_prec = parse_precision(exp) |
245
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
443 else: |
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
444 exp_plus = None |
246
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
445 exp_prec = None |
11 | 446 grouping = parse_grouping(integer) |
447 return NumberPattern(pattern, (pos_prefix, neg_prefix), | |
448 (pos_suffix, neg_suffix), grouping, | |
246
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
449 int_prec, frac_prec, |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
450 exp_prec, exp_plus) |
11 | 451 |
452 | |
453 class NumberPattern(object): | |
24 | 454 |
11 | 455 def __init__(self, pattern, prefix, suffix, grouping, |
246
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
456 int_prec, frac_prec, exp_prec, exp_plus): |
11 | 457 self.pattern = pattern |
458 self.prefix = prefix | |
459 self.suffix = suffix | |
460 self.grouping = grouping | |
246
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
461 self.int_prec = int_prec |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
462 self.frac_prec = frac_prec |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
463 self.exp_prec = exp_prec |
245
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
464 self.exp_plus = exp_plus |
24 | 465 if '%' in ''.join(self.prefix + self.suffix): |
220 | 466 self.scale = 100 |
24 | 467 elif u'‰' in ''.join(self.prefix + self.suffix): |
220 | 468 self.scale = 1000 |
24 | 469 else: |
220 | 470 self.scale = 1 |
11 | 471 |
472 def __repr__(self): | |
473 return '<%s %r>' % (type(self).__name__, self.pattern) | |
474 | |
127 | 475 def apply(self, value, locale, currency=None): |
24 | 476 value *= self.scale |
246
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
477 is_negative = int(value < 0) |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
478 if self.exp_prec: # Scientific notation |
245
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
479 value = abs(value) |
359 | 480 if value: |
481 exp = int(math.floor(math.log(value, 10))) | |
482 else: | |
483 exp = 0 | |
245
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
484 # Minimum number of integer digits |
246
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
485 if self.int_prec[0] == self.int_prec[1]: |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
486 exp -= self.int_prec[0] - 1 |
245
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
487 # Exponent grouping |
246
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
488 elif self.int_prec[1]: |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
489 exp = int(exp) / self.int_prec[1] * self.int_prec[1] |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
490 if not have_decimal or not isinstance(value, Decimal): |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
491 value = float(value) |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
492 if exp < 0: |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
493 value = value * 10**(-exp) |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
494 else: |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
495 value = value / 10**exp |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
496 exp_sign = '' |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
497 if exp < 0: |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
498 exp_sign = get_minus_sign_symbol(locale) |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
499 elif self.exp_plus: |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
500 exp_sign = get_plus_sign_symbol(locale) |
245
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
501 exp = abs(exp) |
246
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
502 number = u'%s%s%s%s' % \ |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
503 (self._format_sigdig(value, self.frac_prec[0], |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
504 self.frac_prec[1]), |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
505 get_exponential_symbol(locale), exp_sign, |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
506 self._format_int(str(exp), self.exp_prec[0], |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
507 self.exp_prec[1], locale)) |
245
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
508 elif '@' in self.pattern: # Is it a siginificant digits pattern? |
213
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
509 text = self._format_sigdig(abs(value), |
246
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
510 self.int_prec[0], |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
511 self.int_prec[1]) |
213
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
512 if '.' in text: |
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
513 a, b = text.split('.') |
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
514 a = self._format_int(a, 0, 1000, locale) |
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
515 if b: |
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
516 b = get_decimal_symbol(locale) + b |
246
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
517 number = a + b |
213
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
518 else: |
246
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
519 number = self._format_int(text, 0, 1000, locale) |
213
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
520 else: # A normal number pattern |
220 | 521 a, b = split_number(bankersround(abs(value), |
246
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
522 self.frac_prec[1])) |
220 | 523 b = b or '0' |
246
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
524 a = self._format_int(a, self.int_prec[0], |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
525 self.int_prec[1], locale) |
213
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
526 b = self._format_frac(b, locale) |
246
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
527 number = a + b |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
528 retval = u'%s%s%s' % (self.prefix[is_negative], number, |
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
529 self.suffix[is_negative]) |
127 | 530 if u'¤' in retval: |
531 retval = retval.replace(u'¤¤', currency.upper()) | |
532 retval = retval.replace(u'¤', get_currency_symbol(currency, locale)) | |
533 return retval | |
11 | 534 |
213
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
535 def _format_sigdig(self, value, min, max): |
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
536 """Convert value to a string. |
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
537 |
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
538 The resulting string will contain between (min, max) number of |
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
539 significant digits. |
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
540 """ |
220 | 541 a, b = split_number(value) |
213
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
542 ndecimals = len(a) |
220 | 543 if a == '0' and b != '': |
213
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
544 ndecimals = 0 |
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
545 while b.startswith('0'): |
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
546 b = b[1:] |
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
547 ndecimals -= 1 |
220 | 548 a, b = split_number(bankersround(value, max - ndecimals)) |
549 digits = len((a + b).lstrip('0')) | |
550 if not digits: | |
213
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
551 digits = 1 |
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
552 # Figure out if we need to add any trailing '0':s |
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
553 if len(a) >= max and a != '0': |
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
554 return a |
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
555 if digits < min: |
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
556 b += ('0' * (min - digits)) |
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
557 if b: |
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
558 return '%s.%s' % (a, b) |
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
559 return a |
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
560 |
b13f3bf4c208
Added support for siginificant digits in number patterns.
jonas
parents:
139
diff
changeset
|
561 def _format_int(self, value, min, max, locale): |
11 | 562 width = len(value) |
563 if width < min: | |
245
d462423feeea
Added initial support for scientific notation patterns.
jonas
parents:
235
diff
changeset
|
564 value = '0' * (min - width) + value |
11 | 565 gsize = self.grouping[0] |
566 ret = '' | |
567 symbol = get_group_symbol(locale) | |
568 while len(value) > gsize: | |
569 ret = symbol + value[-gsize:] + ret | |
570 value = value[:-gsize] | |
571 gsize = self.grouping[1] | |
572 return value + ret | |
573 | |
574 def _format_frac(self, value, locale): | |
246
f21b6e6a13a7
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
245
diff
changeset
|
575 min, max = self.frac_prec |
220 | 576 if len(value) < min: |
577 value += ('0' * (min - len(value))) | |
24 | 578 if max == 0 or (min == 0 and int(value) == 0): |
11 | 579 return '' |
580 width = len(value) | |
52 | 581 while len(value) > min and value[-1] == '0': |
582 value = value[:-1] | |
11 | 583 return get_decimal_symbol(locale) + value |