diff options
author | Stephan Richter <stephan.richter@gmail.com> | 2017-05-23 11:32:25 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-05-23 11:32:25 -0400 |
commit | 87775ca4f7b75461ecc475f1f485115240104310 (patch) | |
tree | c2ee28b2b8769cb4314df328f3b496b6d130c381 | |
parent | 4355a23dc341b243aa09368f74602fc774b747db (diff) | |
parent | 63e752a934476377e9d7c01401f53d042e357725 (diff) | |
download | zope-i18n-87775ca4f7b75461ecc475f1f485115240104310.tar.gz |
Merge pull request #2 from Shoobx/master
Support formatting really small numbers and explicit rounding.
-rw-r--r-- | CHANGES.rst | 17 | ||||
-rw-r--r-- | setup.py | 4 | ||||
-rw-r--r-- | src/zope/i18n/format.py | 37 | ||||
-rw-r--r-- | src/zope/i18n/tests/locale3/en/LC_MESSAGES/zope-i18n.in | bin | 364 -> 315 bytes | |||
-rw-r--r-- | src/zope/i18n/tests/locale3/en/LC_MESSAGES/zope-i18n.mo | bin | 295 -> 315 bytes | |||
-rw-r--r-- | src/zope/i18n/tests/test_formats.py | 26 | ||||
-rw-r--r-- | tox.ini | 4 |
7 files changed, 74 insertions, 14 deletions
diff --git a/CHANGES.rst b/CHANGES.rst index 7162443..1f40674 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,12 +5,20 @@ CHANGES 4.2.0 (unreleased) ------------------ -- Better error message on PO-File Syntax Errors. - [SyZn] +- Better error message on PO-File Syntax Errors. [SyZn] -- Add support for Python 3.5. +- Add support for Python 3.5 and 3.6. -- Drop support for Python 2.6 and 3.2. +- Drop support for Python 2.6, 3.2 and 3.3. + +- Support for formatting really small numbers, e.g. 1e-9. These numbers needs + special treatment, because standard str(x) collapses them to scientific + representation. + +- Support for specifying rounding in NumberFormatter. This is required in some + cases, e.g. when you format a Decimal('0.9999') that sould not be rounded. + Currently, formatting Decimal('0.99999') will raise a TypeError if rounding + is not set to False 4.1.0 (2015-11-06) @@ -19,6 +27,7 @@ CHANGES - ``interpolate()`` now works recursively, if the mapping has a value which is a ``zope.i18nmessageid.Message`` itself. + 4.0.1 (2015-06-05) -------------------- @@ -63,9 +63,9 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Natural Language :: English', @@ -85,6 +85,7 @@ setup( ], extras_require=dict( test=[ + 'python-gettext', 'zope.component [zcml]', 'zope.configuration', 'zope.security', @@ -99,6 +100,7 @@ setup( ], ), tests_require = [ + 'python-gettext', 'zope.component [zcml]', 'zope.configuration', 'zope.security', diff --git a/src/zope/i18n/format.py b/src/zope/i18n/format.py index 7095ece..494d09c 100644 --- a/src/zope/i18n/format.py +++ b/src/zope/i18n/format.py @@ -31,6 +31,9 @@ from ._compat import _u PY3 = sys.version_info[0] == 3 if PY3: unicode = str + NATIVE_NUMBER_TYPES = (int, float) +else: + NATIVE_NUMBER_TYPES = (int, float, long) def roundHalfUp(n): """Works like round() in python2.x @@ -327,8 +330,11 @@ class NumberFormat(object): integer = self.symbols['nativeZeroDigit']*(min_size-size) + integer return integer - def _format_fraction(self, fraction, pattern): - max_precision = len(pattern) + def _format_fraction(self, fraction, pattern, rounding=True): + if rounding: + max_precision = len(pattern) + else: + max_precision = sys.maxsize min_precision = pattern.count('0') precision = len(fraction) roundInt = False @@ -356,7 +362,7 @@ class NumberFormat(object): fraction = self.symbols['decimal'] + fraction return fraction, roundInt - def format(self, obj, pattern=None): + def format(self, obj, pattern=None, rounding=True): "See zope.i18n.interfaces.IFormat" # Make or get binary form of datetime pattern if pattern is not None: @@ -369,9 +375,24 @@ class NumberFormat(object): else: bin_pattern = bin_pattern[1] + if isinstance(obj, NATIVE_NUMBER_TYPES): + # repr() handles high-precision numbers correctly in + # Python 2 and 3. str() is only correct in Python 3. + strobj = repr(obj) + else: + strobj = str(obj) + if 'e' in strobj: + # Str(obj) # returned scientific representation of a number (e.g. + # 1e-7). We can't rely on str() to format fraction. + decimalprec = len(bin_pattern[FRACTION]) or 1 + obj_int, obj_frac = ("%.*f" % (decimalprec, obj)).split('.') + # Remove trailing 0, but leave at least one + obj_frac = obj_frac.rstrip("0") or "0" + obj_int_frac = [obj_int, obj_frac] + else: + obj_int_frac = strobj.split('.') if bin_pattern[EXPONENTIAL] != '': - obj_int_frac = str(obj).split('.') # The exponential might have a mandatory sign; remove it from the # bin_pattern and remember the setting exp_bin_pattern = bin_pattern[EXPONENTIAL] @@ -400,7 +421,8 @@ class NumberFormat(object): number = ''.join(obj_int_frac) fraction, roundInt = self._format_fraction(number[1:], - bin_pattern[FRACTION]) + bin_pattern[FRACTION], + rounding=rounding) if roundInt: number = str(int(number[0]) + 1) + fraction else: @@ -415,10 +437,9 @@ class NumberFormat(object): number += self.symbols['exponential'] + exponent else: - obj_int_frac = str(obj).split('.') if len(obj_int_frac) > 1: - fraction, roundInt = self._format_fraction(obj_int_frac[1], - bin_pattern[FRACTION]) + fraction, roundInt = self._format_fraction( + obj_int_frac[1], bin_pattern[FRACTION], rounding=rounding) else: fraction = '' roundInt = False diff --git a/src/zope/i18n/tests/locale3/en/LC_MESSAGES/zope-i18n.in b/src/zope/i18n/tests/locale3/en/LC_MESSAGES/zope-i18n.in Binary files differindex d58a7d7..56ec799 100644 --- a/src/zope/i18n/tests/locale3/en/LC_MESSAGES/zope-i18n.in +++ b/src/zope/i18n/tests/locale3/en/LC_MESSAGES/zope-i18n.in diff --git a/src/zope/i18n/tests/locale3/en/LC_MESSAGES/zope-i18n.mo b/src/zope/i18n/tests/locale3/en/LC_MESSAGES/zope-i18n.mo Binary files differindex 8bdb36f..56ec799 100644 --- a/src/zope/i18n/tests/locale3/en/LC_MESSAGES/zope-i18n.mo +++ b/src/zope/i18n/tests/locale3/en/LC_MESSAGES/zope-i18n.mo diff --git a/src/zope/i18n/tests/test_formats.py b/src/zope/i18n/tests/test_formats.py index aa7910c..ce235c3 100644 --- a/src/zope/i18n/tests/test_formats.py +++ b/src/zope/i18n/tests/test_formats.py @@ -1176,6 +1176,32 @@ class TestNumberFormat(TestCase): self.assertEqual(self.format.format(41.02, '(0.0##E0##* )* '), '(4.102E1 ) ') + def testFormatSmallNumbers(self): + self.assertEqual(self.format.format( + -1e-7, '(#0.00#####);(-#0.00#####)'), '(-0.0000001)') + self.assertEqual(self.format.format(1e-9, '(#0.00###)'), '(0.00)') + self.assertEqual(self.format.format(1e-9, '(#0.00###)'), '(0.00)') + + def testFormatHighPrecisionNumbers(self): + self.assertEqual(self.format.format( + 1+1e-7, '(#0.00#####);(-#0.00#####)'), '(1.0000001)') + self.assertEqual(self.format.format( + 1+1e-7, '(#0.00###)'), '(1.00000)') + self.assertEqual(self.format.format( + 1+1e-9, '(#0.00#######);(-#0.00#######)'), '(1.000000001)') + self.assertEqual(self.format.format( + 1+1e-9, '(#0.00###)'), '(1.00000)') + self.assertEqual(self.format.format( + 1+1e-12, '(#0.00##########);(-#0.00##########)'), + '(1.000000000001)') + self.assertEqual(self.format.format( + 1+1e-12, '(#0.00###)'), '(1.00000)') + + def testNoRounding(self): + # Witout Rounding + self.assertEqual(self.format.format( + decimal.Decimal('0.99999'), '0.###', rounding=False), '0.99999') + def test_suite(): return TestSuite(( @@ -1,7 +1,9 @@ [tox] -envlist = py27,py33,py34,py35,pypy,pypy3,coverage,docs +envlist = py27,py34,py35,py36,pypy,pypy3,coverage,docs [testenv] +# We need to use develop egg to get namespace directories right. +usedevelop = true commands = python setup.py -q test -q deps = |