diff --git a/CHANGES.rst b/CHANGES.rst
index ae2374d..e2085c4 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -5,7 +5,8 @@
 4.6.2 (unreleased)
 ==================
 
-- Nothing changed yet.
+- Fix `NumberFormat` to respect the thousand grouping given by the pattern.
+  Triple grouping was hardcoded, which is not true for all locales.
 
 
 4.6.1 (2018-10-24)
diff --git a/src/zope/i18n/format.py b/src/zope/i18n/format.py
index 96f5ef3..abc3e50 100644
--- a/src/zope/i18n/format.py
+++ b/src/zope/i18n/format.py
@@ -226,7 +226,6 @@ class NumberParseError(Exception):
 class NumberFormat(object):
     __doc__ = INumberFormat.__doc__
 
-
     type = None
     _pattern = None
     _bin_pattern = None
@@ -368,6 +367,33 @@ class NumberFormat(object):
             fraction = self.symbols['decimal'] + fraction
         return fraction, roundInt
 
+    # taken from cpython lib/Locale.py
+    def _grouping_intervals(self, grouping):
+        last_interval = None
+        for interval in grouping:
+            # 0: re-use last group ad infinitum
+            if interval == 0:
+                if last_interval is None:
+                    raise ValueError("invalid grouping")
+                while True:
+                    yield last_interval
+            yield interval
+            last_interval = interval
+
+    def _group(self, integer, grouping):
+        # take a given chunk of digits and insert the group symbol
+        # grouping is usually: (3, 0) or (3, 2, 0)
+        digits = list(reversed(integer))
+        last_idx = 0
+        for group_length in self._grouping_intervals(grouping):
+            pos = last_idx + group_length
+            if pos >= len(digits):
+                break
+            digits.insert(pos, self.symbols['group'])
+            last_idx = pos + 1
+        res = ''.join(reversed(digits))
+        return res
+
     def format(self, obj, pattern=None, rounding=True):
         "See zope.i18n.interfaces.IFormat"
         # Make or get binary form of datetime pattern
@@ -454,13 +480,8 @@ class NumberFormat(object):
             integer = self._format_integer(str(int(math.fabs(obj))),
                                            bin_pattern[INTEGER])
             # Adding grouping
-            if bin_pattern[GROUPING] == 1:
-                help = ''
-                for pos in range(1, len(integer)+1):
-                    if (pos-1)%3 == 0 and pos != 1:
-                        help = self.symbols['group'] + help
-                    help = integer[-pos] + help
-                integer = help
+            if bin_pattern[GROUPING]:
+                integer = self._group(integer, bin_pattern[GROUPING])
             pre_padding = len(bin_pattern[INTEGER]) - len(integer)
             post_padding = len(bin_pattern[FRACTION]) - len(fraction)+1
             number = integer + fraction
@@ -779,12 +800,10 @@ def parseNumberPattern(pattern):
     fraction = ''
     exponential = ''
     suffix = ''
-    grouping = 0
     neg_pattern = None
 
     SPECIALCHARS = "*.,#0;E'"
 
-    length = len(pattern)
     state = BEGIN
     helper = ''
     for pos, char in enumerate(pattern):
@@ -832,7 +851,8 @@ def parseNumberPattern(pattern):
             if char == "#" or char == "0":
                 helper += char
             elif char == ",":
-                grouping = 1
+                # just add grouping markers to the integer pattern
+                helper += char
             elif char == ".":
                 integer = helper
                 helper = ''
@@ -934,6 +954,23 @@ def parseNumberPattern(pattern):
     if state == READ_EXPONENTIAL:
         exponential = helper
 
+    # the integer pattern can have the grouping delimiters too, let's take care
+    # about those here and now
+    # convert to a tuple of length of groups, from right to left
+    # example: (3, 0) for the usual triple separated, (3, 2, 0) for Hindi
+    # practically trying to return the same as locale.localeconv()['grouping']
+    grouping = ()
+    if "," in integer:
+        last_index = -1
+        for index, char in enumerate(reversed(integer)):
+            if char == ",":
+                grouping += (index-last_index-1,)
+                last_index = index
+        # use last group ad infinitum
+        grouping += (0,)
+        # remove grouping markers from integer pattern
+        integer = integer.replace(",", "")
+
     pattern = (padding_1, prefix, padding_2, integer, fraction, exponential,
                padding_3, suffix, padding_4, grouping)
 

NumberFormat is not prepared for 'lakh' it always assumes triple grouping
`zope.i18n.format.NumberFormat` assumes that number thousand grouping is always 3, the grouping by 3 is hardcoded.

`zope/i18n/locales/data/en_IN.xml` has the right formatting:

```
				<currencyFormat >
					<pattern>¤ ##,##,##0.00;-¤ ##,##,##0.00</pattern>
				</currencyFormat>
```

note that grouping is 2-2-3 for lakh

