Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle ZoneInfo objects in get_timezone_location, get_timezone_name #741

Merged
merged 15 commits into from Nov 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Expand Up @@ -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:
Expand Down
26 changes: 17 additions & 9 deletions babel/dates.py
Expand Up @@ -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).
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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:
Expand Down
129 changes: 89 additions & 40 deletions tests/test_dates.py
Expand Up @@ -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
akx marked this conversation as resolved.
Show resolved Hide resolved


class DateTimeFormatTestCase(unittest.TestCase):

def test_quarter_format(self):
Expand Down Expand Up @@ -583,60 +600,92 @@ 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') ==
u'Canada (St. John’s) Time')
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'))

Expand Down
1 change: 1 addition & 0 deletions tox.ini
Expand Up @@ -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
Expand Down