Repository: python-babel/babel (python)

Issue:
Python message extractor doesn't respect unicode_literals in __future__
`extract_python` of Babel doesn't respect `from __future__ import unicode_literals` in Python 2.x.  With the below example code:

```python
# coding: utf-8
from __future__ import unicode_literals
_('\xa0')
```

We get this encoding error:

```
UnicodeDecodeError: 'utf8' codec can't decode byte 0xa0 in position 10: invalid start byte
```

A workround is to use `u''` literals explicitly:

```python
# coding: utf-8
from __future__ import unicode_literals
_(u'\xa0')
```

Pull request:
Python message extractor respects unicode_literals in __future__
This patch fixes #426.

Patch:
diff --git a/.travis.yml b/.travis.yml
index 08ac545..978d377 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -33,6 +33,12 @@ matrix:
       python: 3.4
     - os: linux
       python: 3.5
+    - os: osx
+      language: generic
+      env:
+      - PYTHON_VERSION=3.5.1
+      - PYENV_ROOT=~/.pyenv
+      - PATH=$PYENV_ROOT/shims:$PATH:$PYENV_ROOT/bin
 
 install:
   - bash .ci/deps.${TRAVIS_OS_NAME}.sh
diff --git a/babel/_compat.py b/babel/_compat.py
index aea3389..75abf9e 100644
--- a/babel/_compat.py
+++ b/babel/_compat.py
@@ -58,13 +58,19 @@ number_types = integer_types + (float,)
 
 
 #
-# Since Python 3.3, a fast decimal implementation is already included in the
-# standard library.  Otherwise use cdecimal when available
+# Use cdecimal when available
 #
-if sys.version_info[:2] >= (3, 3):
-    import decimal
-else:
-    try:
-        import cdecimal as decimal
-    except ImportError:
-        import decimal
+from decimal import (Decimal as _dec,
+                     InvalidOperation as _invop,
+                     ROUND_HALF_EVEN as _RHE)
+try:
+    from cdecimal import (Decimal as _cdec,
+                          InvalidOperation as _cinvop,
+                          ROUND_HALF_EVEN as _CRHE)
+    Decimal = _cdec
+    InvalidOperation = (_invop, _cinvop)
+    ROUND_HALF_EVEN = _CRHE
+except ImportError:
+    Decimal = _dec
+    InvalidOperation = _invop
+    ROUND_HALF_EVEN = _RHE
diff --git a/babel/dates.py b/babel/dates.py
index 523e456..4a0bbd3 100644
--- a/babel/dates.py
+++ b/babel/dates.py
@@ -1080,16 +1080,12 @@ def get_period_id(time, tzinfo=None, type=None, locale=LC_TIME):
     locale = Locale.parse(locale)
 
     # The LDML rules state that the rules may not overlap, so iterating in arbitrary
-    # order should be alright, though `at` periods should be preferred.
-    rulesets = locale.day_period_rules.get(type, {}).items()
-
-    for rule_id, rules in rulesets:
+    # order should be alright.
+    for rule_id, rules in locale.day_period_rules.get(type, {}).items():
         for rule in rules:
             if "at" in rule and rule["at"] == seconds_past_midnight:
                 return rule_id
 
-    for rule_id, rules in rulesets:
-        for rule in rules:
             start_ok = end_ok = False
 
             if "from" in rule and seconds_past_midnight >= rule["from"]:
@@ -1100,8 +1096,8 @@ def get_period_id(time, tzinfo=None, type=None, locale=LC_TIME):
                 end_ok = True
             if "before" in rule and seconds_past_midnight < rule["before"]:
                 end_ok = True
-            if "after" in rule:
-                raise NotImplementedError("'after' is deprecated as of CLDR 29.")
+            if "after" in rule and seconds_past_midnight > rule["after"]:
+                start_ok = True
 
             if start_ok and end_ok:
                 return rule_id
diff --git a/babel/messages/catalog.py b/babel/messages/catalog.py
index 84cee43..aeb9a9e 100644
--- a/babel/messages/catalog.py
+++ b/babel/messages/catalog.py
@@ -402,8 +402,6 @@ class Catalog(object):
                 self.msgid_bugs_address = value
             elif name == 'last-translator':
                 self.last_translator = value
-            elif name == 'language':
-                self.locale = Locale.parse(value)
             elif name == 'language-team':
                 self.language_team = value
             elif name == 'content-type':
diff --git a/babel/messages/extract.py b/babel/messages/extract.py
index 4dc56a4..db17848 100644
--- a/babel/messages/extract.py
+++ b/babel/messages/extract.py
@@ -22,7 +22,7 @@ from os.path import relpath
 import sys
 from tokenize import generate_tokens, COMMENT, NAME, OP, STRING
 
-from babel.util import parse_encoding, pathmatch
+from babel.util import parse_encoding, parse_future_flags, pathmatch
 from babel._compat import PY2, text_type
 from textwrap import dedent
 