see: https://en.wikipedia.org/wiki/Lakh
diff --git a/src/zope/i18n/tests/test_formats.py b/src/zope/i18n/tests/test_formats.py
index 99c4bf7..879a71a 100644
--- a/src/zope/i18n/tests/test_formats.py
+++ b/src/zope/i18n/tests/test_formats.py
@@ -694,247 +694,349 @@ class TestNumberPatternParser(_TestCase):
     def testParseSimpleIntegerPattern(self):
         self.assertEqual(
             parseNumberPattern('###0'),
-            ((None, '', None, '###0', '', '', None, '', None, 0),
-             (None, '', None, '###0', '', '', None, '', None, 0)))
+            ((None, '', None, '###0', '', '', None, '', None, ()),
+             (None, '', None, '###0', '', '', None, '', None, ())))
 
     def testParseScientificIntegerPattern(self):
         self.assertEqual(
             parseNumberPattern('###0E#0'),
-            ((None, '', None, '###0', '', '#0', None, '', None, 0),
-             (None, '', None, '###0', '', '#0', None, '', None, 0)))
+            ((None, '', None, '###0', '', '#0', None, '', None, ()),
+             (None, '', None, '###0', '', '#0', None, '', None, ())))
         self.assertEqual(
             parseNumberPattern('###0E+#0'),
-            ((None, '', None, '###0', '', '+#0', None, '', None, 0),
-             (None, '', None, '###0', '', '+#0', None, '', None, 0)))
+            ((None, '', None, '###0', '', '+#0', None, '', None, ()),
+             (None, '', None, '###0', '', '+#0', None, '', None, ())))
 
     def testParsePosNegAlternativeIntegerPattern(self):
         self.assertEqual(
             parseNumberPattern('###0;#0'),
-            ((None, '', None, '###0', '', '', None, '', None, 0),
-             (None, '', None,   '#0', '', '', None, '', None, 0)))
+            ((None, '', None, '###0', '', '', None, '', None, ()),
+             (None, '', None,   '#0', '', '', None, '', None, ())))
 
     def testParsePrefixedIntegerPattern(self):
         self.assertEqual(
             parseNumberPattern('+###0'),
-            ((None, '+', None, '###0', '', '', None, '', None, 0),
-             (None, '+', None, '###0', '', '', None, '', None, 0)))
+            ((None, '+', None, '###0', '', '', None, '', None, ()),
+             (None, '+', None, '###0', '', '', None, '', None, ())))
 
     def testParsePosNegIntegerPattern(self):
         self.assertEqual(
             parseNumberPattern('+###0;-###0'),
-            ((None, '+', None, '###0', '', '', None, '', None, 0),
-             (None, '-', None, '###0', '', '', None, '', None, 0)))
+            ((None, '+', None, '###0', '', '', None, '', None, ()),
+             (None, '-', None, '###0', '', '', None, '', None, ())))
 
     def testParseScientificPosNegIntegerPattern(self):
         self.assertEqual(
             parseNumberPattern('+###0E0;-###0E#0'),
-            ((None, '+', None, '###0', '', '0', None, '', None, 0),
-             (None, '-', None, '###0', '', '#0', None, '', None, 0)))
+            ((None, '+', None, '###0', '', '0', None, '', None, ()),
+             (None, '-', None, '###0', '', '#0', None, '', None, ())))
 
     def testParseThousandSeparatorIntegerPattern(self):
         self.assertEqual(
             parseNumberPattern('#,##0'),
-            ((None, '', None, '###0', '', '', None, '', None, 1),
-             (None, '', None, '###0', '', '', None, '', None, 1)))
+            ((None, '', None, '###0', '', '', None, '', None, (3, 0)),
+             (None, '', None, '###0', '', '', None, '', None, (3, 0))))
 
     def testParseSimpleDecimalPattern(self):
         self.assertEqual(
             parseNumberPattern('###0.00#'),
-            ((None, '', None, '###0', '00#', '', None, '', None, 0),
-             (None, '', None, '###0', '00#', '', None, '', None, 0)))
+            ((None, '', None, '###0', '00#', '', None, '', None, ()),
+             (None, '', None, '###0', '00#', '', None, '', None, ())))
 
     def testParseScientificDecimalPattern(self):
         self.assertEqual(
             parseNumberPattern('###0.00#E#0'),
-            ((None, '', None, '###0', '00#', '#0', None, '', None, 0),
-             (None, '', None, '###0', '00#', '#0', None, '', None, 0)))
+            ((None, '', None, '###0', '00#', '#0', None, '', None, ()),
+             (None, '', None, '###0', '00#', '#0', None, '', None, ())))
 
     def testParsePosNegAlternativeFractionPattern(self):
         self.assertEqual(
             parseNumberPattern('###0.00#;#0.0#'),
-            ((None, '', None, '###0', '00#', '', None, '', None, 0),
-             (None, '', None,   '#0',  '0#', '', None, '', None, 0)))
+            ((None, '', None, '###0', '00#', '', None, '', None, ()),
+             (None, '', None,   '#0',  '0#', '', None, '', None, ())))
 
     def testParsePosNegFractionPattern(self):
         self.assertEqual(
             parseNumberPattern('+###0.0##;-###0.0##'),
-            ((None, '+', None, '###0', '0##', '', None, '', None, 0),
-             (None, '-', None, '###0', '0##', '', None, '', None, 0)))
+            ((None, '+', None, '###0', '0##', '', None, '', None, ()),
+             (None, '-', None, '###0', '0##', '', None, '', None, ())))
 
     def testParseScientificPosNegFractionPattern(self):
         self.assertEqual(
             parseNumberPattern('+###0.0##E#0;-###0.0##E0'),
-            ((None, '+', None, '###0', '0##', '#0', None, '', None, 0),
-             (None, '-', None, '###0', '0##', '0', None, '', None, 0)))
+            ((None, '+', None, '###0', '0##', '#0', None, '', None, ()),
+             (None, '-', None, '###0', '0##', '0', None, '', None, ())))
 
     def testParseThousandSeparatorFractionPattern(self):
         self.assertEqual(
             parseNumberPattern('#,##0.0#'),
-            ((None, '', None, '###0', '0#', '', None, '', None, 1),
-             (None, '', None, '###0', '0#', '', None, '', None, 1)))
+            ((None, '', None, '###0', '0#', '', None, '', None, (3, 0)),
+             (None, '', None, '###0', '0#', '', None, '', None, (3, 0))))
+
+    def testParseThousandSeparatorPatterns(self):
+        # the following patterns are present in the ICU XMLs:
+        self.assertEqual(
+            parseNumberPattern('#,##0.00;-#,##0.00'),
+            ((None, '', None, '###0', '00', '', None, '', None, (3, 0)),
+             (None, '-', None, '###0', '00', '', None, '', None, (3, 0))))
+
+        self.assertEqual(
+            parseNumberPattern('#,##,##0.###;-#,##,##0.###'),
+            ((None, '', None, '#####0', '###', '', None, '', None, (3, 2, 0)),
+             (None, '-', None, '#####0', '###', '', None, '', None, (3, 2, 0))))
+
+        self.assertEqual(
+            parseNumberPattern('#,##0.##;-#,##0.##'),
+            ((None, '', None, '###0', '##', '', None, '', None, (3, 0)),
+             (None, '-', None, '###0', '##', '', None, '', None, (3, 0))))
+
+        self.assertEqual(
+            parseNumberPattern('#,##0.###;-#,##0.###'),
+            ((None, '', None, '###0', '###', '', None, '', None, (3, 0)),
+             (None, '-', None, '###0', '###', '', None, '', None, (3, 0))))
+
+        self.assertEqual(
+            parseNumberPattern('#,##0.###;(#,##0.###)'),
+            ((None, '', None, '###0', '###', '', None, '', None, (3, 0)),
+             (None, '(', None, '###0', '###', '', None, ')', None, (3, 0))))
+
+        self.assertEqual(
+            parseNumberPattern('#,##0.###;#,##0.###-'),
+            ((None, '', None, '###0', '###', '', None, '', None, (3, 0)),
+             (None, '', None, '###0', '###', '', None, '-', None, (3, 0))))
+
+        self.assertEqual(
+            parseNumberPattern('##,##,##0.###;-##,##,##0.###'),
+            ((None, '', None, '######0', '###', '', None, '', None, (3, 2, 0)),
+             (None, '-', None, '######0', '###', '', None, '', None, (3, 2, 0))))
+
+        self.assertEqual(
+            parseNumberPattern('##,##0.##;-##,##0.##'),
+            ((None, '', None, '####0', '##', '', None, '', None, (3, 0)),
+             (None, '-', None, '####0', '##', '', None, '', None, (3, 0))))
+
+        self.assertEqual(
+            parseNumberPattern('###0.###;-###0.###'),
+            ((None, '', None, '###0', '###', '', None, '', None, ()),
+             (None, '-', None, '###0', '###', '', None, '', None, ())))
+
+        self.assertEqual(
+            parseNumberPattern('###0.###;###0.###-'),
+            ((None, '', None, '###0', '###', '', None, '', None, ()),
+             (None, '', None, '###0', '###', '', None, '-', None, ())))
+
+        self.assertEqual(
+            parseNumberPattern('#0.###;-#0.###'),
+            ((None, '', None, '#0', '###', '', None, '', None, ()),
+             (None, '-', None, '#0', '###', '', None, '', None, ())))
+
+        self.assertEqual(
+            parseNumberPattern('#,##0.###;#,##0.###'),
+            ((None, '', None, '###0', '###', '', None, '', None, (3, 0)),
+             (None, '', None, '###0', '###', '', None, '', None, (3, 0))))
+
+        self.assertEqual(
+            parseNumberPattern('###0.###;-###0.###'),
+            ((None, '', None, '###0', '###', '', None, '', None, ()),
+             (None, '-', None, '###0', '###', '', None, '', None, ())))
+
+        self.assertEqual(
+            parseNumberPattern('#,##0.00;-#,##0.00'),
+            ((None, '', None, '###0', '00', '', None, '', None, (3, 0)),
+             (None, '-', None, '###0', '00', '', None, '', None, (3, 0))))
+
+        self.assertEqual(
+            parseNumberPattern('#,##0.00;(#,##0.00)'),
+            ((None, '', None, '###0', '00', '', None, '', None, (3, 0)),
+             (None, '(', None, '###0', '00', '', None, ')', None, (3, 0))))
+
+        self.assertEqual(
+            parseNumberPattern('#,##0.00;-#,##0.00'),
+            ((None, '', None, '###0', '00', '', None, '', None, (3, 0)),
+             (None, '-', None, '###0', '00', '', None, '', None, (3, 0))))
+
+        self.assertEqual(
+            parseNumberPattern('##,##,##0.00;-##,##,##0.00'),
+            ((None, '', None, '######0', '00', '', None, '', None, (3, 2, 0)),
+             (None, '-', None, '######0', '00', '', None, '', None, (3, 2, 0))))
+
+        self.assertEqual(
+            parseNumberPattern('###0.00;-###0.00'),
+            ((None, '', None, '###0', '00', '', None, '', None, ()),
+             (None, '-', None, '###0', '00', '', None, '', None, ())))
+
+        self.assertEqual(
+            parseNumberPattern('#,##0;-#,##0'),
+            ((None, '', None, '###0', '', '', None, '', None, (3, 0)),
+             (None, '-', None, '###0', '', '', None, '', None, (3, 0))))
+
+        self.assertEqual(
+            parseNumberPattern('###0.00;-###0.00'),
+            ((None, '', None, '###0', '00', '', None, '', None, ()),
+             (None, '-', None, '###0', '00', '', None, '', None, ())))
 
     def testParsePadding1WithoutPrefixPattern(self):
         self.assertEqual(
             parseNumberPattern('* ###0'),
-            ((' ', '', None, '###0', '', '', None, '', None, 0),
-             (' ', '', None, '###0', '', '', None, '', None, 0)))
+            ((' ', '', None, '###0', '', '', None, '', None, ()),
+             (' ', '', None, '###0', '', '', None, '', None, ())))
         self.assertEqual(
             parseNumberPattern('* ###0.0##'),
-            ((' ', '', None, '###0', '0##', '', None, '', None, 0),
-             (' ', '', None, '###0', '0##', '', None, '', None, 0)))
+            ((' ', '', None, '###0', '0##', '', None, '', None, ()),
+             (' ', '', None, '###0', '0##', '', None, '', None, ())))
         self.assertEqual(
             parseNumberPattern('* ###0.0##;*_###0.0##'),
-            ((' ', '', None, '###0', '0##', '', None, '', None, 0),
-             ('_', '', None, '###0', '0##', '', None, '', None, 0)))
+            ((' ', '', None, '###0', '0##', '', None, '', None, ()),
+             ('_', '', None, '###0', '0##', '', None, '', None, ())))
 
     def testParsePadding1WithPrefixPattern(self):
         self.assertEqual(
             parseNumberPattern('* +###0'),
-            ((' ', '+', None, '###0', '', '', None, '', None, 0),
-             (' ', '+', None, '###0', '', '', None, '', None, 0)))
+            ((' ', '+', None, '###0', '', '', None, '', None, ()),
+             (' ', '+', None, '###0', '', '', None, '', None, ())))
         self.assertEqual(
             parseNumberPattern('* +###0.0##'),
-            ((' ', '+', None, '###0', '0##', '', None, '', None, 0),
-             (' ', '+', None, '###0', '0##', '', None, '', None, 0)))
+            ((' ', '+', None, '###0', '0##', '', None, '', None, ()),
+             (' ', '+', None, '###0', '0##', '', None, '', None, ())))
         self.assertEqual(
             parseNumberPattern('* +###0.0##;*_-###0.0##'),
-            ((' ', '+', None, '###0', '0##', '', None, '', None, 0),
-             ('_', '-', None, '###0', '0##', '', None, '', None, 0)))
+            ((' ', '+', None, '###0', '0##', '', None, '', None, ()),
+             ('_', '-', None, '###0', '0##', '', None, '', None, ())))
 
     def testParsePadding1Padding2WithPrefixPattern(self):
         self.assertEqual(
             parseNumberPattern('* +* ###0'),
-            ((' ', '+', ' ', '###0', '', '', None, '', None, 0),
-             (' ', '+', ' ', '###0', '', '', None, '', None, 0)))
+            ((' ', '+', ' ', '###0', '', '', None, '', None, ()),
+             (' ', '+', ' ', '###0', '', '', None, '', None, ())))
         self.assertEqual(
             parseNumberPattern('* +* ###0.0##'),
-            ((' ', '+', ' ', '###0', '0##', '', None, '', None, 0),
-             (' ', '+', ' ', '###0', '0##', '', None, '', None, 0)))
+            ((' ', '+', ' ', '###0', '0##', '', None, '', None, ()),
+             (' ', '+', ' ', '###0', '0##', '', None, '', None, ())))
         self.assertEqual(
             parseNumberPattern('* +* ###0.0##;*_-*_###0.0##'),
-            ((' ', '+', ' ', '###0', '0##', '', None, '', None, 0),
-             ('_', '-', '_', '###0', '0##', '', None, '', None, 0)))
+            ((' ', '+', ' ', '###0', '0##', '', None, '', None, ()),
+             ('_', '-', '_', '###0', '0##', '', None, '', None, ())))
 
