diff babel/numbers.py @ 9:3be73c6f01f1

Add basic support for number format patterns.
author jonas
date Wed, 30 May 2007 21:30:14 +0000
parents f71ca60f2a4a
children 7d37639a7411
line wrap: on
line diff
--- a/babel/numbers.py
+++ b/babel/numbers.py
@@ -56,7 +56,7 @@
     :return: the group symbol
     :rtype: `unicode`
     """
-    return Locale.parse(locale).number_symbols.get('group', u'.')
+    return Locale.parse(locale).number_symbols.get('group', u',')
 
 def format_number(number, locale=LC_NUMERIC):
     """Returns the given number formatted for a specific locale.
@@ -69,45 +69,42 @@
     :return: the formatted number
     :rtype: `unicode`
     """
-    group = get_group_symbol(locale)
-    if not group:
-        return unicode(number)
-    thou = re.compile(r'([0-9])([0-9][0-9][0-9]([%s]|$))' % group).search
-    v = str(number)
-    mo = thou(v)
-    while mo is not None:
-        l = mo.start(0)
-        v = v[:l+1] + group + v[l+1:]
-        mo = thou(v)
-    return unicode(v)
+    # Do we really need this one?
+    return format_decimal(number, locale=locale)
 
-def format_decimal(number, places=2, locale=LC_NUMERIC):
+def format_decimal(number, format=None, locale=LC_NUMERIC):
     """Returns the given decimal number formatted for a specific locale.
     
-    >>> format_decimal(1099.98, locale='en_US')
-    u'1,099.98'
-    
+    >>> format_decimal(1, locale='en_US')
+    u'1'
+    >>> format_decimal(1.2345, locale='en_US')
+    u'1.234'
+    >>> format_decimal(1.2345, locale='sv_SE')
+    u'1,234'
+    >>> format_decimal(12345, locale='de_DE')
+    u'12.345'
+    >>> format_decimal(-1.2345, format='#,##0.##;-#', locale='sv_SE')
+    u'-1,23'
+    >>> format_decimal(-1.2345, format='#,##0.##;(#)', locale='sv_SE')
+    u'(1,23)'
+
     The appropriate thousands grouping and the decimal separator are used for
     each locale:
     
-    >>> format_decimal(1099.98, locale='de_DE')
-    u'1.099,98'
-    
-    The number of decimal places defaults to 2, but can also be specified
-    explicitly:
-    
-    >>> format_decimal(1099.98, places=4, locale='en_US')
-    u'1,099.9800'
-    
+    >>> format_decimal(12345, locale='en_US')
+    u'12,345'
+
     :param number: the number to format
-    :param places: the number of digit behind the decimal point
+    :param format: 
     :param locale: the `Locale` object or locale identifier
     :return: the formatted decimal number
     :rtype: `unicode`
     """
     locale = Locale.parse(locale)
-    a, b = (('%%.%df' % places) % number).split('.')
-    return unicode(format_number(a, locale) + get_decimal_symbol(locale) + b)
+    pattern = locale.decimal_formats.get(format)
+    if not pattern:
+        pattern = parse_pattern(format)
+    return pattern.apply(number, locale)
 
 def format_currency(value, locale=LC_NUMERIC):
     """Returns formatted currency value.