@@ -399,6 +399,7 @@ def extract_python(fileobj, keywords, comment_tags, options):
     comment_tag = None
 
     encoding = parse_encoding(fileobj) or options.get('encoding', 'UTF-8')
+    future_flags = parse_future_flags(fileobj, encoding)
 
     if PY2:
         next_line = fileobj.readline
@@ -470,8 +471,9 @@ def extract_python(fileobj, keywords, comment_tags, options):
                 # encoding
                 # https://sourceforge.net/tracker/?func=detail&atid=355470&
                 # aid=617979&group_id=5470
-                value = eval('# coding=%s\n%s' % (str(encoding), value),
-                             {'__builtins__': {}}, {})
+                code = compile('# coding=%s\n%s' % (str(encoding), value),
+                               '<string>', 'eval', future_flags)
+                value = eval(code, {'__builtins__': {}}, {})
                 if PY2 and not isinstance(value, text_type):
                     value = value.decode(encoding)
                 buf.append(value)
diff --git a/babel/messages/frontend.py b/babel/messages/frontend.py
index d991013..d190a2c 100644
--- a/babel/messages/frontend.py
+++ b/babel/messages/frontend.py
@@ -416,7 +416,7 @@ class extract_messages(Command):
                               copyright_holder=self.copyright_holder,
                               charset=self.charset)
 
-            for path, method_map, options_map in mappings:
+            for path, (method_map, options_map) in mappings.items():
                 def callback(filename, method, options):
                     if method == 'ignore':
                         return
@@ -468,14 +468,14 @@ class extract_messages(Command):
                      sort_by_file=self.sort_by_file)
 
     def _get_mappings(self):
-        mappings = []
+        mappings = {}
 
         if self.mapping_file:
             fileobj = open(self.mapping_file, 'U')
             try:
                 method_map, options_map = parse_mapping(fileobj)
                 for path in self.input_paths:
-                    mappings.append((path, method_map, options_map))
+                    mappings[path] = method_map, options_map
             finally:
                 fileobj.close()
 
@@ -489,11 +489,11 @@ class extract_messages(Command):
                     for pattern, method, options in mapping:
                         method_map.append((pattern, method))
                         options_map[pattern] = options or {}
-                mappings.append((path, method_map, options_map))
+                mappings[path] = method_map, options_map
 
         else:
             for path in self.input_paths:
-                mappings.append((path, DEFAULT_MAPPING, {}))
+                mappings[path] = DEFAULT_MAPPING, {}
 
         return mappings
 