-    def testParsePadding3WithoutSufffixPattern(self):
+    def testParsePadding3WithoutSuffixPattern(self):
         self.assertEqual(
             parseNumberPattern('###0* '),
-            ((None, '', None, '###0', '', '', ' ', '', None, 0),
-             (None, '', None, '###0', '', '', ' ', '', None, 0)))
+            ((None, '', None, '###0', '', '', ' ', '', None, ()),
+             (None, '', None, '###0', '', '', ' ', '', None, ())))
         self.assertEqual(
             parseNumberPattern('###0.0##* '),
-            ((None, '', None, '###0', '0##', '', ' ', '', None, 0),
-             (None, '', None, '###0', '0##', '', ' ', '', None, 0)))
+            ((None, '', None, '###0', '0##', '', ' ', '', None, ()),
+             (None, '', None, '###0', '0##', '', ' ', '', None, ())))
         self.assertEqual(
             parseNumberPattern('###0.0##* ;###0.0##*_'),
-            ((None, '', None, '###0', '0##', '', ' ', '', None, 0),
-             (None, '', None, '###0', '0##', '', '_', '', None, 0)))
+            ((None, '', None, '###0', '0##', '', ' ', '', None, ()),
+             (None, '', None, '###0', '0##', '', '_', '', None, ())))
 
     def testParsePadding3InScientificPattern(self):
         self.assertEqual(
             parseNumberPattern('###0E#0* '),
-            ((None, '', None, '###0', '', '#0', ' ', '', None, 0),
-             (None, '', None, '###0', '', '#0', ' ', '', None, 0)))
+            ((None, '', None, '###0', '', '#0', ' ', '', None, ()),
+             (None, '', None, '###0', '', '#0', ' ', '', None, ())))
         self.assertEqual(
             parseNumberPattern('###0.0##E0* '),
-            ((None, '', None, '###0', '0##', '0', ' ', '', None, 0),
-             (None, '', None, '###0', '0##', '0', ' ', '', None, 0)))
+            ((None, '', None, '###0', '0##', '0', ' ', '', None, ()),
+             (None, '', None, '###0', '0##', '0', ' ', '', None, ())))
         self.assertEqual(
             parseNumberPattern('###0.0##E#0* ;###0.0##E0*_'),
-            ((None, '', None, '###0', '0##', '#0', ' ', '', None, 0),
-             (None, '', None, '###0', '0##', '0', '_', '', None, 0)))
+            ((None, '', None, '###0', '0##', '#0', ' ', '', None, ()),
+             (None, '', None, '###0', '0##', '0', '_', '', None, ())))
 
     def testParsePadding3WithSufffixPattern(self):
         self.assertEqual(
             parseNumberPattern('###0* /'),
-            ((None, '', None, '###0', '', '', ' ', '/', None, 0),
-             (None, '', None, '###0', '', '', ' ', '/', None, 0)))
+            ((None, '', None, '###0', '', '', ' ', '/', None, ()),
+             (None, '', None, '###0', '', '', ' ', '/', None, ())))
         self.assertEqual(
             parseNumberPattern('###0.0#* /'),
-            ((None, '', None, '###0', '0#', '', ' ', '/', None, 0),
-             (None, '', None, '###0', '0#', '', ' ', '/', None, 0)))
+            ((None, '', None, '###0', '0#', '', ' ', '/', None, ()),
+             (None, '', None, '###0', '0#', '', ' ', '/', None, ())))
         self.assertEqual(
             parseNumberPattern('###0.0#* /;###0.0#*_/'),
-            ((None, '', None, '###0', '0#', '', ' ', '/', None, 0),
-             (None, '', None, '###0', '0#', '', '_', '/', None, 0)))
+            ((None, '', None, '###0', '0#', '', ' ', '/', None, ()),
+             (None, '', None, '###0', '0#', '', '_', '/', None, ())))
 
     def testParsePadding3And4WithSuffixPattern(self):
         self.assertEqual(
             parseNumberPattern('###0* /* '),
-            ((None, '', None, '###0', '', '', ' ', '/', ' ', 0),
-             (None, '', None, '###0', '', '', ' ', '/', ' ', 0)))
+            ((None, '', None, '###0', '', '', ' ', '/', ' ', ()),
+             (None, '', None, '###0', '', '', ' ', '/', ' ', ())))
         self.assertEqual(
             parseNumberPattern('###0* /* ;###0*_/*_'),
-            ((None, '', None, '###0', '', '', ' ', '/', ' ', 0),
-             (None, '', None, '###0', '', '', '_', '/', '_', 0)))
+            ((None, '', None, '###0', '', '', ' ', '/', ' ', ()),
+             (None, '', None, '###0', '', '', '_', '/', '_', ())))
 
     def testParseMultipleCharacterPrefix(self):
         self.assertEqual(
             parseNumberPattern('DM###0'),
-            ((None, 'DM', None, '###0', '', '', None, '', None, 0),
-             (None, 'DM', None, '###0', '', '', None, '', None, 0)))
+            ((None, 'DM', None, '###0', '', '', None, '', None, ()),
+             (None, 'DM', None, '###0', '', '', None, '', None, ())))
         self.assertEqual(
             parseNumberPattern('DM* ###0'),
-            ((None, 'DM', ' ', '###0', '', '', None, '', None, 0),
-             (None, 'DM', ' ', '###0', '', '', None, '', None, 0)))
+            ((None, 'DM', ' ', '###0', '', '', None, '', None, ()),
+             (None, 'DM', ' ', '###0', '', '', None, '', None, ())))
 
     def testParseStringEscapedPrefix(self):
         self.assertEqual(
             parseNumberPattern("'DEM'###0"),
-            ((None, 'DEM', None, '###0', '', '', None, '', None, 0),
-             (None, 'DEM', None, '###0', '', '', None, '', None, 0)))
+            ((None, 'DEM', None, '###0', '', '', None, '', None, ()),
+             (None, 'DEM', None, '###0', '', '', None, '', None, ())))
         self.assertEqual(
             parseNumberPattern("D'EM'###0"),
-            ((None, 'DEM', None, '###0', '', '', None, '', None, 0),
-             (None, 'DEM', None, '###0', '', '', None, '', None, 0)))
+            ((None, 'DEM', None, '###0', '', '', None, '', None, ()),
+             (None, 'DEM', None, '###0', '', '', None, '', None, ())))
         self.assertEqual(
             parseNumberPattern("D'E'M###0"),
-            ((None, 'DEM', None, '###0', '', '', None, '', None, 0),
-             (None, 'DEM', None, '###0', '', '', None, '', None, 0)))
+            ((None, 'DEM', None, '###0', '', '', None, '', None, ()),
+             (None, 'DEM', None, '###0', '', '', None, '', None, ())))
 
     def testParseStringEscapedSuffix(self):
         self.assertEqual(
             parseNumberPattern("###0'DEM'"),
-            ((None, '', None, '###0', '', '', None, 'DEM', None, 0),
-             (None, '', None, '###0', '', '', None, 'DEM', None, 0)))
+            ((None, '', None, '###0', '', '', None, 'DEM', None, ()),
+             (None, '', None, '###0', '', '', None, 'DEM', None, ())))
         self.assertEqual(
             parseNumberPattern("###0D'EM'"),
-            ((None, '', None, '###0', '', '', None, 'DEM', None, 0),
-             (None, '', None, '###0', '', '', None, 'DEM', None, 0)))
+            ((None, '', None, '###0', '', '', None, 'DEM', None, ()),
+             (None, '', None, '###0', '', '', None, 'DEM', None, ())))
         self.assertEqual(
             parseNumberPattern("###0D'E'M"),
-            ((None, '', None, '###0', '', '', None, 'DEM', None, 0),
-             (None, '', None, '###0', '', '', None, 'DEM', None, 0)))
+            ((None, '', None, '###0', '', '', None, 'DEM', None, ()),
+             (None, '', None, '###0', '', '', None, 'DEM', None, ())))
 
     def testParseInvalidBegin(self):
         with self.assertRaisesRegex(NumberPatternParseError,
                                     "Wrong syntax at beginning"):
             parseNumberPattern(".")
 
