summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDS/Charlie <82801887+ds-cbo@users.noreply.github.com>2023-01-11 11:34:43 +0100
committerGitHub <noreply@github.com>2023-01-11 12:34:43 +0200
commit14216ed8f90832eed9c8ea24442d5b63da4389eb (patch)
treec6f5fecc12d5c62da148a0840eaf180e74c0b7f4
parent53637ddbacaef2474429b22176091a362ce6567f (diff)
downloadbabel-14216ed8f90832eed9c8ea24442d5b63da4389eb.tar.gz
Implement zoneinfo support and make pytz optional (#940)
-rw-r--r--CHANGES.rst7
-rw-r--r--babel/dates.py151
-rw-r--r--babel/localtime/__init__.py6
-rw-r--r--babel/localtime/_helpers.py42
-rw-r--r--babel/localtime/_unix.py48
-rw-r--r--babel/localtime/_win32.py16
-rw-r--r--babel/support.py21
-rw-r--r--babel/util.py10
-rw-r--r--docs/dates.rst22
-rw-r--r--docs/dev.rst7
-rw-r--r--docs/installation.rst17
-rwxr-xr-xsetup.py3
-rw-r--r--tests/test_date_intervals.py6
-rw-r--r--tests/test_dates.py183
-rw-r--r--tests/test_support.py12
15 files changed, 340 insertions, 211 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index 949d16d..bee5c3f 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,6 +1,13 @@
Babel Changelog
===============
+Unreleased
+----------
+
+* Use `zoneinfo` timezone resolving on python 3.9+, while keeping pytz support
+ for lower versions
+
+
Version 2.11.0
--------------
diff --git a/babel/dates.py b/babel/dates.py
index 27f3fe6..f728961 100644
--- a/babel/dates.py
+++ b/babel/dates.py
@@ -19,20 +19,24 @@ from __future__ import annotations
import re
import warnings
+from typing import TYPE_CHECKING, SupportsInt
+
+try:
+ import pytz
+except ModuleNotFoundError:
+ pytz = None
+ import zoneinfo
+
from bisect import bisect_right
from collections.abc import Iterable
from datetime import date, datetime, time, timedelta, tzinfo
-from typing import TYPE_CHECKING, SupportsInt
-
-import pytz as _pytz
+from babel import localtime
from babel.core import Locale, default_locale, get_global
from babel.localedata import LocaleDataDict
-from babel.util import LOCALTZ, UTC
if TYPE_CHECKING:
from typing_extensions import Literal, TypeAlias
-
_Instant: TypeAlias = date | time | float | None
_PredefinedTimeFormat: TypeAlias = Literal['full', 'long', 'medium', 'short']
_Context: TypeAlias = Literal['format', 'stand-alone']
@@ -48,6 +52,12 @@ if TYPE_CHECKING:
NO_INHERITANCE_MARKER = u'\u2205\u2205\u2205'
+if pytz:
+ UTC = pytz.utc
+else:
+ UTC = zoneinfo.ZoneInfo('UTC')
+LOCALTZ = localtime.LOCALTZ
+
LC_TIME = default_locale('LC_TIME')
# Aliases for use in scopes where the modules are shadowed by local variables
@@ -56,6 +66,24 @@ datetime_ = datetime
time_ = time
+def _localize(tz: tzinfo, dt: datetime) -> datetime:
+ # Support localizing with both pytz and zoneinfo tzinfos
+ # nothing to do
+ if dt.tzinfo is tz:
+ return dt
+
+ if hasattr(tz, 'localize'): # pytz
+ return tz.localize(dt)
+
+ if dt.tzinfo is None:
+ # convert naive to localized
+ return dt.replace(tzinfo=tz)
+
+ # convert timezones
+ return dt.astimezone(tz)
+
+
+
def _get_dt_and_tzinfo(dt_or_tzinfo: _DtOrTzinfo) -> tuple[datetime_ | None, tzinfo]:
"""
Parse a `dt_or_tzinfo` value into a datetime and a tzinfo.
@@ -150,7 +178,7 @@ def _ensure_datetime_tzinfo(datetime: datetime_, tzinfo: tzinfo | None = None) -
If a tzinfo is passed in, the datetime is normalized to that timezone.
- >>> _ensure_datetime_tzinfo(datetime(2015, 1, 1)).tzinfo.zone
+ >>> _get_tz_name(_ensure_datetime_tzinfo(datetime(2015, 1, 1)))
'UTC'
>>> tz = get_timezone("Europe/Stockholm")
@@ -158,7 +186,7 @@ def _ensure_datetime_tzinfo(datetime: datetime_, tzinfo: tzinfo | None = None) -
14
:param datetime: Datetime to augment.
- :param tzinfo: Optional tznfo.
+ :param tzinfo: optional tzinfo
:return: datetime with tzinfo
:rtype: datetime
"""
@@ -184,8 +212,10 @@ def _get_time(time: time | datetime | None, tzinfo: tzinfo | None = None) -> tim
time = datetime.utcnow()
elif isinstance(time, (int, float)):
time = datetime.utcfromtimestamp(time)
+
if time.tzinfo is None:
time = time.replace(tzinfo=UTC)
+
if isinstance(time, datetime):
if tzinfo is not None:
time = time.astimezone(tzinfo)
@@ -197,28 +227,40 @@ def _get_time(time: time | datetime | None, tzinfo: tzinfo | None = None) -> tim
return time
-def get_timezone(zone: str | _pytz.BaseTzInfo | None = None) -> _pytz.BaseTzInfo:
+def get_timezone(zone: str | tzinfo | None = None) -> tzinfo:
"""Looks up a timezone by name and returns it. The timezone object
- returned comes from ``pytz`` and corresponds to the `tzinfo` interface and
- can be used with all of the functions of Babel that operate with dates.
+ returned comes from ``pytz`` or ``zoneinfo``, whichever is available.
+ It corresponds to the `tzinfo` interface and can be used with all of
+ the functions of Babel that operate with dates.
If a timezone is not known a :exc:`LookupError` is raised. If `zone`
is ``None`` a local zone object is returned.
:param zone: the name of the timezone to look up. If a timezone object
- itself is passed in, mit's returned unchanged.
+ itself is passed in, it's returned unchanged.
"""
if zone is None:
return LOCALTZ
if not isinstance(zone, str):
return zone
- try:
- return _pytz.timezone(zone)
- except _pytz.UnknownTimeZoneError:
- raise LookupError(f"Unknown timezone {zone}")
+ exc = None
+ if pytz:
+ try:
+ return pytz.timezone(zone)
+ except pytz.UnknownTimeZoneError as exc:
+ pass
+ else:
+ assert zoneinfo
+ try:
+ return zoneinfo.ZoneInfo(zone)
+ except zoneinfo.ZoneInfoNotFoundError as exc:
+ pass
+
+ raise LookupError(f"Unknown timezone {zone}") from exc
-def get_next_timezone_transition(zone: _pytz.BaseTzInfo | None = None, dt: _Instant = None) -> TimezoneTransition:
+
+def get_next_timezone_transition(zone: tzinfo | None = None, dt: _Instant = None) -> TimezoneTransition:
"""Given a timezone it will return a :class:`TimezoneTransition` object
that holds the information about the next timezone transition that's going
to happen. For instance this can be used to detect when the next DST
@@ -474,7 +516,7 @@ def get_timezone_gmt(datetime: _Instant = None, width: Literal['long', 'short',
>>> get_timezone_gmt(dt, locale='en', width='iso8601_short')
u'+00'
>>> tz = get_timezone('America/Los_Angeles')
- >>> dt = tz.localize(datetime(2007, 4, 1, 15, 30))
+ >>> dt = _localize(tz, datetime(2007, 4, 1, 15, 30))
>>> get_timezone_gmt(dt, locale='en')
u'GMT-07:00'
>>> get_timezone_gmt(dt, 'short', locale='en')
@@ -608,7 +650,7 @@ def get_timezone_name(dt_or_tzinfo: _DtOrTzinfo = None, width: Literal['long', '
u'PST'
If this function gets passed only a `tzinfo` object and no concrete
- `datetime`, the returned display name is indenpendent of daylight savings
+ `datetime`, the returned display name is independent of daylight savings
time. This can be used for example for selecting timezones, or to set the
time of events that recur across DST changes:
@@ -755,12 +797,11 @@ def format_datetime(datetime: _Instant = None, format: _PredefinedTimeFormat | s
>>> format_datetime(dt, locale='en_US')
u'Apr 1, 2007, 3:30:00 PM'
- For any pattern requiring the display of the time-zone, the third-party
- ``pytz`` package is needed to explicitly specify the time-zone:
+ For any pattern requiring the display of the timezone:
>>> format_datetime(dt, 'full', tzinfo=get_timezone('Europe/Paris'),
... locale='fr_FR')
- u'dimanche 1 avril 2007 \xe0 17:30:00 heure d\u2019\xe9t\xe9 d\u2019Europe centrale'
+ 'dimanche 1 avril 2007 à 17:30:00 heure d’été d’Europe centrale'
>>> format_datetime(dt, "yyyy.MM.dd G 'at' HH:mm:ss zzz",
... tzinfo=get_timezone('US/Eastern'), locale='en')
u'2007.04.01 AD at 11:30:00 EDT'
@@ -806,9 +847,9 @@ def format_time(time: time | datetime | float | None = None, format: _Predefined
>>> t = datetime(2007, 4, 1, 15, 30)
>>> tzinfo = get_timezone('Europe/Paris')
- >>> t = tzinfo.localize(t)
+ >>> t = _localize(tzinfo, t)
>>> format_time(t, format='full', tzinfo=tzinfo, locale='fr_FR')
- u'15:30:00 heure d\u2019\xe9t\xe9 d\u2019Europe centrale'
+ '15:30:00 heure d’été d’Europe centrale'
>>> format_time(t, "hh 'o''clock' a, zzzz", tzinfo=get_timezone('US/Eastern'),
... locale='en')
u"09 o'clock AM, Eastern Daylight Time"
@@ -841,12 +882,17 @@ def format_time(time: time | datetime | float | None = None, format: _Predefined
:param tzinfo: the time-zone to apply to the time for display
:param locale: a `Locale` object or a locale identifier
"""
+
+ # get reference date for if we need to find the right timezone variant
+ # in the pattern
+ ref_date = time.date() if isinstance(time, datetime) else None
+
time = _get_time(time, tzinfo)
locale = Locale.parse(locale)
if format in ('full', 'long', 'medium', 'short'):
format = get_time_format(format, locale=locale)
- return parse_pattern(format).apply(time, locale)
+ return parse_pattern(format).apply(time, locale, reference_date=ref_date)
def format_skeleton(skeleton: str, datetime: _Instant = None, tzinfo: tzinfo | None = None,
@@ -1124,7 +1170,7 @@ def format_interval(start: _Instant, end: _Instant, skeleton: str | None = None,
return _format_fallback_interval(start, end, skeleton, tzinfo, locale)
-def get_period_id(time: _Instant, tzinfo: _pytz.BaseTzInfo | None = None, type: Literal['selection'] | None = None,
+def get_period_id(time: _Instant, tzinfo: tzinfo | None = None, type: Literal['selection'] | None = None,
locale: Locale | str | None = LC_TIME) -> str:
"""
Get the day period ID for a given time.
@@ -1327,18 +1373,29 @@ class DateTimePattern:
return NotImplemented
return self.format % other
- def apply(self, datetime: date | time, locale: Locale | str | None) -> str:
- return self % DateTimeFormat(datetime, locale)
+ def apply(
+ self,
+ datetime: date | time,
+ locale: Locale | str | None,
+ reference_date: date | None = None
+ ) -> str:
+ return self % DateTimeFormat(datetime, locale, reference_date)
class DateTimeFormat:
- def __init__(self, value: date | time, locale: Locale | str):
+ def __init__(
+ self,
+ value: date | time,
+ locale: Locale | str,
+ reference_date: date | None = None
+ ):
assert isinstance(value, (date, datetime, time))
if isinstance(value, (datetime, time)) and value.tzinfo is None:
value = value.replace(tzinfo=UTC)
self.value = value
self.locale = Locale.parse(locale)
+ self.reference_date = reference_date
def __getitem__(self, name: str) -> str:
char = name[0]
@@ -1558,46 +1615,54 @@ class DateTimeFormat:
def format_timezone(self, char: str, num: int) -> str:
width = {3: 'short', 4: 'long', 5: 'iso8601'}[max(3, num)]
+
+ # It could be that we only receive a time to format, but also have a
+ # reference date which is important to distinguish between timezone
+ # variants (summer/standard time)
+ value = self.value
+ if self.reference_date:
+ value = datetime.combine(self.reference_date, self.value)
+
if char == 'z':
- return get_timezone_name(self.value, width, locale=self.locale)
+ return get_timezone_name(value, width, locale=self.locale)
elif char == 'Z':
if num == 5:
- return get_timezone_gmt(self.value, width, locale=self.locale, return_z=True)
- return get_timezone_gmt(self.value, width, locale=self.locale)
+ return get_timezone_gmt(value, width, locale=self.locale, return_z=True)
+ return get_timezone_gmt(value, width, locale=self.locale)
elif char == 'O':
if num == 4:
- return get_timezone_gmt(self.value, width, locale=self.locale)
+ return get_timezone_gmt(value, width, locale=self.locale)
# TODO: To add support for O:1
elif char == 'v':
- return get_timezone_name(self.value.tzinfo, width,
+ return get_timezone_name(value.tzinfo, width,
locale=self.locale)
elif char == 'V':
if num == 1:
- return get_timezone_name(self.value.tzinfo, width,
+ return get_timezone_name(value.tzinfo, width,
uncommon=True, locale=self.locale)
elif num == 2:
- return get_timezone_name(self.value.tzinfo, locale=self.locale, return_zone=True)
+ return get_timezone_name(value.tzinfo, locale=self.locale, return_zone=True)
elif num == 3:
- return get_timezone_location(self.value.tzinfo, locale=self.locale, return_city=True)
- return get_timezone_location(self.value.tzinfo, locale=self.locale)
+ return get_timezone_location(value.tzinfo, locale=self.locale, return_city=True)
+ return get_timezone_location(value.tzinfo, locale=self.locale)
# Included additional elif condition to add support for 'Xx' in timezone format
elif char == 'X':
if num == 1:
- return get_timezone_gmt(self.value, width='iso8601_short', locale=self.locale,
+ return get_timezone_gmt(value, width='iso8601_short', locale=self.locale,
return_z=True)
elif num in (2, 4):
- return get_timezone_gmt(self.value, width='short', locale=self.locale,
+ return get_timezone_gmt(value, width='short', locale=self.locale,
return_z=True)
elif num in (3, 5):
- return get_timezone_gmt(self.value, width='iso8601', locale=self.locale,
+ return get_timezone_gmt(value, width='iso8601', locale=self.locale,
return_z=True)
elif char == 'x':
if num == 1:
- return get_timezone_gmt(self.value, width='iso8601_short', locale=self.locale)
+ return get_timezone_gmt(value, width='iso8601_short', locale=self.locale)
elif num in (2, 4):
- return get_timezone_gmt(self.value, width='short', locale=self.locale)
+ return get_timezone_gmt(value, width='short', locale=self.locale)
elif num in (3, 5):
- return get_timezone_gmt(self.value, width='iso8601', locale=self.locale)
+ return get_timezone_gmt(value, width='iso8601', locale=self.locale)
def format(self, value: SupportsInt, length: int) -> str:
return '%0*d' % (length, value)
diff --git a/babel/localtime/__init__.py b/babel/localtime/__init__.py
index ffe2d49..c7f214a 100644
--- a/babel/localtime/__init__.py
+++ b/babel/localtime/__init__.py
@@ -14,8 +14,6 @@ import time
from datetime import datetime, timedelta, tzinfo
from threading import RLock
-import pytz
-
if sys.platform == 'win32':
from babel.localtime._win32 import _get_localzone
else:
@@ -61,7 +59,7 @@ class _FallbackLocalTimezone(tzinfo):
return tt.tm_isdst > 0
-def get_localzone() -> pytz.BaseTzInfo:
+def get_localzone() -> tzinfo:
"""Returns the current underlying local timezone object.
Generally this function does not need to be used, it's a
better idea to use the :data:`LOCALTZ` singleton instead.
@@ -71,5 +69,5 @@ def get_localzone() -> pytz.BaseTzInfo:
try:
LOCALTZ = get_localzone()
-except pytz.UnknownTimeZoneError:
+except LookupError:
LOCALTZ = _FallbackLocalTimezone()
diff --git a/babel/localtime/_helpers.py b/babel/localtime/_helpers.py
new file mode 100644
index 0000000..b7238f6
--- /dev/null
+++ b/babel/localtime/_helpers.py
@@ -0,0 +1,42 @@
+try:
+ import pytz
+except ModuleNotFoundError:
+ pytz = None
+ import zoneinfo
+
+
+def _get_tzinfo(tzenv: str):
+ """Get the tzinfo from `zoneinfo` or `pytz`
+
+ :param tzenv: timezone in the form of Continent/City
+ :return: tzinfo object or None if not found
+ """
+ if pytz:
+ try:
+ return pytz.timezone(tzenv)
+ except pytz.UnknownTimeZoneError:
+ pass
+ else:
+ try:
+ return zoneinfo.ZoneInfo(tzenv)
+ except zoneinfo.ZoneInfoNotFoundError:
+ pass
+
+ return None
+
+def _get_tzinfo_or_raise(tzenv: str):
+ tzinfo = _get_tzinfo(tzenv)
+ if tzinfo is None:
+ raise LookupError(
+ f"Can not find timezone {tzenv}. \n"
+ "Timezone names are generally in the form `Continent/City`."
+ )
+ return tzinfo
+
+
+def _get_tzinfo_from_file(tzfilename: str):
+ with open(tzfilename, 'rb') as tzfile:
+ if pytz:
+ return pytz.tzfile.build_tzinfo('local', tzfile)
+ else:
+ return zoneinfo.ZoneInfo.from_file(tzfile)
diff --git a/babel/localtime/_unix.py b/babel/localtime/_unix.py
index beb7f60..319c8cf 100644
--- a/babel/localtime/_unix.py
+++ b/babel/localtime/_unix.py
@@ -1,33 +1,31 @@
import os
import re
-import pytz
+from datetime import tzinfo
-def _tz_from_env(tzenv: str) -> pytz.BaseTzInfo:
+from babel.localtime._helpers import (
+ _get_tzinfo_from_file,
+ _get_tzinfo_or_raise,
+ _get_tzinfo,
+)
+
+def _tz_from_env(tzenv: str) -> tzinfo:
if tzenv[0] == ':':
tzenv = tzenv[1:]
# TZ specifies a file
if os.path.exists(tzenv):
- with open(tzenv, 'rb') as tzfile:
- return pytz.tzfile.build_tzinfo('local', tzfile)
+ return _get_tzinfo_from_file(tzenv)
# TZ specifies a zoneinfo zone.
- try:
- tz = pytz.timezone(tzenv)
- # That worked, so we return this:
- return tz
- except pytz.UnknownTimeZoneError:
- raise pytz.UnknownTimeZoneError(
- "tzlocal() does not support non-zoneinfo timezones like %s. \n"
- "Please use a timezone in the form of Continent/City")
+ return _get_tzinfo_or_raise(tzenv)
-def _get_localzone(_root: str = '/') -> pytz.BaseTzInfo:
+def _get_localzone(_root: str = '/') -> tzinfo:
"""Tries to find the local timezone configuration.
- This method prefers finding the timezone name and passing that to pytz,
- over passing in the localtime file, as in the later case the zoneinfo
- name is unknown.
+ This method prefers finding the timezone name and passing that to
+ zoneinfo or pytz, over passing in the localtime file, as in the later
+ case the zoneinfo name is unknown.
The parameter _root makes the function look for files like /etc/localtime
beneath the _root directory. This is primarily used by the tests.
In normal usage you call the function without parameters.
@@ -48,10 +46,9 @@ def _get_localzone(_root: str = '/') -> pytz.BaseTzInfo:
pos = link_dst.find('/zoneinfo/')
if pos >= 0:
zone_name = link_dst[pos + 10:]
- try:
- return pytz.timezone(zone_name)
- except pytz.UnknownTimeZoneError:
- pass
+ tzinfo = _get_tzinfo(zone_name)
+ if tzinfo is not None:
+ return tzinfo
# Now look for distribution specific configuration files
# that contain the timezone name.
@@ -69,7 +66,8 @@ def _get_localzone(_root: str = '/') -> pytz.BaseTzInfo:
etctz, dummy = etctz.split(' ', 1)
if '#' in etctz:
etctz, dummy = etctz.split('#', 1)
- return pytz.timezone(etctz.replace(' ', '_'))
+
+ return _get_tzinfo_or_raise(etctz.replace(' ', '_'))
# CentOS has a ZONE setting in /etc/sysconfig/clock,
# OpenSUSE has a TIMEZONE setting in /etc/sysconfig/clock and
@@ -87,7 +85,7 @@ def _get_localzone(_root: str = '/') -> pytz.BaseTzInfo:
if match is not None:
# We found a timezone
etctz = match.group("etctz")
- return pytz.timezone(etctz.replace(' ', '_'))
+ return _get_tzinfo_or_raise(etctz.replace(' ', '_'))
# No explicit setting existed. Use localtime
for filename in ('etc/localtime', 'usr/local/etc/localtime'):
@@ -95,8 +93,6 @@ def _get_localzone(_root: str = '/') -> pytz.BaseTzInfo:
if not os.path.exists(tzpath):
continue
+ return _get_tzinfo_from_file(tzpath)
- with open(tzpath, 'rb') as tzfile:
- return pytz.tzfile.build_tzinfo('local', tzfile)
-
- raise pytz.UnknownTimeZoneError('Can not find any timezone configuration')
+ raise LookupError('Can not find any timezone configuration')
diff --git a/babel/localtime/_win32.py b/babel/localtime/_win32.py
index 98d5170..3d7e0d5 100644
--- a/babel/localtime/_win32.py
+++ b/babel/localtime/_win32.py
@@ -5,11 +5,10 @@ try:
except ImportError:
winreg = None
-from typing import Any, Dict, cast
-
-import pytz
-
+from datetime import tzinfo
from babel.core import get_global
+from babel.localtime._helpers import _get_tzinfo_or_raise
+from typing import Any, Dict, cast
# When building the cldr data on windows this module gets imported.
# Because at that point there is no global.dat yet this call will
@@ -85,13 +84,14 @@ def get_localzone_name() -> str:
# Return what we have.
if timezone is None:
- raise pytz.UnknownTimeZoneError(f"Can not find timezone {tzkeyname}")
+ raise LookupError(f"Can not find timezone {tzkeyname}")
return timezone
-def _get_localzone() -> pytz.BaseTzInfo:
+def _get_localzone() -> tzinfo:
if winreg is None:
- raise pytz.UnknownTimeZoneError(
+ raise LookupError(
'Runtime support not available')
- return pytz.timezone(get_localzone_name())
+
+ return _get_tzinfo_or_raise(get_localzone_name())
diff --git a/babel/support.py b/babel/support.py
index 7a6eaa2..7477ee1 100644
--- a/babel/support.py
+++ b/babel/support.py
@@ -17,14 +17,19 @@ import gettext
import locale
import os
from collections.abc import Iterator
-from datetime import date as _date, datetime as _datetime, time as _time, timedelta as _timedelta
+from datetime import (
+ date as _date,
+ datetime as _datetime,
+ time as _time,
+ timedelta as _timedelta,
+ tzinfo
+)
from typing import TYPE_CHECKING, Any, Callable
-from pytz import BaseTzInfo
-
from babel.core import Locale
+
from babel.dates import (format_date, format_datetime, format_time,
- format_timedelta)
+ format_timedelta, get_timezone)
from babel.numbers import (format_compact_currency, format_compact_decimal,
format_currency, format_decimal, format_percent,
format_scientific)
@@ -47,7 +52,7 @@ class Format:
u'1.234'
"""
- def __init__(self, locale: Locale | str, tzinfo: BaseTzInfo | None = None) -> None:
+ def __init__(self, locale: Locale | str, tzinfo: tzinfo | None = None) -> None:
"""Initialize the formatter.
:param locale: the locale identifier or `Locale` instance
@@ -70,8 +75,7 @@ class Format:
"""Return a date and time formatted according to the given pattern.
>>> from datetime import datetime
- >>> from pytz import timezone
- >>> fmt = Format('en_US', tzinfo=timezone('US/Eastern'))
+ >>> fmt = Format('en_US', tzinfo=get_timezone('US/Eastern'))
>>> fmt.datetime(datetime(2007, 4, 1, 15, 30))
u'Apr 1, 2007, 11:30:00 AM'
"""
@@ -82,8 +86,7 @@ class Format:
"""Return a time formatted according to the given pattern.
>>> from datetime import datetime
- >>> from pytz import timezone
- >>> fmt = Format('en_US', tzinfo=timezone('US/Eastern'))
+ >>> fmt = Format('en_US', tzinfo=get_timezone('US/Eastern'))
>>> fmt.time(datetime(2007, 4, 1, 15, 30))
u'11:30:00 AM'
"""
diff --git a/babel/util.py b/babel/util.py
index f159c33..fb4870d 100644
--- a/babel/util.py
+++ b/babel/util.py
@@ -14,14 +14,12 @@ import collections
import os
import re
import textwrap
+from babel import localtime, dates
+
from collections.abc import Generator, Iterable
from datetime import datetime as datetime_, timedelta, tzinfo
from typing import IO, Any, TypeVar
-import pytz as _pytz
-
-from babel import localtime
-
missing = object()
_T = TypeVar("_T")
@@ -255,8 +253,8 @@ class FixedOffsetTimezone(tzinfo):
# Export the localtime functionality here because that's
# where it was in the past.
-UTC = _pytz.utc
-LOCALTZ = localtime.LOCALTZ
+UTC = dates.UTC
+LOCALTZ = dates.LOCALTZ
get_localzone = localtime.get_localzone
STDOFFSET = localtime.STDOFFSET
diff --git a/docs/dates.rst b/docs/dates.rst
index 4420187..8b35091 100644
--- a/docs/dates.rst
+++ b/docs/dates.rst
@@ -276,11 +276,9 @@ class, which you need appropriate implementations for to actually use in your
application. Babel includes a ``tzinfo`` implementation for UTC (Universal
Time).
-Babel uses `pytz`_ for real timezone support which includes the
-definitions of practically all of the time-zones used on the world, as
-well as important functions for reliably converting from UTC to local
-time, and vice versa. The module is generally wrapped for you so you can
-directly interface with it from within Babel:
+Babel uses either `zoneinfo`_ or `pytz`_ for timezone support.
+If pytz is installed, it is preferred over the standard library's zoneinfo.
+You can directly interface with either of these modules from within Babel:
.. code-block:: pycon
@@ -294,9 +292,9 @@ directly interface with it from within Babel:
The recommended approach to deal with different time-zones in a Python
application is to always use UTC internally, and only convert from/to the users
time-zone when accepting user input and displaying date/time data, respectively.
-You can use Babel together with ``pytz`` to apply a time-zone to any
-``datetime`` or ``time`` object for display, leaving the original information
-unchanged:
+You can use Babel together with ``zoneinfo`` or ``pytz`` to apply a time-zone
+to any ``datetime`` or ``time`` object for display, leaving the original
+information unchanged:
.. code-block:: pycon
@@ -314,6 +312,9 @@ For many timezones it's also possible to ask for the next timezone
transition. This for instance is useful to answer the question “when do I
have to move the clock forward next”:
+.. warning:: ``get_next_timezone_transition`` is deprecated and will be removed
+ in the next version of Babel
+
.. code-block:: pycon
>>> t = get_next_timezone_transition('Europe/Vienna', datetime(2011, 3, 2))
@@ -339,7 +340,7 @@ your operating system. It's provided through the ``LOCALTZ`` constant:
>>> get_timezone_name(LOCALTZ)
u'Central European Time'
-.. _pytz: http://pytz.sourceforge.net/
+.. _pytz: https://pythonhosted.org/pytz/
Localized Time-zone Names
@@ -370,8 +371,9 @@ display a list of time-zones to the user.
.. code-block:: pycon
>>> from datetime import datetime
+ >>> from babel.dates import _localize
- >>> dt = tz.localize(datetime(2007, 8, 15))
+ >>> dt = _localize(tz, datetime(2007, 8, 15))
>>> get_timezone_name(dt, locale=Locale.parse('de_DE'))
u'Mitteleurop\xe4ische Sommerzeit'
>>> get_timezone_name(tz, locale=Locale.parse('de_DE'))
diff --git a/docs/dev.rst b/docs/dev.rst
index 1c4453d..97a105b 100644
--- a/docs/dev.rst
+++ b/docs/dev.rst
@@ -46,10 +46,9 @@ Unicode is a big deal in Babel. Here is how the rules are set up:
Dates and Timezones
-------------------
-Generally all timezone support in Babel is based on pytz which it just
-depends on. Babel should assume that timezone objects are pytz based
-because those are the only ones with an API that actually work correctly
-(due to the API problems with non UTC based timezones).
+Babel's timezone support relies on either ``pytz`` or ``zoneinfo``; if ``pytz``
+is installed, it is preferred over ``zoneinfo``. Babel should assume that any
+timezone objects can be from either of these modules.
Assumptions to make:
diff --git a/docs/installation.rst b/docs/installation.rst
index 26fe23a..8bf614c 100644
--- a/docs/installation.rst
+++ b/docs/installation.rst
@@ -4,10 +4,14 @@ Installation
============
Babel is distributed as a standard Python package fully set up with all
-the dependencies it needs. It primarily depends on the excellent `pytz`_
-library for timezone handling. To install it you can use ``pip``.
+the dependencies it needs. On Python versions where the standard library
+`zoneinfo`_ module is not available, `pytz`_ needs to be installed for
+timezone support. If `pytz`_ is installed, it is preferred over the
+standard library `zoneinfo`_ module where possible.
-.. _pytz: http://pytz.sourceforge.net/
+.. _pytz: https://pythonhosted.org/pytz/
+
+.. _zoneinfo: https://docs.python.org/3/library/zoneinfo.html
.. _virtualenv:
@@ -79,16 +83,15 @@ Get the git checkout in a new virtualenv and run in development mode::
New python executable in venv/bin/python
Installing distribute............done.
$ . venv/bin/activate
- $ pip install pytz
$ python setup.py import_cldr
$ pip install --editable .
...
Finished processing dependencies for Babel
-Make sure to not forget about the ``pip install pytz`` and ``import_cldr`` steps
-because otherwise you will be missing the locale data.
+Make sure to not forget about the ``import_cldr`` step because otherwise
+you will be missing the locale data.
The custom setup command will download the most appropriate CLDR release from the
-official website and convert it for Babel but will not work without ``pytz``.
+official website and convert it for Babel.
This will pull also in the dependencies and activate the git head as the
current version inside the virtualenv. Then all you have to do is run
diff --git a/setup.py b/setup.py
index 157f7c1..db63216 100755
--- a/setup.py
+++ b/setup.py
@@ -63,7 +63,8 @@ setup(
# This version identifier is currently necessary as
# pytz otherwise does not install on pip 1.4 or
# higher.
- 'pytz>=2015.7',
+ # Python 3.9 and later include zoneinfo which replaces pytz
+ 'pytz>=2015.7; python_version<"3.9"',
],
cmdclass={'import_cldr': import_cldr},
zip_safe=False,
diff --git a/tests/test_date_intervals.py b/tests/test_date_intervals.py
index dc3ae34..eb3aa89 100644
--- a/tests/test_date_intervals.py
+++ b/tests/test_date_intervals.py
@@ -1,8 +1,8 @@
import datetime
from babel import dates
-from babel.dates import get_timezone
from babel.util import UTC
+from .test_dates import timezone_getter
TEST_DT = datetime.datetime(2016, 1, 8, 11, 46, 15)
TEST_TIME = TEST_DT.time()
@@ -31,10 +31,10 @@ def test_format_interval_no_difference():
assert dates.format_interval(t1, t2, "yMd", fuzzy=False, locale="fi") == "8.1.2016"
-def test_format_interval_in_tz():
+def test_format_interval_in_tz(timezone_getter):
t1 = TEST_DT.replace(tzinfo=UTC)
t2 = t1 + datetime.timedelta(minutes=18)
- hki_tz = get_timezone("Europe/Helsinki")
+ hki_tz = timezone_getter("Europe/Helsinki")
assert dates.format_interval(t1, t2, "Hmv", tzinfo=hki_tz, locale="fi") == "13.46\u201314.04 aikavyöhyke: Suomi"
diff --git a/tests/test_dates.py b/tests/test_dates.py
index b62f542..9fd7314 100644
--- a/tests/test_dates.py
+++ b/tests/test_dates.py
@@ -12,36 +12,46 @@
import calendar
from datetime import date, datetime, time, timedelta
-import unittest
import freezegun
import pytest
-import pytz
-from pytz import timezone
+
+# for tests it can be useful to have both zoneinfo and pytz available
+try:
+ import zoneinfo
+except ModuleNotFoundError:
+ try:
+ from backports import zoneinfo
+ except ImportError:
+ zoneinfo = None
+
+try:
+ import pytz
+except ModuleNotFoundError:
+ pytz = None
from babel import dates, Locale
-from babel.dates import NO_INHERITANCE_MARKER
+from babel.dates import NO_INHERITANCE_MARKER, _localize, _get_tz_name, LOCALTZ
from babel.util import FixedOffsetTimezone
-@pytest.fixture(params=["pytz.timezone", "zoneinfo.ZoneInfo"])
+@pytest.fixture(params=["pytz.timezone", "zoneinfo.ZoneInfo"], scope="package")
def timezone_getter(request):
if request.param == "pytz.timezone":
- return timezone
+ if pytz:
+ return pytz.timezone
+ else:
+ pytest.skip("pytz not available")
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
+ if zoneinfo:
+ return zoneinfo.ZoneInfo
+ else:
+ pytest.skip("zoneinfo not available")
else:
raise NotImplementedError
-class DateTimeFormatTestCase(unittest.TestCase):
+class DateTimeFormatTestCase:
def test_quarter_format(self):
d = date(2006, 6, 8)
@@ -205,33 +215,33 @@ class DateTimeFormatTestCase(unittest.TestCase):
d = time(0, 0, 0)
assert dates.DateTimeFormat(d, locale='en_US')['AAAA'] == '0000'
- def test_timezone_rfc822(self):
- tz = timezone('Europe/Berlin')
- t = tz.localize(datetime(2015, 1, 1, 15, 30))
+ def test_timezone_rfc822(self, timezone_getter):
+ tz = timezone_getter('Europe/Berlin')
+ t = _localize(tz, datetime(2015, 1, 1, 15, 30))
assert dates.DateTimeFormat(t, locale='de_DE')['Z'] == '+0100'
- def test_timezone_gmt(self):
- tz = timezone('Europe/Berlin')
- t = tz.localize(datetime(2015, 1, 1, 15, 30))
+ def test_timezone_gmt(self, timezone_getter):
+ tz = timezone_getter('Europe/Berlin')
+ t = _localize(tz, datetime(2015, 1, 1, 15, 30))
assert dates.DateTimeFormat(t, locale='de_DE')['ZZZZ'] == 'GMT+01:00'
- def test_timezone_name(self):
- tz = timezone('Europe/Paris')
- dt = tz.localize(datetime(2007, 4, 1, 15, 30))
+ def test_timezone_name(self, timezone_getter):
+ tz = timezone_getter('Europe/Paris')
+ dt = _localize(tz, datetime(2007, 4, 1, 15, 30))
assert dates.DateTimeFormat(dt, locale='fr_FR')['v'] == 'heure : France'
- def test_timezone_location_format(self):
- tz = timezone('Europe/Paris')
- dt = datetime(2007, 4, 1, 15, 30, tzinfo=tz)
+ def test_timezone_location_format(self, timezone_getter):
+ tz = timezone_getter('Europe/Paris')
+ dt = _localize(tz, datetime(2007, 4, 1, 15, 30))
assert dates.DateTimeFormat(dt, locale='fr_FR')['VVVV'] == 'heure : France'
- def test_timezone_walltime_short(self):
- tz = timezone('Europe/Paris')
+ def test_timezone_walltime_short(self, timezone_getter):
+ tz = timezone_getter('Europe/Paris')
t = time(15, 30, tzinfo=tz)
assert dates.DateTimeFormat(t, locale='fr_FR')['v'] == 'heure : France'
- def test_timezone_walltime_long(self):
- tz = timezone('Europe/Paris')
+ def test_timezone_walltime_long(self, timezone_getter):
+ tz = timezone_getter('Europe/Paris')
t = time(15, 30, tzinfo=tz)
assert dates.DateTimeFormat(t, locale='fr_FR')['vvvv'] == u'heure d’Europe centrale'
@@ -249,7 +259,7 @@ class DateTimeFormatTestCase(unittest.TestCase):
assert dates.format_time(t, 'K a', locale=l) == '0 PM'
-class FormatDateTestCase(unittest.TestCase):
+class FormatDateTestCase:
def test_with_time_fields_in_pattern(self):
with pytest.raises(AttributeError):
@@ -265,18 +275,18 @@ class FormatDateTestCase(unittest.TestCase):
assert dates.format_date(d, 'w', locale='en_US') == '14'
-class FormatDatetimeTestCase(unittest.TestCase):
+class FormatDatetimeTestCase:
- def test_with_float(self):
- d = datetime(2012, 4, 1, 15, 30, 29, tzinfo=timezone('UTC'))
+ def test_with_float(self, timezone_getter):
+ UTC = timezone_getter('UTC')
+ d = datetime(2012, 4, 1, 15, 30, 29, tzinfo=UTC)
epoch = float(calendar.timegm(d.timetuple()))
formatted_string = dates.format_datetime(epoch, format='long', locale='en_US')
assert formatted_string == u'April 1, 2012 at 3:30:29 PM UTC'
- def test_timezone_formats_los_angeles(self):
- dt = datetime(2016, 1, 13, 7, 8, 35)
- tz = dates.get_timezone('America/Los_Angeles')
- dt = tz.localize(dt)
+ def test_timezone_formats_los_angeles(self, timezone_getter):
+ tz = timezone_getter('America/Los_Angeles')
+ dt = _localize(tz, datetime(2016, 1, 13, 7, 8, 35))
assert dates.format_datetime(dt, 'z', locale='en') == u'PST'
assert dates.format_datetime(dt, 'zz', locale='en') == u'PST'
assert dates.format_datetime(dt, 'zzz', locale='en') == u'PST'
@@ -300,10 +310,9 @@ class FormatDatetimeTestCase(unittest.TestCase):
assert dates.format_datetime(dt, 'xxxx', locale='en') == u'-0800'
assert dates.format_datetime(dt, 'xxxxx', locale='en') == u'-08:00'
- def test_timezone_formats_utc(self):
- dt = datetime(2016, 1, 13, 7, 8, 35)
- tz = dates.get_timezone('UTC')
- dt = tz.localize(dt)
+ def test_timezone_formats_utc(self, timezone_getter):
+ tz = timezone_getter('UTC')
+ dt = _localize(tz, datetime(2016, 1, 13, 7, 8, 35))
assert dates.format_datetime(dt, 'Z', locale='en') == u'+0000'
assert dates.format_datetime(dt, 'ZZ', locale='en') == u'+0000'
assert dates.format_datetime(dt, 'ZZZ', locale='en') == u'+0000'
@@ -323,10 +332,9 @@ class FormatDatetimeTestCase(unittest.TestCase):
assert dates.format_datetime(dt, 'xxxx', locale='en') == u'+0000'
assert dates.format_datetime(dt, 'xxxxx', locale='en') == u'+00:00'
- def test_timezone_formats_kolkata(self):
- dt = datetime(2016, 1, 13, 7, 8, 35)
- tz = dates.get_timezone('Asia/Kolkata')
- dt = tz.localize(dt)
+ def test_timezone_formats_kolkata(self, timezone_getter):
+ tz = timezone_getter('Asia/Kolkata')
+ dt = _localize(tz, datetime(2016, 1, 13, 7, 8, 35))
assert dates.format_datetime(dt, 'zzzz', locale='en') == u'India Standard Time'
assert dates.format_datetime(dt, 'ZZZZ', locale='en') == u'GMT+05:30'
assert dates.format_datetime(dt, 'ZZZZZ', locale='en') == u'+05:30'
@@ -345,18 +353,19 @@ class FormatDatetimeTestCase(unittest.TestCase):
assert dates.format_datetime(dt, 'xxxxx', locale='en') == u'+05:30'
-class FormatTimeTestCase(unittest.TestCase):
+class FormatTimeTestCase:
- def test_with_naive_datetime_and_tzinfo(self):
+ def test_with_naive_datetime_and_tzinfo(self, timezone_getter):
assert dates.format_time(
datetime(2007, 4, 1, 15, 30),
'long',
- tzinfo=timezone('US/Eastern'),
+ tzinfo=timezone_getter('US/Eastern'),
locale='en',
) == '11:30:00 AM EDT'
- def test_with_float(self):
- d = datetime(2012, 4, 1, 15, 30, 29, tzinfo=timezone('UTC'))
+ def test_with_float(self, timezone_getter):
+ tz = timezone_getter('UTC')
+ d = _localize(tz, datetime(2012, 4, 1, 15, 30, 29))
epoch = float(calendar.timegm(d.timetuple()))
assert dates.format_time(epoch, format='long', locale='en_US') == u'3:30:29 PM UTC'
@@ -369,7 +378,7 @@ class FormatTimeTestCase(unittest.TestCase):
dates.format_time(datetime(2007, 4, 1, 15, 30), "yyyy-MM-dd HH:mm", locale='en_US')
-class FormatTimedeltaTestCase(unittest.TestCase):
+class FormatTimedeltaTestCase:
def test_zero_seconds(self):
td = timedelta(seconds=0)
@@ -398,7 +407,7 @@ class FormatTimedeltaTestCase(unittest.TestCase):
dates.format_timedelta(timedelta(hours=1), format=format)
-class TimeZoneAdjustTestCase(unittest.TestCase):
+class TimeZoneAdjustTestCase:
def _utc(self):
class EvilFixedOffsetTimezone(FixedOffsetTimezone):
@@ -410,7 +419,7 @@ class TimeZoneAdjustTestCase(unittest.TestCase):
assert hasattr(UTC, 'normalize') is False
return UTC
- def test_can_format_time_with_non_pytz_timezone(self):
+ def test_can_format_time_with_custom_timezone(self):
# regression test for #257
utc = self._utc()
t = datetime(2007, 4, 1, 15, 30, tzinfo=utc)
@@ -465,13 +474,13 @@ def test_get_time_format():
u'HH:mm:ss zzzz')
-def test_get_timezone_gmt():
+def test_get_timezone_gmt(timezone_getter):
dt = datetime(2007, 4, 1, 15, 30)
assert dates.get_timezone_gmt(dt, locale='en') == u'GMT+00:00'
assert dates.get_timezone_gmt(dt, locale='en', return_z=True) == 'Z'
assert dates.get_timezone_gmt(dt, locale='en', width='iso8601_short') == u'+00'
- tz = timezone('America/Los_Angeles')
- dt = tz.localize(datetime(2007, 4, 1, 15, 30))
+ tz = timezone_getter('America/Los_Angeles')
+ dt = _localize(tz, datetime(2007, 4, 1, 15, 30))
assert dates.get_timezone_gmt(dt, locale='en') == u'GMT-07:00'
assert dates.get_timezone_gmt(dt, 'short', locale='en') == u'-0700'
assert dates.get_timezone_gmt(dt, locale='en', width='iso8601_short') == u'-07'
@@ -582,21 +591,27 @@ def test_format_date():
u"Sun, Apr 1, '07")
-def test_format_datetime():
+def test_format_datetime(timezone_getter):
dt = datetime(2007, 4, 1, 15, 30)
assert (dates.format_datetime(dt, locale='en_US') ==
u'Apr 1, 2007, 3:30:00 PM')
- full = dates.format_datetime(dt, 'full', tzinfo=timezone('Europe/Paris'),
- locale='fr_FR')
+ full = dates.format_datetime(
+ dt, 'full',
+ tzinfo=timezone_getter('Europe/Paris'),
+ locale='fr_FR'
+ )
assert full == (u'dimanche 1 avril 2007 à 17:30:00 heure '
u'd\u2019\xe9t\xe9 d\u2019Europe centrale')
- custom = dates.format_datetime(dt, "yyyy.MM.dd G 'at' HH:mm:ss zzz",
- tzinfo=timezone('US/Eastern'), locale='en')
+ custom = dates.format_datetime(
+ dt, "yyyy.MM.dd G 'at' HH:mm:ss zzz",
+ tzinfo=timezone_getter('US/Eastern'),
+ locale='en'
+ )
assert custom == u'2007.04.01 AD at 11:30:00 EDT'
-def test_format_time():
+def test_format_time(timezone_getter):
t = time(15, 30)
assert dates.format_time(t, locale='en_US') == u'3:30:00 PM'
assert dates.format_time(t, format='short', locale='de_DE') == u'15:30'
@@ -604,31 +619,31 @@ def test_format_time():
assert (dates.format_time(t, "hh 'o''clock' a", locale='en') ==
u"03 o'clock PM")
- t = datetime(2007, 4, 1, 15, 30)
- tzinfo = timezone('Europe/Paris')
- t = tzinfo.localize(t)
- fr = dates.format_time(t, format='full', tzinfo=tzinfo, locale='fr_FR')
- assert fr == u'15:30:00 heure d\u2019\xe9t\xe9 d\u2019Europe centrale'
- custom = dates.format_time(t, "hh 'o''clock' a, zzzz",
- tzinfo=timezone('US/Eastern'), locale='en')
+ paris = timezone_getter('Europe/Paris')
+ eastern = timezone_getter('US/Eastern')
+
+ t = _localize(paris, datetime(2007, 4, 1, 15, 30))
+ fr = dates.format_time(t, format='full', tzinfo=paris, locale='fr_FR')
+ assert fr == '15:30:00 heure d’été d’Europe centrale'
+
+ custom = dates.format_time(t, "hh 'o''clock' a, zzzz", tzinfo=eastern, locale='en')
assert custom == u"09 o'clock AM, Eastern Daylight Time"
t = time(15, 30)
- paris = dates.format_time(t, format='full',
- tzinfo=timezone('Europe/Paris'), locale='fr_FR')
- assert paris == u'15:30:00 heure normale d\u2019Europe centrale'
- us_east = dates.format_time(t, format='full',
- tzinfo=timezone('US/Eastern'), locale='en_US')
+ paris = dates.format_time(t, format='full', tzinfo=paris, locale='fr_FR')
+ assert paris == '15:30:00 heure normale d’Europe centrale'
+
+ us_east = dates.format_time(t, format='full', tzinfo=eastern, locale='en_US')
assert us_east == u'3:30:00 PM Eastern Standard Time'
-def test_format_skeleton():
+def test_format_skeleton(timezone_getter):
dt = datetime(2007, 4, 1, 15, 30)
assert (dates.format_skeleton('yMEd', dt, locale='en_US') == u'Sun, 4/1/2007')
assert (dates.format_skeleton('yMEd', dt, locale='th') == u'อา. 1/4/2007')
assert (dates.format_skeleton('EHm', dt, locale='en') == u'Sun 15:30')
- assert (dates.format_skeleton('EHm', dt, tzinfo=timezone('Asia/Bangkok'), locale='th') == u'อา. 22:30 น.')
+ assert (dates.format_skeleton('EHm', dt, tzinfo=timezone_getter('Asia/Bangkok'), locale='th') == u'อา. 22:30 น.')
def test_format_timedelta():
@@ -721,18 +736,18 @@ def test_format_current_moment():
@pytest.mark.all_locales
-def test_no_inherit_metazone_marker_never_in_output(locale):
+def test_no_inherit_metazone_marker_never_in_output(locale, timezone_getter):
# See: https://github.com/python-babel/babel/issues/428
- tz = pytz.timezone('America/Los_Angeles')
- t = tz.localize(datetime(2016, 1, 6, 7))
+ tz = timezone_getter('America/Los_Angeles')
+ t = _localize(tz, 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():
+def test_no_inherit_metazone_formatting(timezone_getter):
# See: https://github.com/python-babel/babel/issues/428
- tz = pytz.timezone('America/Los_Angeles')
- t = tz.localize(datetime(2016, 1, 6, 7))
+ tz = timezone_getter('America/Los_Angeles')
+ t = _localize(tz, 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"
diff --git a/tests/test_support.py b/tests/test_support.py
index c73e53b..d39d8ef 100644
--- a/tests/test_support.py
+++ b/tests/test_support.py
@@ -23,6 +23,8 @@ from io import BytesIO
from babel import support
from babel.messages import Catalog
from babel.messages.mofile import write_mo
+from babel.dates import get_timezone
+from .test_dates import timezone_getter
SKIP_LGETTEXT = sys.version_info >= (3, 8)
@@ -300,16 +302,14 @@ def test_format_date():
assert fmt.date(date(2007, 4, 1)) == 'Apr 1, 2007'
-def test_format_datetime():
- from pytz import timezone
- fmt = support.Format('en_US', tzinfo=timezone('US/Eastern'))
+def test_format_datetime(timezone_getter):
+ fmt = support.Format('en_US', tzinfo=timezone_getter('US/Eastern'))
when = datetime(2007, 4, 1, 15, 30)
assert fmt.datetime(when) == 'Apr 1, 2007, 11:30:00 AM'
-def test_format_time():
- from pytz import timezone
- fmt = support.Format('en_US', tzinfo=timezone('US/Eastern'))
+def test_format_time(timezone_getter):
+ fmt = support.Format('en_US', tzinfo=timezone_getter('US/Eastern'))
assert fmt.time(datetime(2007, 4, 1, 15, 30)) == '11:30:00 AM'