Skip to content

Commit

Permalink
Use zoneinfo as a base Timezone implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
sdispater committed Jan 18, 2022
1 parent 745a036 commit ae2c9f5
Show file tree
Hide file tree
Showing 24 changed files with 259 additions and 1,202 deletions.
27 changes: 14 additions & 13 deletions pendulum/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
from .tz import test_local_timezone
from .tz import timezone
from .tz import timezones
from .tz.timezone import Timezone as _Timezone
from .tz.timezone import FixedTimezone
from .tz.timezone import Timezone


_TEST_NOW = None # type: Optional[DateTime]
Expand All @@ -59,13 +60,13 @@


def _safe_timezone(
obj: Optional[Union[str, float, _datetime.tzinfo, _Timezone]]
) -> _Timezone:
obj: Optional[Union[str, float, _datetime.tzinfo, Timezone]]
) -> Timezone:
"""
Creates a timezone instance
from a string, Timezone, TimezoneInfo or integer offset.
"""
if isinstance(obj, _Timezone):
if isinstance(obj, (Timezone, FixedTimezone)):
return obj

if obj is None or obj == "local":
Expand Down Expand Up @@ -99,7 +100,7 @@ def datetime(
minute: int = 0,
second: int = 0,
microsecond: int = 0,
tz: Optional[Union[str, float, _Timezone]] = UTC,
tz: Optional[Union[str, float, Timezone]] = UTC,
dst_rule: str = POST_TRANSITION,
) -> DateTime:
"""
Expand Down Expand Up @@ -173,7 +174,7 @@ def time(hour: int, minute: int = 0, second: int = 0, microsecond: int = 0) -> T


def instance(
dt: _datetime.datetime, tz: Optional[Union[str, _Timezone]] = UTC
dt: _datetime.datetime, tz: Optional[Union[str, Timezone]] = UTC
) -> DateTime:
"""
Create a DateTime instance from a datetime one.
Expand All @@ -187,7 +188,7 @@ def instance(
tz = dt.tzinfo or tz

# Checking for pytz/tzinfo
if isinstance(tz, _datetime.tzinfo) and not isinstance(tz, _Timezone):
if isinstance(tz, _datetime.tzinfo) and not isinstance(tz, Timezone):
# pytz
if hasattr(tz, "localize") and tz.zone:
tz = tz.zone
Expand All @@ -202,7 +203,7 @@ def instance(
)


def now(tz: Optional[Union[str, _Timezone]] = None) -> DateTime:
def now(tz: Optional[Union[str, Timezone]] = None) -> DateTime:
"""
Get a DateTime instance for the current date and time.
"""
Expand Down Expand Up @@ -237,21 +238,21 @@ def now(tz: Optional[Union[str, _Timezone]] = None) -> DateTime:
)


def today(tz: Union[str, _Timezone] = "local") -> DateTime:
def today(tz: Union[str, Timezone] = "local") -> DateTime:
"""
Create a DateTime instance for today.
"""
return now(tz).start_of("day")


def tomorrow(tz: Union[str, _Timezone] = "local") -> DateTime:
def tomorrow(tz: Union[str, Timezone] = "local") -> DateTime:
"""
Create a DateTime instance for today.
"""
return today(tz).add(days=1)


def yesterday(tz: Union[str, _Timezone] = "local") -> DateTime:
def yesterday(tz: Union[str, Timezone] = "local") -> DateTime:
"""
Create a DateTime instance for today.
"""
Expand All @@ -261,7 +262,7 @@ def yesterday(tz: Union[str, _Timezone] = "local") -> DateTime:
def from_format(
string: str,
fmt: str,
tz: Union[str, _Timezone] = UTC,
tz: Union[str, Timezone] = UTC,
locale: Optional[str] = None, # noqa
) -> DateTime:
"""
Expand All @@ -275,7 +276,7 @@ def from_format(


def from_timestamp(
timestamp: Union[int, float], tz: Union[str, _Timezone] = UTC
timestamp: Union[int, float], tz: Union[str, Timezone] = UTC
) -> DateTime:
"""
Create a DateTime instance from a timestamp.
Expand Down
8 changes: 7 additions & 1 deletion pendulum/_extensions/_helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,13 @@ char *_get_tz_name(PyObject *dt)

