Repository: python-babel/babel (python)

Issue:
get_timezone_name returning '∅∅∅' (CLDR-'no inheritance marker') as timezone. Babel 2.3.4/Python 2.7.7
`get_timezone_name` is returning `'∅∅∅'`  (3 empty set unicode characters) for several english locales, including `en_GB` and `en_IE`. The same results occur when using the `format_time` method. This only happens with the short format. 

**Steps to Reproduce & Actual Results:**

```
>>> tz = pytz.timezone('America/Los_Angeles')
>>> t = tz.localize(datetime(2016,1,6,7))
>>> print dates.format_time(t, format='long', locale='en_US')
7:00:00 AM PST
>>> print dates.format_time(t, format='long', locale='en_GB')
07:00:00 ∅∅∅
>>> print dates.get_timezone_name(t, width='short', locale='en_US')
PST
>>> print dates.get_timezone_name(t, width='short', locale='en_GB')
∅∅∅
```

**Expected Results:**

From the docs: "Note that short format is currently not supported for all timezones and all locales. This is partially because not every timezone has a short code in every locale. In that case it currently falls back to the long format."

```
>>> print dates.format_time(t, format='long', locale='en_GB')
07:00:00 Pacific Standard Time
>>> print dates.get_timezone_name(t, width='short', locale='en_GB')
Pacific Standard Time
```

**Additional Information:**

In `get_timezone_name` - putting a breakpoint on 
https://github.com/python-babel/babel/blob/master/babel/dates.py#L645

I get the following results:

```
metazone_info['short']['generic'] = u'\u2205\u2205\u2205'
metazone_info['short']['daylight'] = u'\u2205\u2205\u2205'
metazone_info['short']['standard'] = u'\u2205\u2205\u2205'
```

According to the CLDR, this is the 'no inheritance marker' when doing a lookup on metazone data. 
http://www.unicode.org/reports/tr35/tr35-dates.html#Metazone_Names

"If a given short metazone form is known NOT to be understood in a given locale and the parent locale has this value such that it would normally be inherited, the inheritance of this value can be explicitly disabled by use of the 'no inheritance marker' as the value, which is 3 simultaneous empty set characters ( U+2205 )."

To best align with the babel API docs, in the case where the CLDR data contains the 'no inheritance marker', it feels like the long form should be returned. 


Pull request:
Properly fall back to `long` metazone names when short forms are no-inherit
Thanks to @stringtheory for the bug report!

Fixes #428


Patch:
diff --git a/babel/dates.py b/babel/dates.py
index 523e456..c84ca48 100644
--- a/babel/dates.py
+++ b/babel/dates.py
@@ -30,6 +30,16 @@ from babel.util import UTC, LOCALTZ
 from babel._compat import string_types, integer_types, number_types
 
 
+# "If a given short metazone form is known NOT to be understood in a given
+#  locale and the parent locale has this value such that it would normally
+#  be inherited, the inheritance of this value can be explicitly disabled by
+#  use of the 'no inheritance marker' as the value, which is 3 simultaneous [sic]
+#  empty set characters ( U+2205 )."
+#  - http://www.unicode.org/reports/tr35/tr35-dates.html#Metazone_Names
+
+NO_INHERITANCE_MARKER = u'\u2205\u2205\u2205'
+
+
 LC_TIME = default_locale('LC_TIME')
 
 # Aliases for use in scopes where the modules are shadowed by local variables
@@ -642,8 +652,13 @@ def get_timezone_name(dt_or_tzinfo=None, width='long', uncommon=False,
     if metazone:
         metazone_info = locale.meta_zones.get(metazone, {})
         if width in metazone_info:
-            if zone_variant in metazone_info[width]:
-                return metazone_info[width][zone_variant]
+            name = metazone_info[width].get(zone_variant)
+            if width == 'short' and name == NO_INHERITANCE_MARKER:
+                # If the short form is marked no-inheritance,
+                # try to fall back to the long name instead.
+                name = metazone_info.get('long', {}).get(zone_variant)
+            if name:
+                return name
 
     # If we have a concrete datetime, we assume that the result can't be
     # independent of daylight savings time, so we return the GMT offset


Test patch:
diff --git a/tests/test_dates.py b/tests/test_dates.py
index b0d093e..f74cd39 100644
--- a/tests/test_dates.py
+++ b/tests/test_dates.py
@@ -15,9 +15,12 @@ import calendar
 from datetime import date, datetime, time, timedelta
 import unittest
 
+import pytest
+import pytz
 from pytz import timezone
 
 from babel import dates, Locale
+from babel.dates import NO_INHERITANCE_MARKER
 from babel.util import FixedOffsetTimezone
 
 
@@ -761,3 +764,22 @@ def test_format_current_moment(monkeypatch):
     # Freeze time! Well, some of it anyway.
     monkeypatch.setattr(datetime_module, "datetime", frozen_datetime)
     assert dates.format_datetime(locale="en_US") == dates.format_datetime(frozen_instant, locale="en_US")
+
+
+@pytest.mark.all_locales
+def test_no_inherit_metazone_marker_never_in_output(locale):
+    # See: https://github.com/python-babel/babel/issues/428
+    tz = pytz.timezone('America/Los_Angeles')
+    t = tz.localize(datetime(2016, 1, 6, 7))
+    assert NO_INHERITANCE_MARKER not in dates.format_time(t, format='long', locale=locale)
+    assert NO_INHERITANCE_MARKER not in dates.get_timezone_name(t, width='short', locale=locale)
+
+
+def test_no_inherit_metazone_formatting():
+    # See: https://github.com/python-babel/babel/issues/428
+    tz = pytz.timezone('America/Los_Angeles')
+    t = tz.localize(datetime(2016, 1, 6, 7))
+    assert dates.format_time(t, format='long', locale='en_US') == "7:00:00 AM PST"
+    assert dates.format_time(t, format='long', locale='en_GB') == "07:00:00 Pacific Standard Time"
+    assert dates.get_timezone_name(t, width='short', locale='en_US') == "PST"
+    assert dates.get_timezone_name(t, width='short', locale='en_GB') == "Pacific Standard Time"