diff --git a/.travis.yml b/.travis.yml index 9650f674c..57431d7e9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,7 +40,7 @@ matrix: install: - bash .ci/deps.${TRAVIS_OS_NAME}.sh - pip install --upgrade pip - - pip install --upgrade $CDECIMAL pytest==4.3.1 pytest-cov==2.6.1 freezegun==0.3.12 + - pip install --upgrade $CDECIMAL pytest==4.3.1 pytest-cov==2.6.1 freezegun==0.3.12 'backports.zoneinfo;python_version>="3.6" and python_version<"3.9"' - pip install --editable . script: diff --git a/babel/dates.py b/babel/dates.py index f1bd66faf..1775029fa 100644 --- a/babel/dates.py +++ b/babel/dates.py @@ -76,6 +76,21 @@ def _get_dt_and_tzinfo(dt_or_tzinfo): return dt, tzinfo +def _get_tz_name(dt_or_tzinfo): + """ + Get the timezone name out of a time, datetime, or tzinfo object. + + :rtype: str + """ + dt, tzinfo = _get_dt_and_tzinfo(dt_or_tzinfo) + if hasattr(tzinfo, 'zone'): # pytz object + return tzinfo.zone + elif hasattr(tzinfo, 'key') and tzinfo.key is not None: # ZoneInfo object + return tzinfo.key + else: + return tzinfo.tzname(dt or datetime.utcnow()) + + def _get_datetime(instant): """ Get a datetime out of an "instant" (date, time, datetime, number). @@ -500,13 +515,9 @@ def get_timezone_location(dt_or_tzinfo=None, locale=LC_TIME, return_city=False): :return: the localized timezone name using location format """ - dt, tzinfo = _get_dt_and_tzinfo(dt_or_tzinfo) locale = Locale.parse(locale) - if hasattr(tzinfo, 'zone'): - zone = tzinfo.zone - else: - zone = tzinfo.tzname(dt or datetime.utcnow()) + zone = _get_tz_name(dt_or_tzinfo) # Get the canonical time-zone code zone = get_global('zone_aliases').get(zone, zone) @@ -619,10 +630,7 @@ def get_timezone_name(dt_or_tzinfo=None, width='long', uncommon=False, dt, tzinfo = _get_dt_and_tzinfo(dt_or_tzinfo) locale = Locale.parse(locale) - if hasattr(tzinfo, 'zone'): - zone = tzinfo.zone - else: - zone = tzinfo.tzname(dt) + zone = _get_tz_name(dt_or_tzinfo) if zone_variant is None: if dt is None: diff --git a/tests/test_dates.py b/tests/test_dates.py index 5be0d16a1..423f737de 100644 --- a/tests/test_dates.py +++ b/tests/test_dates.py @@ -24,6 +24,23 @@ from babel.util import FixedOffsetTimezone +@pytest.fixture(params=["pytz.timezone", "zoneinfo.ZoneInfo"]) +def timezone_getter(request): + if request.param == "pytz.timezone": + return timezone + elif request.param == "zoneinfo.ZoneInfo": + try: + import zoneinfo + except ImportError: + try: + from backports import zoneinfo + except ImportError: + pytest.skip("zoneinfo not available") + return zoneinfo.ZoneInfo + else: + raise NotImplementedError + + class DateTimeFormatTestCase(unittest.TestCase): def test_quarter_format(self): @@ -583,8 +600,8 @@ def test_get_timezone_gmt(): assert dates.get_timezone_gmt(dt, 'long', locale='fr_FR') == u'UTC-07:00' -def test_get_timezone_location(): - tz = timezone('America/St_Johns') +def test_get_timezone_location(timezone_getter): + tz = timezone_getter('America/St_Johns') assert (dates.get_timezone_location(tz, locale='de_DE') == u"Kanada (St. John\u2019s) Zeit") assert (dates.get_timezone_location(tz, locale='en') == @@ -592,51 +609,83 @@ def test_get_timezone_location(): assert (dates.get_timezone_location(tz, locale='en', return_city=True) == u'St. John’s') - tz = timezone('America/Mexico_City') + tz = timezone_getter('America/Mexico_City') assert (dates.get_timezone_location(tz, locale='de_DE') == u'Mexiko (Mexiko-Stadt) Zeit') - tz = timezone('Europe/Berlin') + tz = timezone_getter('Europe/Berlin') assert (dates.get_timezone_location(tz, locale='de_DE') == u'Deutschland (Berlin) Zeit') -def test_get_timezone_name(): - dt = time(15, 30, tzinfo=timezone('America/Los_Angeles')) - assert (dates.get_timezone_name(dt, locale='en_US') == - u'Pacific Standard Time') - assert (dates.get_timezone_name(dt, locale='en_US', return_zone=True) == - u'America/Los_Angeles') - assert dates.get_timezone_name(dt, width='short', locale='en_US') == u'PST' - - tz = timezone('America/Los_Angeles') - assert dates.get_timezone_name(tz, locale='en_US') == u'Pacific Time' - assert dates.get_timezone_name(tz, 'short', locale='en_US') == u'PT' - - tz = timezone('Europe/Berlin') - assert (dates.get_timezone_name(tz, locale='de_DE') == - u'Mitteleurop\xe4ische Zeit') - assert (dates.get_timezone_name(tz, locale='pt_BR') == - u'Hor\xe1rio da Europa Central') - - tz = timezone('America/St_Johns') - assert dates.get_timezone_name(tz, locale='de_DE') == u'Neufundland-Zeit' - - tz = timezone('America/Los_Angeles') - assert dates.get_timezone_name(tz, locale='en', width='short', - zone_variant='generic') == u'PT' - assert dates.get_timezone_name(tz, locale='en', width='short', - zone_variant='standard') == u'PST' - assert dates.get_timezone_name(tz, locale='en', width='short', - zone_variant='daylight') == u'PDT' - assert dates.get_timezone_name(tz, locale='en', width='long', - zone_variant='generic') == u'Pacific Time' - assert dates.get_timezone_name(tz, locale='en', width='long', - zone_variant='standard') == u'Pacific Standard Time' - assert dates.get_timezone_name(tz, locale='en', width='long', - zone_variant='daylight') == u'Pacific Daylight Time' - - localnow = datetime.utcnow().replace(tzinfo=timezone('UTC')).astimezone(dates.LOCALTZ) +@pytest.mark.parametrize( + "tzname, params, expected", + [ + ("America/Los_Angeles", {"locale": "en_US"}, u"Pacific Time"), + ("America/Los_Angeles", {"width": "short", "locale": "en_US"}, u"PT"), + ("Europe/Berlin", {"locale": "de_DE"}, u"Mitteleurop\xe4ische Zeit"), + ("Europe/Berlin", {"locale": "pt_BR"}, u"Hor\xe1rio da Europa Central"), + ("America/St_Johns", {"locale": "de_DE"}, u"Neufundland-Zeit"), + ( + "America/Los_Angeles", + {"locale": "en", "width": "short", "zone_variant": "generic"}, + u"PT", + ), + ( + "America/Los_Angeles", + {"locale": "en", "width": "short", "zone_variant": "standard"}, + u"PST", + ), + ( + "America/Los_Angeles", + {"locale": "en", "width": "short", "zone_variant": "daylight"}, + u"PDT", + ), + ( + "America/Los_Angeles", + {"locale": "en", "width": "long", "zone_variant": "generic"}, + u"Pacific Time", + ), + ( + "America/Los_Angeles", + {"locale": "en", "width": "long", "zone_variant": "standard"}, + u"Pacific Standard Time", + ), + ( + "America/Los_Angeles", + {"locale": "en", "width": "long", "zone_variant": "daylight"}, + u"Pacific Daylight Time", + ), + ("Europe/Berlin", {"locale": "en_US"}, u"Central European Time"), + ], +) +def test_get_timezone_name_tzinfo(timezone_getter, tzname, params, expected): + tz = timezone_getter(tzname) + assert dates.get_timezone_name(tz, **params) == expected + + +@pytest.mark.parametrize("timezone_getter", ["pytz.timezone"], indirect=True) +@pytest.mark.parametrize( + "tzname, params, expected", + [ + ("America/Los_Angeles", {"locale": "en_US"}, u"Pacific Standard Time"), + ( + "America/Los_Angeles", + {"locale": "en_US", "return_zone": True}, + u"America/Los_Angeles", + ), + ("America/Los_Angeles", {"width": "short", "locale": "en_US"}, u"PST"), + ], +) +def test_get_timezone_name_time_pytz(timezone_getter, tzname, params, expected): + """pytz (by design) can't determine if the time is in DST or not, + so it will always return Standard time""" + dt = time(15, 30, tzinfo=timezone_getter(tzname)) + assert dates.get_timezone_name(dt, **params) == expected + + +def test_get_timezone_name_misc(timezone_getter): + localnow = datetime.utcnow().replace(tzinfo=timezone_getter('UTC')).astimezone(dates.LOCALTZ) assert (dates.get_timezone_name(None, locale='en_US') == dates.get_timezone_name(localnow, locale='en_US')) diff --git a/tox.ini b/tox.ini index eccffea94..c069c5942 100644 --- a/tox.ini +++ b/tox.ini @@ -7,6 +7,7 @@ deps = pytest-cov==2.6.1 cdecimal: m3-cdecimal freezegun==0.3.12 + backports.zoneinfo;python_version>"3.6" and python_version<"3.9" whitelist_externals = make commands = make clean-cldr test passenv = PYTHON_TEST_FLAGS