if (tzinfo != Py_None)
{
if (PyObject_HasAttrString(tzinfo, "name"))
if (PyObject_HasAttrString(tzinfo, "key"))
{
// zoneinfo timezone
tz = (char *)PyUnicode_AsUTF8(
PyObject_GetAttrString(tzinfo, "name"));
}
else if (PyObject_HasAttrString(tzinfo, "name"))
{
// Pendulum timezone
tz = (char *)PyUnicode_AsUTF8(
Expand Down
10 changes: 8 additions & 2 deletions pendulum/_extensions/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ def precise_diff(
:rtype: PreciseDiff
"""
print("DT", d1, d2)
sign = 1

if d1 == d2:
Expand Down Expand Up @@ -234,14 +235,19 @@ def precise_diff(
# Trying to figure out the timezone names
# If we can't find them, we assume different timezones
if tzinfo1 and tzinfo2:
if hasattr(tzinfo1, "name"):
if hasattr(tzinfo1, "key"):
# zoneinfo timezone
tz1 = tzinfo1.key
elif hasattr(tzinfo1, "name"):
# Pendulum timezone
tz1 = tzinfo1.name
elif hasattr(tzinfo1, "zone"):
# pytz timezone
tz1 = tzinfo1.zone

if hasattr(tzinfo2, "name"):
if hasattr(tzinfo2, "key"):
tz2 = tzinfo2.key
elif hasattr(tzinfo2, "name"):
tz2 = tzinfo2.name
elif hasattr(tzinfo2, "zone"):
tz2 = tzinfo2.zone
Expand Down
3 changes: 2 additions & 1 deletion pendulum/datetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
from .period import Period
from .time import Time
from .tz import UTC
from .tz.timezone import FixedTimezone
from .tz.timezone import Timezone


Expand Down Expand Up @@ -164,7 +165,7 @@ def offset_hours(self) -> int:

@property
def timezone(self) -> Optional[Timezone]:
if not isinstance(self.tzinfo, Timezone):
if not isinstance(self.tzinfo, (Timezone, FixedTimezone)):
return

return self.tzinfo
Expand Down
39 changes: 22 additions & 17 deletions pendulum/tz/__init__.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,38 @@
from typing import Tuple
import os

from typing import Union

import pytzdata
import tzdata

from .local_timezone import get_local_timezone
from .local_timezone import set_local_timezone
from .local_timezone import test_local_timezone
from .timezone import UTC
from .timezone import FixedTimezone as _FixedTimezone
from .timezone import Timezone as _Timezone
from .timezone import FixedTimezone
from .timezone import Timezone


PRE_TRANSITION = "pre"
POST_TRANSITION = "post"
TRANSITION_ERROR = "error"

timezones = pytzdata.timezones # type: Tuple[str, ...]
_timezones = None


_tz_cache = {}


def timezone(name, extended=True): # type: (Union[str, int], bool) -> _Timezone
def timezones():
global _timezones

if _timezones is None:
with open(os.path.join(os.path.dirname(tzdata.__file__), "zones")) as f:
_timezones = tuple(tz.strip() for tz in f.readlines())

return _timezones


def timezone(name: Union[str, int]) -> Union[Timezone, FixedTimezone]:
"""
Return a Timezone instance given its name.
"""
Expand All @@ -31,29 +42,23 @@ def timezone(name, extended=True): # type: (Union[str, int], bool) -> _Timezone
if name.lower() == "utc":
return UTC

if name in _tz_cache:
return _tz_cache[name]

tz = _Timezone(name, extended=extended)
_tz_cache[name] = tz

return tz
return Timezone(name)


def fixed_timezone(offset): # type: (int) -> _FixedTimezone
def fixed_timezone(offset: int) -> FixedTimezone:
"""
Return a Timezone instance given its offset in seconds.
"""
if offset in _tz_cache:
return _tz_cache[offset] # type: ignore
return _tz_cache[offset]

tz = _FixedTimezone(offset)
tz = FixedTimezone(offset)
_tz_cache[offset] = tz

return tz


def local_timezone(): # type: () -> _Timezone
def local_timezone() -> Timezone:
"""
Return the local timezone.
"""
Expand Down
5 changes: 5 additions & 0 deletions pendulum/tz/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ class TimezoneError(ValueError):
pass


class InvalidTimezone(TimezoneError):

pass


class NonExistingTime(TimezoneError):

message = "The datetime {} does not exist."
Expand Down
27 changes: 15 additions & 12 deletions pendulum/tz/local_timezone.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
from typing import Optional
from typing import Union

from pendulum.utils._compat import zoneinfo

from .timezone import FixedTimezone
from .timezone import Timezone
from .timezone import TimezoneFile
from .zoneinfo.exceptions import InvalidTimezone


try:
Expand All @@ -25,7 +26,7 @@
_local_timezone = None


def get_local_timezone(): # type: () -> Timezone
def get_local_timezone() -> Union[Timezone, FixedTimezone]:
global _local_timezone

if _mock_local_timezone is not None:
Expand All @@ -46,15 +47,15 @@ def set_local_timezone(mock=None): # type: (Optional[Union[str, Timezone]]) ->


@contextmanager
def test_local_timezone(mock): # type: (Timezone) -> Iterator[None]
def test_local_timezone(mock: Timezone) -> Iterator[None]:
set_local_timezone(mock)

yield

set_local_timezone()


def _get_system_timezone(): # type: () -> Timezone
def _get_system_timezone() -> Timezone:
if sys.platform == "win32":
return _get_windows_timezone()
elif "darwin" in sys.platform:
Expand All @@ -63,7 +64,7 @@ def _get_system_timezone(): # type: () -> Timezone
return _get_unix_timezone()


def _get_windows_timezone(): # type: () -> Timezone
def _get_windows_timezone() -> Timezone:
from .data.windows import windows_timezones

# Windows is special. It has unique time zone names (in several
Expand Down Expand Up @@ -142,7 +143,7 @@ def _get_windows_timezone(): # type: () -> Timezone
return Timezone(timezone)


def _get_darwin_timezone(): # type: () -> Timezone
def _get_darwin_timezone() -> Timezone:
# link will be something like /usr/share/zoneinfo/America/Los_Angeles.
link = os.readlink("/etc/localtime")
tzname = link[link.rfind("zoneinfo/") + 9 :]
Expand Down Expand Up @@ -212,7 +213,7 @@ def _get_unix_timezone(_root="/"): # type: (str) -> Timezone

try:
return Timezone(os.path.join(*tzpath))
except InvalidTimezone:
except zoneinfo.ZoneInfoNotFoundError:
pass

# systemd distributions use symlinks that include the zone name,
Expand All @@ -227,7 +228,7 @@ def _get_unix_timezone(_root="/"): # type: (str) -> Timezone
tzpath.insert(0, parts.pop(0))
try:
return Timezone(os.path.join(*tzpath))
except InvalidTimezone:
except zoneinfo.ZoneInfoNotFoundError:
pass

# No explicit setting existed. Use localtime
Expand All @@ -237,18 +238,20 @@ def _get_unix_timezone(_root="/"): # type: (str) -> Timezone
if not os.path.isfile(tzpath):
continue

return TimezoneFile(tzpath)
with open(tzpath, "rb") as f:
return Timezone.from_file(f)

raise RuntimeError("Unable to find any timezone configuration")


def _tz_from_env(tzenv): # type: (str) -> Timezone
def _tz_from_env(tzenv: str) -> Timezone:
if tzenv[0] == ":":
tzenv = tzenv[1:]

# TZ specifies a file
if os.path.isfile(tzenv):
return TimezoneFile(tzenv)
with open(tzenv, "rb") as f:
return Timezone.from_file(f)

# TZ specifies a zoneinfo zone.
try:
Expand Down
Loading

0 comments on commit ae2c9f5

Please sign in to comment.