@@ -120,7 +117,7 @@
     :return: the formatted currency value
     :rtype: `unicode`
     """
-    return format_decimal(value, places=2, locale=locale)
+    return format_decimal(value, locale=locale)
 
 def format_percent(value, places=2, locale=LC_NUMERIC):
     raise NotImplementedError
@@ -162,3 +159,126 @@
     string = string.replace(get_group_symbol(locale), '') \
                    .replace(get_decimal_symbol(locale), '.')
     return float(string)
+
+
+PREFIX_END = r'[^0-9@#.,]'
+NUMBER_TOKEN = r'[0-9@#.\-,E]'
+
+PREFIX_PATTERN = r"(?P<prefix>(?:'[^']*'|%s)*)" % PREFIX_END
+NUMBER_PATTERN = r"(?P<number>%s+)" % NUMBER_TOKEN
+SUFFIX_PATTERN = r"(?P<suffix>.*)"
+
+number_re = re.compile(r"%s%s%s" % (PREFIX_PATTERN, NUMBER_PATTERN, 
+                                    SUFFIX_PATTERN))
+
+# TODO:
+#  Filling
+#  Rounding
+#  Scientific notation
+#  Significant Digits
+def parse_pattern(pattern):
+    """Parse number format patterns"""
+    if isinstance(pattern, NumberPattern):
+        return pattern
+
+    # Do we have a negative subpattern?
+    if ';' in pattern:
+        pattern, neg_pattern = pattern.split(';', 1)
+        pos_prefix, number, pos_suffix = number_re.search(pattern).groups()
+        neg_prefix, _, neg_suffix = number_re.search(neg_pattern).groups()
+    else:
+        pos_prefix, number, pos_suffix = number_re.search(pattern).groups()
+        neg_prefix = '-' + pos_prefix
+        neg_suffix = pos_suffix
+    integer, fraction = number.rsplit('.', 1)
+    min_frac = max_frac = 0
+
+    def parse_precision(p):
+        """Calculate the min and max allowed digits"""
+        min = max = 0
+        for c in p:
+            if c == '0':
+                min += 1
+                max += 1
+            elif c == '#':
+                max += 1
+            else:
+                break
+        return min, max
+
+    def parse_grouping(p):
+        """Parse primary and secondary digit grouping
+
+        >>> parse_grouping('##')
+        0, 0
+        >>> parse_grouping('#,###')
+        3, 3
+        >>> parse_grouping('#,####,###')
+        3, 4
+        """
+        width = len(p)
+        g1 = p.rfind(',')
+        if g1 == -1:
+            return 1000, 1000
+        g1 = width - g1 - 1
+        g2 = p[:-g1 - 1].rfind(',')
+        if g2 == -1:
+            return g1, g1
+        g2 = width - g1 - g2 - 2
+        return g1, g2
+
+    int_precision = parse_precision(integer)
+    frac_precision = parse_precision(fraction)
+    grouping = parse_grouping(integer)
+    int_precision = (int_precision[0], 1000) # Unlimited
+    return NumberPattern(pattern, (pos_prefix, neg_prefix), 
+                         (pos_suffix, neg_suffix), grouping,
+                         int_precision, frac_precision)
+
+
+class NumberPattern(object):
+    def __init__(self, pattern, prefix, suffix, grouping,
+                 int_precision, frac_precision):
+        self.pattern = pattern
+        self.prefix = prefix
+        self.suffix = suffix
+        self.grouping = grouping
+        self.int_precision = int_precision
+        self.frac_precision = frac_precision
+
+    def __repr__(self):
+        return '<%s %r>' % (type(self).__name__, self.pattern)
+
+    def apply(self, value, locale):
+        negative = int(value < 0)
+        a, b = str(value * 1.0).split('.')
+        a = a.lstrip('-')
+        return '%s%s%s%s' % (self.prefix[negative], 
+                             self._format_int(a, locale), 
+                             self._format_frac(b, locale),
+                             self.suffix[negative])
+
+    def _format_int(self, value, locale):
+        min, max = self.int_precision
+        width = len(value)
+        if width < min:
+            value += '0' * (min - width)
+        gsize = self.grouping[0]
+        ret = ''
+        symbol = get_group_symbol(locale)
+        while len(value) > gsize:
+            ret = symbol + value[-gsize:] + ret
+            value = value[:-gsize]
+            gsize = self.grouping[1]
+        return value + ret
+
+    def _format_frac(self, value, locale):
+        min, max = self.frac_precision
+        if min == 0 and int(value) == 0:
+            return ''
+        width = len(value)
+        if width < min:
+            value += '0' * (min - width)
+        if width > max:
+            value = value[:max] # FIXME: Rounding?!?
+        return get_decimal_symbol(locale) + value
Copyright (C) 2012-2017 Edgewall Software