-    def testParseFractionQuate(self):
+    def testParseFractionQuote(self):
         pattern, neg_pattern = parseNumberPattern("0.'")
         self.assertEqual(
-            (None, '', None, '0', '', '', None, '', None, 0),
+            (None, '', None, '0', '', '', None, '', None, ()),
             pattern)
         self.assertEqual(
-            (None, '', None, '0', '', '', None, '', None, 0),
+            (None, '', None, '0', '', '', None, '', None, ()),
             neg_pattern)
 
     def testParseExponentialQuote(self):
         pattern, neg_pattern = parseNumberPattern("0E'")
         self.assertEqual(
-            (None, '', None, '0', '', '', None, '', None, 0),
+            (None, '', None, '0', '', '', None, '', None, ()),
             pattern)
         self.assertEqual(
-            (None, '', None, '0', '', '', None, '', None, 0),
+            (None, '', None, '0', '', '', None, '', None, ()),
             neg_pattern)
 
     def testParseExponentialNumber(self):
         pattern, neg_pattern = parseNumberPattern("0E1")
         self.assertEqual(
-            (None, '', None, '0', '', '', None, '1', None, 0),
+            (None, '', None, '0', '', '', None, '1', None, ()),
             pattern)
         self.assertEqual(
-            (None, '', None, '0', '', '', None, '1', None, 0),
+            (None, '', None, '0', '', '', None, '1', None, ()),
             neg_pattern)
 
 class TestNumberFormat(_TestCase):