diff --git a/babel/messages/plurals.py b/babel/messages/plurals.py
index 38c04ca..cc7b79e 100644
--- a/babel/messages/plurals.py
+++ b/babel/messages/plurals.py
@@ -75,7 +75,7 @@ PLURALS = {
     # Cree
     # 'cr': (),
     # Czech
-    'cs': (3, '((n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2)'),
+    'cs': (3, '(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)'),
     # Church Slavic
     # 'cu': (),
     # Chuvash
@@ -171,7 +171,7 @@ PLURALS = {
     # Russian
     'ru': (3, '(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)'),
     # Slovak
-    'sk': (3, '((n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2)'),
+    'sk': (3, '(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)'),
     # Slovenian
     'sl': (4, '(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3)'),
     # Serbian - From Pootle's PO's
diff --git a/babel/messages/pofile.py b/babel/messages/pofile.py
index f3ba2fa..a775ec0 100644
--- a/babel/messages/pofile.py
+++ b/babel/messages/pofile.py
@@ -73,164 +73,6 @@ def denormalize(string):
         return unescape(string)
 
 
-class PoFileParser(object):
-    """Support class to  read messages from a ``gettext`` PO (portable object) file
-    and add them to a `Catalog`
-
-    See `read_po` for simple cases.
-    """
-
-    def __init__(self, catalog, ignore_obsolete=False):
-        self.catalog = catalog
-        self.ignore_obsolete = ignore_obsolete
-        self.counter = 0
-        self.offset = 0
-        self.messages = []
-        self.translations = []
-        self.locations = []
-        self.flags = []
-        self.user_comments = []
-        self.auto_comments = []
-        self.obsolete = False
-        self.context = []
-        self.in_msgid = False
-        self.in_msgstr = False
-        self.in_msgctxt = False
-
-    def _add_message(self):
-        """
-        Add a message to the catalog based on the current parser state and
-        clear the state ready to process the next message.
-        """
-        self.translations.sort()
-        if len(self.messages) > 1:
-            msgid = tuple([denormalize(m) for m in self.messages])
-        else:
-            msgid = denormalize(self.messages[0])
-        if isinstance(msgid, (list, tuple)):
-            string = []
-            for idx in range(self.catalog.num_plurals):
-                try:
-                    string.append(self.translations[idx])
-                except IndexError:
-                    string.append((idx, ''))
-            string = tuple([denormalize(t[1]) for t in string])
-        else:
-            string = denormalize(self.translations[0][1])
-        if self.context:
-            msgctxt = denormalize('\n'.join(self.context))
-        else:
-            msgctxt = None
-        message = Message(msgid, string, list(self.locations), set(self.flags),
-                          self.auto_comments, self.user_comments, lineno=self.offset + 1,
-                          context=msgctxt)
-        if self.obsolete:
-            if not self.ignore_obsolete:
-                self.catalog.obsolete[msgid] = message
-        else:
-            self.catalog[msgid] = message
-        del self.messages[:]
-        del self.translations[:]
-        del self.context[:]
-        del self.locations[:]
-        del self.flags[:]
-        del self.auto_comments[:]
-        del self.user_comments[:]
-        self.obsolete = False
-        self.counter += 1
-
-    def _process_message_line(self, lineno, line):
-        if line.startswith('msgid_plural'):
-            self.in_msgid = True
-            msg = line[12:].lstrip()
-            self.messages.append(msg)
-        elif line.startswith('msgid'):
-            self.in_msgid = True
-            self.offset = lineno
-            txt = line[5:].lstrip()
-            if self.messages:
-                self._add_message()
-            self.messages.append(txt)
-        elif line.startswith('msgstr'):
-            self.in_msgid = False
-            self.in_msgstr = True
-            msg = line[6:].lstrip()
-            if msg.startswith('['):
-                idx, msg = msg[1:].split(']', 1)
-                self.translations.append([int(idx), msg.lstrip()])
-            else:
-                self.translations.append([0, msg])
-        elif line.startswith('msgctxt'):
-            if self.messages:
-                self._add_message()
-            self.in_msgid = self.in_msgstr = False
-            self.context.append(line[7:].lstrip())
-        elif line.startswith('"'):
-            if self.in_msgid:
-                self.messages[-1] += u'\n' + line.rstrip()
-            elif self.in_msgstr:
-                self.translations[-1][1] += u'\n' + line.rstrip()
-            elif self.in_msgctxt:
-                self.context.append(line.rstrip())
-
-    def _process_comment(self, line):
-
-        self.in_msgid = self.in_msgstr = False
-        if self.messages and self.translations:
-            self._add_message()
-        if line[1:].startswith(':'):
-            for location in line[2:].lstrip().split():
-                pos = location.rfind(':')
-                if pos >= 0:
-                    try:
-                        lineno = int(location[pos + 1:])
-                    except ValueError:
-                        continue
-                    self.locations.append((location[:pos], lineno))
-                else:
-                    self.locations.append((location, None))
-        elif line[1:].startswith(','):
-            for flag in line[2:].lstrip().split(','):
-                self.flags.append(flag.strip())
-        elif line[1:].startswith('.'):
-            # These are called auto-comments
-            comment = line[2:].strip()
-            if comment:  # Just check that we're not adding empty comments
-                self.auto_comments.append(comment)
-        else:
-            # These are called user comments
-            self.user_comments.append(line[1:].strip())
-
-    def parse(self, fileobj):
-        """
-        Reads from the file-like object `fileobj` and adds any po file
-        units found in it to the `Catalog` supplied to the constructor.
-        """
-
-        for lineno, line in enumerate(fileobj.readlines()):
-            line = line.strip()
-            if not isinstance(line, text_type):
-                line = line.decode(self.catalog.charset)
-            if line.startswith('#'):
-                if line[1:].startswith('~'):
-                    self.obsolete = True
-                    self._process_message_line(lineno, line[2:].lstrip())
-                else:
-                    self._process_comment(line)
-            else:
-                self._process_message_line(lineno, line)
-
-        if self.messages:
-            self._add_message()
-
-        # No actual messages found, but there was some info in comments, from which
-        # we'll construct an empty header message
-        elif not self.counter and (self.flags or self.user_comments or self.auto_comments):
-            self.messages.append(u'')
-            self.translations.append([0, u''])
-            self._add_message()
-
-
 def read_po(fileobj, locale=None, domain=None, ignore_obsolete=False, charset=None):
     """Read messages from a ``gettext`` PO (portable object) file from the given
     file-like object and return a `Catalog`.
@@ -278,8 +120,139 @@ def read_po(fileobj, locale=None, domain=None, ignore_obsolete=False, charset=No
     :param charset: the character set of the catalog.
     """
     catalog = Catalog(locale=locale, domain=domain, charset=charset)
-    parser = PoFileParser(catalog, ignore_obsolete)
-    parser.parse(fileobj)
+
+    counter = [0]
+    offset = [0]
+    messages = []
+    translations = []
+    locations = []
+    flags = []
+    user_comments = []
+    auto_comments = []
+    obsolete = [False]
+    context = []
+    in_msgid = [False]
+    in_msgstr = [False]
+    in_msgctxt = [False]
+
+    def _add_message():
+        translations.sort()
+        if len(messages) > 1:
+            msgid = tuple([denormalize(m) for m in messages])
+        else:
+            msgid = denormalize(messages[0])
+        if isinstance(msgid, (list, tuple)):
+            string = []
+            for idx in range(catalog.num_plurals):
+                try:
+                    string.append(translations[idx])
+                except IndexError:
+                    string.append((idx, ''))
+            string = tuple([denormalize(t[1]) for t in string])
+        else:
+            string = denormalize(translations[0][1])
+        if context:
+            msgctxt = denormalize('\n'.join(context))
+        else:
+            msgctxt = None
+        message = Message(msgid, string, list(locations), set(flags),
+                          auto_comments, user_comments, lineno=offset[0] + 1,
+                          context=msgctxt)
+        if obsolete[0]:
+            if not ignore_obsolete:
+                catalog.obsolete[msgid] = message
+        else:
+            catalog[msgid] = message
+        del messages[:]
+        del translations[:]
+        del context[:]
+        del locations[:]
+        del flags[:]
+        del auto_comments[:]
+        del user_comments[:]
+        obsolete[0] = False
+        counter[0] += 1
+
+    def _process_message_line(lineno, line):
+        if line.startswith('msgid_plural'):
+            in_msgid[0] = True
+            msg = line[12:].lstrip()
+            messages.append(msg)
+        elif line.startswith('msgid'):
+            in_msgid[0] = True
+            offset[0] = lineno
+            txt = line[5:].lstrip()
+            if messages:
+                _add_message()
+            messages.append(txt)
+        elif line.startswith('msgstr'):
+            in_msgid[0] = False
+            in_msgstr[0] = True
+            msg = line[6:].lstrip()
+            if msg.startswith('['):
+                idx, msg = msg[1:].split(']', 1)
+                translations.append([int(idx), msg.lstrip()])
+            else:
+                translations.append([0, msg])
+        elif line.startswith('msgctxt'):
+            if messages:
+                _add_message()
+            in_msgid[0] = in_msgstr[0] = False
+            context.append(line[7:].lstrip())
+        elif line.startswith('"'):
+            if in_msgid[0]:
+                messages[-1] += u'\n' + line.rstrip()
+            elif in_msgstr[0]:
+                translations[-1][1] += u'\n' + line.rstrip()
+            elif in_msgctxt[0]:
+                context.append(line.rstrip())
+
+    for lineno, line in enumerate(fileobj.readlines()):
+        line = line.strip()
+        if not isinstance(line, text_type):
+            line = line.decode(catalog.charset)
+        if line.startswith('#'):
+            in_msgid[0] = in_msgstr[0] = False
+            if messages and translations:
+                _add_message()
+            if line[1:].startswith(':'):
+                for location in line[2:].lstrip().split():
+                    pos = location.rfind(':')
+                    if pos >= 0:
+                        try:
+                            lineno = int(location[pos + 1:])
+                        except ValueError:
+                            continue
+                        locations.append((location[:pos], lineno))
+                    else:
+                        locations.append((location, None))
+            elif line[1:].startswith(','):
+                for flag in line[2:].lstrip().split(','):
+                    flags.append(flag.strip())
+            elif line[1:].startswith('~'):
+                obsolete[0] = True
+                _process_message_line(lineno, line[2:].lstrip())
+            elif line[1:].startswith('.'):
+                # These are called auto-comments
+                comment = line[2:].strip()
+                if comment:  # Just check that we're not adding empty comments
+                    auto_comments.append(comment)
+            else:
+                # These are called user comments
+                user_comments.append(line[1:].strip())
+        else:
+            _process_message_line(lineno, line)
+
+    if messages:
+        _add_message()
+
+    # No actual messages found, but there was some info in comments, from which
+    # we'll construct an empty header message
+    elif not counter[0] and (flags or user_comments or auto_comments):
+        messages.append(u'')
+        translations.append([0, u''])
+        _add_message()
+
     return catalog
 
 
diff --git a/babel/numbers.py b/babel/numbers.py
index a5d30ef..899fabb 100644
--- a/babel/numbers.py
+++ b/babel/numbers.py
@@ -22,7 +22,7 @@ import re
 from datetime import date as date_, datetime as datetime_
 
 from babel.core import default_locale, Locale, get_global
-from babel._compat import decimal
+from babel._compat import Decimal, InvalidOperation, ROUND_HALF_EVEN
 
 
 LC_NUMERIC = default_locale('LC_NUMERIC')
@@ -437,9 +437,9 @@ def parse_decimal(string, locale=LC_NUMERIC):
     """
     locale = Locale.parse(locale)
     try:
-        return decimal.Decimal(string.replace(get_group_symbol(locale), '')
-                               .replace(get_decimal_symbol(locale), '.'))
-    except decimal.InvalidOperation:
+        return Decimal(string.replace(get_group_symbol(locale), '')
+                       .replace(get_decimal_symbol(locale), '.'))
+    except InvalidOperation:
         raise NumberFormatError('%r is not a valid decimal number' % string)
 
 
@@ -568,8 +568,8 @@ class NumberPattern(object):
 
     def apply(self, value, locale, currency=None, force_frac=None):
         frac_prec = force_frac or self.frac_prec
-        if not isinstance(value, decimal.Decimal):
-            value = decimal.Decimal(str(value))
+        if not isinstance(value, Decimal):
+            value = Decimal(str(value))
         value = value.scaleb(self.scale)
         is_negative = int(value.is_signed())
         if self.exp_prec:  # Scientific notation
@@ -605,8 +605,8 @@ class NumberPattern(object):
             if sep:
                 number += get_decimal_symbol(locale) + b
         else:  # A normal number pattern
-            precision = decimal.Decimal('1.' + '1' * frac_prec[1])
-            rounded = value.quantize(precision)
+            precision = Decimal('1.' + '1' * frac_prec[1])
+            rounded = value.quantize(precision, ROUND_HALF_EVEN)
             a, sep, b = str(abs(rounded)).partition(".")
             number = (self._format_int(a, self.int_prec[0],
                                        self.int_prec[1], locale) +
@@ -643,7 +643,7 @@ class NumberPattern(object):
     def _format_significant(self, value, minimum, maximum):
         exp = value.adjusted()
         scale = maximum - 1 - exp
-        digits = str(value.scaleb(scale).quantize(decimal.Decimal(1)))
+        digits = str(value.scaleb(scale).quantize(Decimal(1), ROUND_HALF_EVEN))
         if scale <= 0:
             result = digits + '0' * -scale
         else:
diff --git a/babel/plural.py b/babel/plural.py
index 0b8e425..980629d 100644
--- a/babel/plural.py
+++ b/babel/plural.py
@@ -11,7 +11,7 @@
 import re
 import sys
 
-from babel._compat import decimal
+from babel._compat import Decimal
 
 
 _plural_tags = ('zero', 'one', 'two', 'few', 'many', 'other')
@@ -33,9 +33,9 @@ def extract_operands(source):
             # 2.6's Decimal cannot convert from float directly
             if sys.version_info < (2, 7):
                 n = str(n)
-            n = decimal.Decimal(n)
+            n = Decimal(n)
 
-    if isinstance(n, decimal.Decimal):
+    if isinstance(n, Decimal):
         dec_tuple = n.as_tuple()
         exp = dec_tuple.exponent
         fraction_digits = dec_tuple.digits[exp:] if exp < 0 else ()
diff --git a/babel/units.py b/babel/units.py
index 1ea5b17..798ade2 100644
--- a/babel/units.py
+++ b/babel/units.py
@@ -80,8 +80,8 @@ def format_unit(value, measurement_unit, length='long', format=None, locale=LC_N
 
     Number formats may be overridden with the ``format`` parameter.
 
-    >>> from babel._compat import decimal
-    >>> format_unit(decimal.Decimal("-42.774"), 'temperature-celsius', 'short', format='#.0', locale='fr')
+    >>> from babel._compat import Decimal
+    >>> format_unit(Decimal("-42.774"), 'temperature-celsius', 'short', format='#.0', locale='fr')
     u'-42,8 \\xb0C'
 
     The locale's usual pluralization rules are respected.
diff --git a/babel/util.py b/babel/util.py
index aeb9a5f..996f902 100644
--- a/babel/util.py
+++ b/babel/util.py
@@ -95,6 +95,29 @@ def parse_encoding(fp):
         fp.seek(pos)
 
 
+PYTHON_FUTURE_IMPORT_re = re.compile(
+    r'from\s+__future__\s+import\s+\(*(.+)\)*')
+
+
+def parse_future_flags(fp, encoding='latin-1'):
+    """Parse the compiler flags by :mod:`__future__` from the given Python
+    code.
+    """
+    import __future__
+    pos = fp.tell()
+    fp.seek(0)
+    flags = 0
+    try:
+        body = fp.read().decode(encoding)
+        for m in PYTHON_FUTURE_IMPORT_re.finditer(body):
+            names = [x.strip() for x in m.group(1).split(',')]
+            for name in names:
+                flags |= getattr(__future__, name).compiler_flag
+    finally:
+        fp.seek(pos)
+    return flags
+
+
 def pathmatch(pattern, filename):
     """Extended pathname pattern matching.
 
diff --git a/docs/numbers.rst b/docs/numbers.rst
index 1443b7c..fbba1f3 100644
--- a/docs/numbers.rst
+++ b/docs/numbers.rst
@@ -86,58 +86,6 @@ the specification. The following table is just a relatively brief overview.
   +----------+-----------------------------------------------------------------+
 
 
-Rounding Modes
-==============
-
-Since Babel makes full use of Python's `Decimal`_ type to perform number
-rounding before formatting, users have the chance to control the rounding mode
-and other configurable parameters through the active `Context`_ instance.
-
-By default, Python rounding mode is ``ROUND_HALF_EVEN`` which complies with
-`UTS #35 section 3.3`_.  Yet, the caller has the opportunity to tweak the
-current context before formatting a number or currency:
-
-.. code-block:: pycon
-
-    >>> from babel.numbers import decimal, format_decimal
-    >>> with decimal.localcontext(decimal.Context(rounding=decimal.ROUND_DOWN)):
-    >>>    txt = format_decimal(123.99, format='#', locale='en_US')
-    >>> txt
-    u'123'
-
-It is also possible to use ``decimal.setcontext`` or directly modifying the
-instance returned by ``decimal.getcontext``.  However, using a context manager
-is always more convenient due to the automatic restoration and the ability to
-nest them.
-
-Whatever mechanism is chosen, always make use of the ``decimal`` module imported
-from ``babel.numbers``.  For efficiency reasons, Babel uses the fastest decimal
-implementation available, such as `cdecimal`_.  These various implementation
-offer an identical API, but their types and instances do **not** interoperate
-with each other.
-
-For example, the previous example can be slightly modified to generate
-unexpected results on Python 2.7, with the `cdecimal`_ module installed:
-
-.. code-block:: pycon
-
-    >>> from decimal import localcontext, Context, ROUND_DOWN
-    >>> from babel.numbers import format_decimal
-    >>> with localcontext(Context(rounding=ROUND_DOWN)):
-    >>>    txt = format_decimal(123.99, format='#', locale='en_US')
-    >>> txt
-    u'124'
-
-Changing other parameters such as the precision may also alter the results of
-the number formatting functions.  Remember to test your code to make sure it
-behaves as desired.
-
-.. _Decimal: https://docs.python.org/3/library/decimal.html#decimal-objects
-.. _Context: https://docs.python.org/3/library/decimal.html#context-objects
-.. _`UTS #35 section 3.3`: http://www.unicode.org/reports/tr35/tr35-numbers.html#Formatting
-.. _cdecimal: https://pypi.python.org/pypi/cdecimal
-
-
 Parsing Numbers
 ===============
 
diff --git a/scripts/download_import_cldr.py b/scripts/download_import_cldr.py
index bfcab5f..4da3423 100755
--- a/scripts/download_import_cldr.py
+++ b/scripts/download_import_cldr.py
@@ -12,9 +12,9 @@ except ImportError:
     from urllib import urlretrieve
 
 
-URL = 'http://unicode.org/Public/cldr/29/core.zip'
-FILENAME = 'core-29.zip'
-FILESUM = '44d117e6e591a8f9655602ff0abdee105df3cabe'
+URL = 'http://unicode.org/Public/cldr/28/core.zip'
+FILENAME = 'core-28.zip'
+FILESUM = 'bc545b4c831e1987ea931b04094d7b9fc59ec3d8'
 BLKSIZE = 131072
 
 
diff --git a/scripts/import_cldr.py b/scripts/import_cldr.py
index b804119..8de7043 100755
--- a/scripts/import_cldr.py
+++ b/scripts/import_cldr.py
@@ -845,8 +845,6 @@ def parse_day_period_rules(tree):
             locales = rules.attrib["locales"].split()
             for rule in rules.findall("dayPeriodRule"):
                 type = rule.attrib["type"]
-                if type in ("am", "pm"):  # These fixed periods are handled separately by `get_period_id`
-                    continue
                 rule = _compact_dict(dict(
                     (key, _time_to_seconds_past_midnight(rule.attrib.get(key)))
                     for key in ("after", "at", "before", "from", "to")


Test patch:
diff --git a/tests/messages/test_extract.py b/tests/messages/test_extract.py
index 9d78d92..22ea1cd 100644
--- a/tests/messages/test_extract.py
+++ b/tests/messages/test_extract.py
@@ -498,3 +498,13 @@ msg = _('')
             return [(1, None, (), ())]
         for x in extract.extract(arbitrary_extractor, BytesIO(b"")):
             assert x[0] == 1
+
+    def test_future(self):
+        buf = BytesIO(br"""
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+nbsp = _('\xa0')
+""")
+        messages = list(extract.extract('python', buf,
+                                        extract.DEFAULT_KEYWORDS, [], {}))
+        assert messages[0][1] == u'\xa0'
diff --git a/tests/messages/test_pofile.py b/tests/messages/test_pofile.py
index 4c525b5..1bac360 100644
--- a/tests/messages/test_pofile.py
+++ b/tests/messages/test_pofile.py
@@ -29,14 +29,6 @@ msgstr "Voh"''')
         catalog = pofile.read_po(buf, locale='en_US')
         self.assertEqual(Locale('en', 'US'), catalog.locale)
 
-    def test_locale_gets_overridden_by_file(self):
-        buf = StringIO(r'''
-msgid ""
-msgstr ""
-"Language: en_US\n"''')
-        catalog = pofile.read_po(buf, locale='de')
-        self.assertEqual(Locale('en', 'US'), catalog.locale)
-
     def test_preserve_domain(self):
         buf = StringIO(r'''msgid "foo"
 msgstr "Voh"''')
@@ -120,7 +112,6 @@ msgstr ""
 "POT-Creation-Date: 2007-09-27 11:19+0700\n"
 "PO-Revision-Date: 2007-09-27 21:42-0700\n"
 "Last-Translator: John <cleese@bavaria.de>\n"
-"Language: de\n"
 "Language-Team: German Lang <de@babel.org>\n"
 "Plural-Forms: nplurals=2; plural=(n != 1)\n"
 "MIME-Version: 1.0\n"
@@ -137,7 +128,6 @@ msgstr ""
                                   tzinfo=FixedOffsetTimezone(7 * 60)),
                          catalog.creation_date)
         self.assertEqual(u'John <cleese@bavaria.de>', catalog.last_translator)
-        self.assertEqual(Locale('de'), catalog.locale)
         self.assertEqual(u'German Lang <de@babel.org>', catalog.language_team)
         self.assertEqual(u'iso-8859-2', catalog.charset)
         self.assertEqual(True, list(catalog)[0].fuzzy)
@@ -174,48 +164,6 @@ msgstr "Bahr"
         self.assertEqual(1, len(catalog))
         self.assertEqual(0, len(catalog.obsolete))
 
-    def test_multi_line_obsolete_message(self):
-        buf = StringIO(r'''# This is an obsolete message
-#~ msgid ""
-#~ "foo"
-#~ "foo"
-#~ msgstr ""
-#~ "Voh"
-#~ "Vooooh"
-
-# This message is not obsolete
-#: main.py:1
-msgid "bar"
-msgstr "Bahr"
-''')
-        catalog = pofile.read_po(buf)
-        self.assertEqual(1, len(catalog.obsolete))
-        message = catalog.obsolete[u'foofoo']
-        self.assertEqual(u'foofoo', message.id)
-        self.assertEqual(u'VohVooooh', message.string)
-        self.assertEqual(['This is an obsolete message'], message.user_comments)
-
-    def test_unit_following_multi_line_obsolete_message(self):
-        buf = StringIO(r'''# This is an obsolete message
-#~ msgid ""
-#~ "foo"
-#~ "fooooooo"
-#~ msgstr ""
-#~ "Voh"
-#~ "Vooooh"
-
-# This message is not obsolete
-#: main.py:1
-msgid "bar"
-msgstr "Bahr"
-''')
-        catalog = pofile.read_po(buf)
-        self.assertEqual(1, len(catalog))
-        message = catalog[u'bar']
-        self.assertEqual(u'bar', message.id)
-        self.assertEqual(u'Bahr', message.string)
-        self.assertEqual(['This message is not obsolete'], message.user_comments)
-
     def test_with_context(self):
         buf = BytesIO(b'''# Some string in the menu
 #: main.py:1
diff --git a/tests/test_numbers.py b/tests/test_numbers.py
index 2593cc0..24b274c 100644
--- a/tests/test_numbers.py
+++ b/tests/test_numbers.py
@@ -17,7 +17,7 @@ import pytest
 from datetime import date
 
 from babel import numbers
-from babel._compat import decimal
+from babel._compat import Decimal
 
 
 class FormatDecimalTestCase(unittest.TestCase):
@@ -94,16 +94,16 @@ class FormatDecimalTestCase(unittest.TestCase):
 
     def test_decimals(self):
         """Test significant digits patterns"""
-        self.assertEqual(numbers.format_decimal(decimal.Decimal('1.2345'),
+        self.assertEqual(numbers.format_decimal(Decimal('1.2345'),
                                                 '#.00', locale='en_US'),
                          '1.23')
-        self.assertEqual(numbers.format_decimal(decimal.Decimal('1.2345000'),
+        self.assertEqual(numbers.format_decimal(Decimal('1.2345000'),
                                                 '#.00', locale='en_US'),
                          '1.23')
-        self.assertEqual(numbers.format_decimal(decimal.Decimal('1.2345000'),
+        self.assertEqual(numbers.format_decimal(Decimal('1.2345000'),
                                                 '@@', locale='en_US'),
                          '1.2')
-        self.assertEqual(numbers.format_decimal(decimal.Decimal('12345678901234567890.12345'),
+        self.assertEqual(numbers.format_decimal(Decimal('12345678901234567890.12345'),
                                                 '#.00', locale='en_US'),
                          '12345678901234567890.12')
 
@@ -136,7 +136,7 @@ class FormatDecimalTestCase(unittest.TestCase):
         self.assertEqual(fmt, '1.23E02 m/s')
         fmt = numbers.format_scientific(0.012345, '#.##E00 m/s', locale='en_US')
         self.assertEqual(fmt, '1.23E-02 m/s')
-        fmt = numbers.format_scientific(decimal.Decimal('12345'), '#.##E+00 m/s',
+        fmt = numbers.format_scientific(Decimal('12345'), '#.##E+00 m/s',
                                         locale='en_US')
         self.assertEqual(fmt, '1.23E+04 m/s')
         # 0 (see ticket #99)
@@ -146,7 +146,7 @@ class FormatDecimalTestCase(unittest.TestCase):
     def test_formatting_of_very_small_decimals(self):
         # previously formatting very small decimals could lead to a type error
         # because the Decimal->string conversion was too simple (see #214)
-        number = decimal.Decimal("7E-7")
+        number = Decimal("7E-7")
         fmt = numbers.format_decimal(number, format="@@@", locale='en_US')
         self.assertEqual('0.000000700', fmt)
 
@@ -154,9 +154,9 @@ class FormatDecimalTestCase(unittest.TestCase):
 class NumberParsingTestCase(unittest.TestCase):
 
     def test_can_parse_decimals(self):
-        self.assertEqual(decimal.Decimal('1099.98'),
+        self.assertEqual(Decimal('1099.98'),
                          numbers.parse_decimal('1,099.98', locale='en_US'))
-        self.assertEqual(decimal.Decimal('1099.98'),
+        self.assertEqual(Decimal('1099.98'),
                          numbers.parse_decimal('1.099,98', locale='de'))
         self.assertRaises(numbers.NumberFormatError,
                           lambda: numbers.parse_decimal('2,109,998', locale='de'))
@@ -302,8 +302,8 @@ def test_parse_number():
 
 def test_parse_decimal():
     assert (numbers.parse_decimal('1,099.98', locale='en_US')
-            == decimal.Decimal('1099.98'))
-    assert numbers.parse_decimal('1.099,98', locale='de') == decimal.Decimal('1099.98')
+            == Decimal('1099.98'))
+    assert numbers.parse_decimal('1.099,98', locale='de') == Decimal('1099.98')
 
     with pytest.raises(numbers.NumberFormatError) as excinfo:
         numbers.parse_decimal('2,109,998', locale='de')
diff --git a/tests/test_plural.py b/tests/test_plural.py
index 6406af8..60a2de4 100644
--- a/tests/test_plural.py
+++ b/tests/test_plural.py
@@ -14,7 +14,7 @@ import unittest
 import pytest
 
 from babel import plural, localedata
-from babel._compat import decimal
+from babel._compat import Decimal
 
 
 def test_plural_rule():
@@ -34,29 +34,29 @@ def test_plural_rule_operands_i():
 
 def test_plural_rule_operands_v():
     rule = plural.PluralRule({'one': 'v is 2'})
-    assert rule(decimal.Decimal('1.20')) == 'one'
-    assert rule(decimal.Decimal('1.2')) == 'other'
+    assert rule(Decimal('1.20')) == 'one'
+    assert rule(Decimal('1.2')) == 'other'
     assert rule(2) == 'other'
 
 
 def test_plural_rule_operands_w():
     rule = plural.PluralRule({'one': 'w is 2'})
-    assert rule(decimal.Decimal('1.23')) == 'one'
-    assert rule(decimal.Decimal('1.20')) == 'other'
+    assert rule(Decimal('1.23')) == 'one'
+    assert rule(Decimal('1.20')) == 'other'
     assert rule(1.2) == 'other'
 
 
 def test_plural_rule_operands_f():
     rule = plural.PluralRule({'one': 'f is 20'})
-    assert rule(decimal.Decimal('1.23')) == 'other'
-    assert rule(decimal.Decimal('1.20')) == 'one'
+    assert rule(Decimal('1.23')) == 'other'
+    assert rule(Decimal('1.20')) == 'one'
     assert rule(1.2) == 'other'
 
 
 def test_plural_rule_operands_t():
     rule = plural.PluralRule({'one': 't = 5'})
-    assert rule(decimal.Decimal('1.53')) == 'other'
-    assert rule(decimal.Decimal('1.50')) == 'one'
+    assert rule(Decimal('1.53')) == 'other'
+    assert rule(Decimal('1.50')) == 'one'
     assert rule(1.5) == 'one'
 
 
@@ -253,9 +253,9 @@ EXTRACT_OPERANDS_TESTS = (
 
 @pytest.mark.parametrize('source,n,i,v,w,f,t', EXTRACT_OPERANDS_TESTS)
 def test_extract_operands(source, n, i, v, w, f, t):
-    source = decimal.Decimal(source) if isinstance(source, str) else source
+    source = Decimal(source) if isinstance(source, str) else source
     assert (plural.extract_operands(source) ==
-            decimal.Decimal(n), i, v, w, f, t)
+            Decimal(n), i, v, w, f, t)
 
 
 @pytest.mark.parametrize('locale', ('ru', 'pl'))
diff --git a/tests/test_smoke.py b/tests/test_smoke.py
index 3993f60..eda10ed 100644
--- a/tests/test_smoke.py
+++ b/tests/test_smoke.py
@@ -11,7 +11,7 @@ import pytest
 from babel import Locale
 from babel import dates
 from babel import numbers
-from babel._compat import decimal
+from babel._compat import Decimal
 
 
 @pytest.mark.all_locales
@@ -28,8 +28,8 @@ def test_smoke_dates(locale):
 def test_smoke_numbers(locale):
     locale = Locale.parse(locale)
     for number in (
-        decimal.Decimal("-33.76"),  # Negative Decimal
-        decimal.Decimal("13.37"),  # Positive Decimal
+        Decimal("-33.76"),  # Negative Decimal
+        Decimal("13.37"),  # Positive Decimal
         1.2 - 1.0,  # Inaccurate float
         10,  # Plain old integer
         0,  # Zero