summaryrefslogtreecommitdiff
path: root/babel
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 /babel
parent53637ddbacaef2474429b22176091a362ce6567f (diff)
downloadbabel-14216ed8f90832eed9c8ea24442d5b63da4389eb.tar.gz
Implement zoneinfo support and make pytz optional (#940)
Diffstat (limited to 'babel')
-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
7 files changed, 198 insertions, 96 deletions
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