@@ -1199,14 +1301,30 @@ class TestNumberFormat(_TestCase):
                          '-2.3341E04')
 
     def testFormatThousandSeparatorInteger(self):
-        self.assertEqual(self.format.format(23341, '+#,##0;-#,##0'),
-                         '+23,341')
-        self.assertEqual(self.format.format(-23341, '+#,##0;-#,##0'),
-                         '-23,341')
-        self.assertEqual(self.format.format(41, '+#0,000;-#0,000'),
-                         '+0,041')
-        self.assertEqual(self.format.format(-41, '+#0,000;-#0,000'),
-                         '-0,041')
+        self.assertEqual(
+            self.format.format(23341, '+#,##0;-#,##0'),
+            '+23,341')
+        self.assertEqual(
+            self.format.format(-23341, '+#,##0;-#,##0'),
+            '-23,341')
+        self.assertEqual(
+            self.format.format(41, '+#0,000;-#0,000'),
+            '+0,041')
+        self.assertEqual(
+            self.format.format(-41, '+#0,000;-#0,000'),
+            '-0,041')
+        self.assertEqual(
+            self.format.format(987654321, '+#,##0;-#,##0'),
+            '+987,654,321')
+        self.assertEqual(
+            self.format.format(-987654321, '+#,##0;-#,##0'),
+            '-987,654,321')
+        self.assertEqual(
+            self.format.format(987654321, '+#,##,#0,000;-#,##,#0,000'),
+            '+98,76,54,321')
+        self.assertEqual(
+            self.format.format(-987654321, '+#,##,#0,000;-#,##,#0,000'),
+            '-98,76,54,321')
 
     def testFormatDecimal(self):
         self.assertEqual(self.format.format(23341.02357, '###0.0#'),

pip install -e .[test,docs,compile,zcml]
parse_log_pytest
pytest
pytest
3.6
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.13
attrs @ file:///opt/conda/conda-bld/attrs_1642510447205/work
Babel==2.11.0
certifi==2021.5.30
charset-normalizer==2.0.12
docutils==0.18.1
idna==3.10
imagesize==1.4.1
importlib-metadata @ file:///tmp/build/80754af9/importlib-metadata_1631916693255/work
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
Jinja2==3.0.3
MarkupSafe==2.0.1
more-itertools @ file:///tmp/build/80754af9/more-itertools_1637733554872/work
multipart==1.1.0
packaging @ file:///tmp/build/80754af9/packaging_1637314298585/work
pluggy @ file:///tmp/build/80754af9/pluggy_1615976315926/work
py @ file:///opt/conda/conda-bld/py_1644396412707/work
Pygments==2.14.0
pyparsing @ file:///tmp/build/80754af9/pyparsing_1635766073266/work
pytest==6.2.4
python-gettext==4.1
pytz==2025.2
repoze.sphinx.autointerface==1.0.0
requests==2.27.1
six==1.17.0
snowballstemmer==2.2.0
Sphinx==5.3.0
sphinxcontrib-applehelp==1.0.2
sphinxcontrib-devhelp==1.0.2
sphinxcontrib-htmlhelp==2.0.0
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==1.0.3
sphinxcontrib-serializinghtml==1.1.5
toml @ file:///tmp/build/80754af9/toml_1616166611790/work
typing_extensions @ file:///opt/conda/conda-bld/typing_extensions_1647553014482/work
urllib3==1.26.20
zipp @ file:///tmp/build/80754af9/zipp_1633618647012/work
zope.browser==2.4
zope.component==5.1.0
zope.configuration==4.4.1
zope.contenttype==4.6
zope.deprecation==4.4.0
zope.event==4.6
zope.exceptions==4.6
zope.hookable==5.4
-e git+https://github.com/zopefoundation/zope.i18n.git@a706a1fcd827aa0094ef23b86ac47840c16be465#egg=zope.i18n
zope.i18nmessageid==5.1.1
zope.interface==5.5.2
zope.location==4.3
zope.proxy==4.6.1
zope.publisher==6.1.0
zope.schema==6.2.1
zope.security==5.8
zope.testing==5.0.1
zope.testrunner==5.6

name: zope.i18n
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - attrs=21.4.0=pyhd3eb1b0_0
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - importlib-metadata=4.8.1=py36h06a4308_0
  - importlib_metadata=4.8.1=hd3eb1b0_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - more-itertools=8.12.0=pyhd3eb1b0_0
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - packaging=21.3=pyhd3eb1b0_0
  - pip=21.2.2=py36h06a4308_0
  - pluggy=0.13.1=py36h06a4308_0
  - py=1.11.0=pyhd3eb1b0_0
  - pyparsing=3.0.4=pyhd3eb1b0_0
  - pytest=6.2.4=py36h06a4308_2
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - toml=0.10.2=pyhd3eb1b0_0
  - typing_extensions=4.1.1=pyh06a4308_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zipp=3.6.0=pyhd3eb1b0_0
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.13
      - babel==2.11.0
      - charset-normalizer==2.0.12
      - docutils==0.18.1
      - idna==3.10
      - imagesize==1.4.1
      - jinja2==3.0.3
      - markupsafe==2.0.1
      - multipart==1.1.0
      - pygments==2.14.0
      - python-gettext==4.1
      - pytz==2025.2
      - repoze-sphinx-autointerface==1.0.0
      - requests==2.27.1
      - six==1.17.0
      - snowballstemmer==2.2.0
      - sphinx==5.3.0
      - sphinxcontrib-applehelp==1.0.2
      - sphinxcontrib-devhelp==1.0.2
      - sphinxcontrib-htmlhelp==2.0.0
      - sphinxcontrib-jsmath==1.0.1
      - sphinxcontrib-qthelp==1.0.3
      - sphinxcontrib-serializinghtml==1.1.5
      - urllib3==1.26.20
      - zope-browser==2.4
      - zope-component==5.1.0
      - zope-configuration==4.4.1
      - zope-contenttype==4.6
      - zope-deprecation==4.4.0
      - zope-event==4.6
      - zope-exceptions==4.6
      - zope-hookable==5.4
      - zope-i18nmessageid==5.1.1
      - zope-interface==5.5.2
      - zope-location==4.3
      - zope-proxy==4.6.1
      - zope-publisher==6.1.0
      - zope-schema==6.2.1
      - zope-security==5.8
      - zope-testing==5.0.1
      - zope-testrunner==5.6
prefix: /opt/conda/envs/zope.i18n

src/zope/i18n/tests/test_formats.py::TestNumberPatternParser::testParseExponentialNumber src/zope/i18n/tests/test_formats.py::TestNumberPatternParser::testParseExponentialQuote src/zope/i18n/tests/test_formats.py::TestNumberPatternParser::testParseFractionQuote src/zope/i18n/tests/test_formats.py::TestNumberPatternParser::testParseMultipleCharacterPrefix src/zope/i18n/tests/test_formats.py::TestNumberPatternParser::testParsePadding1Padding2WithPrefixPattern src/zope/i18n/tests/test_formats.py::TestNumberPatternParser::testParsePadding1WithPrefixPattern src/zope/i18n/tests/test_formats.py::TestNumberPatternParser::testParsePadding1WithoutPrefixPattern src/zope/i18n/tests/test_formats.py::TestNumberPatternParser::testParsePadding3And4WithSuffixPattern src/zope/i18n/tests/test_formats.py::TestNumberPatternParser::testParsePadding3InScientificPattern src/zope/i18n/tests/test_formats.py::TestNumberPatternParser::testParsePadding3WithSufffixPattern src/zope/i18n/tests/test_formats.py::TestNumberPatternParser::testParsePadding3WithoutSuffixPattern src/zope/i18n/tests/test_formats.py::TestNumberPatternParser::testParsePosNegAlternativeFractionPattern src/zope/i18n/tests/test_formats.py::TestNumberPatternParser::testParsePosNegAlternativeIntegerPattern src/zope/i18n/tests/test_formats.py::TestNumberPatternParser::testParsePosNegFractionPattern src/zope/i18n/tests/test_formats.py::TestNumberPatternParser::testParsePosNegIntegerPattern src/zope/i18n/tests/test_formats.py::TestNumberPatternParser::testParsePrefixedIntegerPattern src/zope/i18n/tests/test_formats.py::TestNumberPatternParser::testParseScientificDecimalPattern src/zope/i18n/tests/test_formats.py::TestNumberPatternParser::testParseScientificIntegerPattern src/zope/i18n/tests/test_formats.py::TestNumberPatternParser::testParseScientificPosNegFractionPattern src/zope/i18n/tests/test_formats.py::TestNumberPatternParser::testParseScientificPosNegIntegerPattern src/zope/i18n/tests/test_formats.py::TestNumberPatternParser::testParseSimpleDecimalPattern src/zope/i18n/tests/test_formats.py::TestNumberPatternParser::testParseSimpleIntegerPattern src/zope/i18n/tests/test_formats.py::TestNumberPatternParser::testParseStringEscapedPrefix src/zope/i18n/tests/test_formats.py::TestNumberPatternParser::testParseStringEscapedSuffix src/zope/i18n/tests/test_formats.py::TestNumberPatternParser::testParseThousandSeparatorFractionPattern src/zope/i18n/tests/test_formats.py::TestNumberPatternParser::testParseThousandSeparatorIntegerPattern src/zope/i18n/tests/test_formats.py::TestNumberPatternParser::testParseThousandSeparatorPatterns src/zope/i18n/tests/test_formats.py::TestNumberFormat::testFormatThousandSeparatorInteger
src/zope/i18n/tests/test_formats.py::TestDateTimePatternParser::testParseDateTimePatternError src/zope/i18n/tests/test_formats.py::TestDateTimePatternParser::testParseDateTimePatternRepeatDateTimeChars src/zope/i18n/tests/test_formats.py::TestDateTimePatternParser::testParseGermanTimePattern src/zope/i18n/tests/test_formats.py::TestDateTimePatternParser::testParseQuotesInPattern src/zope/i18n/tests/test_formats.py::TestDateTimePatternParser::testParseRealDate src/zope/i18n/tests/test_formats.py::TestDateTimePatternParser::testParseRealDateTime src/zope/i18n/tests/test_formats.py::TestDateTimePatternParser::testParseSimpleTimePattern src/zope/i18n/tests/test_formats.py::TestBuildDateTimeParseInfo::testAMPMMarker src/zope/i18n/tests/test_formats.py::TestBuildDateTimeParseInfo::testEra src/zope/i18n/tests/test_formats.py::TestBuildDateTimeParseInfo::testGenericNumbers src/zope/i18n/tests/test_formats.py::TestBuildDateTimeParseInfo::testMonthAbbr src/zope/i18n/tests/test_formats.py::TestBuildDateTimeParseInfo::testMonthNames src/zope/i18n/tests/test_formats.py::TestBuildDateTimeParseInfo::testMonthNumber src/zope/i18n/tests/test_formats.py::TestBuildDateTimeParseInfo::testTimeZone src/zope/i18n/tests/test_formats.py::TestBuildDateTimeParseInfo::testWeekdayAbbr src/zope/i18n/tests/test_formats.py::TestBuildDateTimeParseInfo::testWeekdayNames src/zope/i18n/tests/test_formats.py::TestBuildDateTimeParseInfo::testWeekdayNumber src/zope/i18n/tests/test_formats.py::TestBuildDateTimeParseInfo::testYear src/zope/i18n/tests/test_formats.py::TestDateTimeFormat::testDateTimeParseError src/zope/i18n/tests/test_formats.py::TestDateTimeFormat::testFormatAMPMDateTime src/zope/i18n/tests/test_formats.py::TestDateTimeFormat::testFormatAllWeekdays src/zope/i18n/tests/test_formats.py::TestDateTimeFormat::testFormatDayInYear src/zope/i18n/tests/test_formats.py::TestDateTimeFormat::testFormatDayOfWeekInMOnth src/zope/i18n/tests/test_formats.py::TestDateTimeFormat::testFormatDayOfWeekInMonth src/zope/i18n/tests/test_formats.py::TestDateTimeFormat::testFormatGregorianEra src/zope/i18n/tests/test_formats.py::TestDateTimeFormat::testFormatHourInDayOneTo24 src/zope/i18n/tests/test_formats.py::TestDateTimeFormat::testFormatHourInDayZeroToEleven src/zope/i18n/tests/test_formats.py::TestDateTimeFormat::testFormatRealDateTime src/zope/i18n/tests/test_formats.py::TestDateTimeFormat::testFormatSimpleDateTime src/zope/i18n/tests/test_formats.py::TestDateTimeFormat::testFormatSimpleHourRepresentation src/zope/i18n/tests/test_formats.py::TestDateTimeFormat::testFormatTimeZone src/zope/i18n/tests/test_formats.py::TestDateTimeFormat::testFormatUnusualFormats src/zope/i18n/tests/test_formats.py::TestDateTimeFormat::testFormatWeekDay src/zope/i18n/tests/test_formats.py::TestDateTimeFormat::testFormatWeekInMonth src/zope/i18n/tests/test_formats.py::TestDateTimeFormat::testFormateMonthLengthOne src/zope/i18n/tests/test_formats.py::TestDateTimeFormat::testInterfaceConformity src/zope/i18n/tests/test_formats.py::TestDateTimeFormat::testParse12PM src/zope/i18n/tests/test_formats.py::TestDateTimeFormat::testParseAMPMDateTime src/zope/i18n/tests/test_formats.py::TestDateTimeFormat::testParseAMPMMissing src/zope/i18n/tests/test_formats.py::TestDateTimeFormat::testParseBadTimezone src/zope/i18n/tests/test_formats.py::TestDateTimeFormat::testParseNotObject src/zope/i18n/tests/test_formats.py::TestDateTimeFormat::testParsePyTzTimezone src/zope/i18n/tests/test_formats.py::TestDateTimeFormat::testParseRealDateTime src/zope/i18n/tests/test_formats.py::TestDateTimeFormat::testParseSimpleDateTime src/zope/i18n/tests/test_formats.py::TestDateTimeFormat::testParseTimeZone src/zope/i18n/tests/test_formats.py::TestDateTimeFormat::testParseTimeZoneNames src/zope/i18n/tests/test_formats.py::TestDateTimeFormat::testParseTwoDigitYearIs20thCentury src/zope/i18n/tests/test_formats.py::TestDateTimeFormat::testParseUnusualFormats src/zope/i18n/tests/test_formats.py::TestNumberPatternParser::testParseInvalidBegin src/zope/i18n/tests/test_formats.py::TestNumberFormat::testChangeOutputType src/zope/i18n/tests/test_formats.py::TestNumberFormat::testFormatDecimal src/zope/i18n/tests/test_formats.py::TestNumberFormat::testFormatHighPrecisionNumbers src/zope/i18n/tests/test_formats.py::TestNumberFormat::testFormatPadding1Padding2WithPrefix src/zope/i18n/tests/test_formats.py::TestNumberFormat::testFormatPadding1Scientific src/zope/i18n/tests/test_formats.py::TestNumberFormat::testFormatPadding1WithPrefix src/zope/i18n/tests/test_formats.py::TestNumberFormat::testFormatPadding1WithoutPrefix src/zope/i18n/tests/test_formats.py::TestNumberFormat::testFormatPadding3Padding4WithSuffix src/zope/i18n/tests/test_formats.py::TestNumberFormat::testFormatPadding3Scientific src/zope/i18n/tests/test_formats.py::TestNumberFormat::testFormatPadding3WithSufffix src/zope/i18n/tests/test_formats.py::TestNumberFormat::testFormatPadding3WithoutSufffix src/zope/i18n/tests/test_formats.py::TestNumberFormat::testFormatPosNegAlternativeInteger src/zope/i18n/tests/test_formats.py::TestNumberFormat::testFormatPosNegInteger src/zope/i18n/tests/test_formats.py::TestNumberFormat::testFormatPosNegScientificInteger src/zope/i18n/tests/test_formats.py::TestNumberFormat::testFormatPrefixedInteger src/zope/i18n/tests/test_formats.py::TestNumberFormat::testFormatScientificDecimal src/zope/i18n/tests/test_formats.py::TestNumberFormat::testFormatScientificDecimalSmallerOne src/zope/i18n/tests/test_formats.py::TestNumberFormat::testFormatScientificInteger src/zope/i18n/tests/test_formats.py::TestNumberFormat::testFormatScientificZero src/zope/i18n/tests/test_formats.py::TestNumberFormat::testFormatSimpleInteger src/zope/i18n/tests/test_formats.py::TestNumberFormat::testFormatSmallNumbers src/zope/i18n/tests/test_formats.py::TestNumberFormat::testInterfaceConformity src/zope/i18n/tests/test_formats.py::TestNumberFormat::testNoRounding src/zope/i18n/tests/test_formats.py::TestNumberFormat::testParseDecimal src/zope/i18n/tests/test_formats.py::TestNumberFormat::testParseDecimalWithGermanDecimalSeparator src/zope/i18n/tests/test_formats.py::TestNumberFormat::testParseDecimalWithOptionalDecimalDigits src/zope/i18n/tests/test_formats.py::TestNumberFormat::testParseFailWithInvalidCharacters src/zope/i18n/tests/test_formats.py::TestNumberFormat::testParseFailWithInvalidGroupCharacterPosition src/zope/i18n/tests/test_formats.py::TestNumberFormat::testParsePadding1Padding2WithPrefix src/zope/i18n/tests/test_formats.py::TestNumberFormat::testParsePadding1Scientific src/zope/i18n/tests/test_formats.py::TestNumberFormat::testParsePadding1WithPrefix src/zope/i18n/tests/test_formats.py::TestNumberFormat::testParsePadding1WithoutPrefix src/zope/i18n/tests/test_formats.py::TestNumberFormat::testParsePadding3Padding4WithSuffix src/zope/i18n/tests/test_formats.py::TestNumberFormat::testParsePadding3Scientific src/zope/i18n/tests/test_formats.py::TestNumberFormat::testParsePadding3WithSufffix src/zope/i18n/tests/test_formats.py::TestNumberFormat::testParsePadding3WithoutSufffix src/zope/i18n/tests/test_formats.py::TestNumberFormat::testParsePosNegAlternativeInteger src/zope/i18n/tests/test_formats.py::TestNumberFormat::testParsePosNegInteger src/zope/i18n/tests/test_formats.py::TestNumberFormat::testParsePrefixedInteger src/zope/i18n/tests/test_formats.py::TestNumberFormat::testParseScientificDecimal src/zope/i18n/tests/test_formats.py::TestNumberFormat::testParseScientificDecimalSmallerOne src/zope/i18n/tests/test_formats.py::TestNumberFormat::testParseScientificInteger src/zope/i18n/tests/test_formats.py::TestNumberFormat::testParseSimpleInteger src/zope/i18n/tests/test_formats.py::TestNumberFormat::testParseThousandSeparatorInteger src/zope/i18n/tests/test_formats.py::TestNumberFormat::testParseWithAlternativeExponentialSymbol src/zope/i18n/tests/test_formats.py::TestNumberFormat::testRounding
src/zope/i18n/format.py CHANGES.rst
src/zope/i18n/format.py CHANGES.rst