Skip to content

Commit ae2c9f5

Browse files
committed
Use zoneinfo as a base Timezone implementation
1 parent 745a036 commit ae2c9f5

24 files changed

+259
-1202
lines changed

pendulum/__init__.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@
4747
from .tz import test_local_timezone
4848
from .tz import timezone
4949
from .tz import timezones
50-
from .tz.timezone import Timezone as _Timezone
50+
from .tz.timezone import FixedTimezone
51+
from .tz.timezone import Timezone
5152

5253

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

6061

6162
def _safe_timezone(
62-
obj: Optional[Union[str, float, _datetime.tzinfo, _Timezone]]
63-
) -> _Timezone:
63+
obj: Optional[Union[str, float, _datetime.tzinfo, Timezone]]
64+
) -> Timezone:
6465
"""
6566
Creates a timezone instance
6667
from a string, Timezone, TimezoneInfo or integer offset.
6768
"""
68-
if isinstance(obj, _Timezone):
69+
if isinstance(obj, (Timezone, FixedTimezone)):
6970
return obj
7071

7172
if obj is None or obj == "local":
@@ -99,7 +100,7 @@ def datetime(
99100
minute: int = 0,
100101
second: int = 0,
101102
microsecond: int = 0,
102-
tz: Optional[Union[str, float, _Timezone]] = UTC,
103+
tz: Optional[Union[str, float, Timezone]] = UTC,
103104
dst_rule: str = POST_TRANSITION,
104105
) -> DateTime:
105106
"""
@@ -173,7 +174,7 @@ def time(hour: int, minute: int = 0, second: int = 0, microsecond: int = 0) -> T
173174

174175

175176
def instance(
176-
dt: _datetime.datetime, tz: Optional[Union[str, _Timezone]] = UTC
177+
dt: _datetime.datetime, tz: Optional[Union[str, Timezone]] = UTC
177178
) -> DateTime:
178179
"""
179180
Create a DateTime instance from a datetime one.
@@ -187,7 +188,7 @@ def instance(
187188
tz = dt.tzinfo or tz
188189

189190
# Checking for pytz/tzinfo
190-
if isinstance(tz, _datetime.tzinfo) and not isinstance(tz, _Timezone):
191+
if isinstance(tz, _datetime.tzinfo) and not isinstance(tz, Timezone):
191192
# pytz
192193
if hasattr(tz, "localize") and tz.zone:
193194
tz = tz.zone
@@ -202,7 +203,7 @@ def instance(
202203
)
203204

204205

205-
def now(tz: Optional[Union[str, _Timezone]] = None) -> DateTime:
206+
def now(tz: Optional[Union[str, Timezone]] = None) -> DateTime:
206207
"""
207208
Get a DateTime instance for the current date and time.
208209
"""
@@ -237,21 +238,21 @@ def now(tz: Optional[Union[str, _Timezone]] = None) -> DateTime:
237238
)
238239

239240

240-
def today(tz: Union[str, _Timezone] = "local") -> DateTime:
241+
def today(tz: Union[str, Timezone] = "local") -> DateTime:
241242
"""
242243
Create a DateTime instance for today.
243244
"""
244245
return now(tz).start_of("day")
245246

246247

247-
def tomorrow(tz: Union[str, _Timezone] = "local") -> DateTime:
248+
def tomorrow(tz: Union[str, Timezone] = "local") -> DateTime:
248249
"""
249250
Create a DateTime instance for today.
250251
"""
251252
return today(tz).add(days=1)
252253

253254

254-
def yesterday(tz: Union[str, _Timezone] = "local") -> DateTime:
255+
def yesterday(tz: Union[str, Timezone] = "local") -> DateTime:
255256
"""
256257
Create a DateTime instance for today.
257258
"""
@@ -261,7 +262,7 @@ def yesterday(tz: Union[str, _Timezone] = "local") -> DateTime:
261262
def from_format(
262263
string: str,
263264
fmt: str,
264-
tz: Union[str, _Timezone] = UTC,
265+
tz: Union[str, Timezone] = UTC,
265266
locale: Optional[str] = None, # noqa
266267
) -> DateTime:
267268
"""
@@ -275,7 +276,7 @@ def from_format(
275276

276277

277278
def from_timestamp(
278-
timestamp: Union[int, float], tz: Union[str, _Timezone] = UTC
279+
timestamp: Union[int, float], tz: Union[str, Timezone] = UTC
279280
) -> DateTime:
280281
"""
281282
Create a DateTime instance from a timestamp.

pendulum/_extensions/_helpers.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,13 @@ char *_get_tz_name(PyObject *dt)
165165

166166
if (tzinfo != Py_None)
167167
{
168-
if (PyObject_HasAttrString(tzinfo, "name"))
168+
if (PyObject_HasAttrString(tzinfo, "key"))
169+
{
170+
// zoneinfo timezone
171+
tz = (char *)PyUnicode_AsUTF8(
172+
PyObject_GetAttrString(tzinfo, "name"));
173+
}
174+
else if (PyObject_HasAttrString(tzinfo, "name"))
169175
{
170176
// Pendulum timezone
171177
tz = (char *)PyUnicode_AsUTF8(

pendulum/_extensions/helpers.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ def precise_diff(
197197
198198
:rtype: PreciseDiff
199199
"""
200+
print("DT", d1, d2)
200201
sign = 1
201202

202203
if d1 == d2:
@@ -234,14 +235,19 @@ def precise_diff(
234235
# Trying to figure out the timezone names
235236
# If we can't find them, we assume different timezones
236237
if tzinfo1 and tzinfo2:
237-
if hasattr(tzinfo1, "name"):
238+
if hasattr(tzinfo1, "key"):
239+
# zoneinfo timezone
240+
tz1 = tzinfo1.key
241+
elif hasattr(tzinfo1, "name"):
238242
# Pendulum timezone
239243
tz1 = tzinfo1.name
240244
elif hasattr(tzinfo1, "zone"):
241245
# pytz timezone
242246
tz1 = tzinfo1.zone
243247

244-
if hasattr(tzinfo2, "name"):
248+
if hasattr(tzinfo2, "key"):
249+
tz2 = tzinfo2.key
250+
elif hasattr(tzinfo2, "name"):
245251
tz2 = tzinfo2.name
246252
elif hasattr(tzinfo2, "zone"):
247253
tz2 = tzinfo2.zone

pendulum/datetime.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
from .period import Period
3838
from .time import Time
3939
from .tz import UTC
40+
from .tz.timezone import FixedTimezone
4041
from .tz.timezone import Timezone
4142

4243

@@ -164,7 +165,7 @@ def offset_hours(self) -> int:
164165

165166
@property
166167
def timezone(self) -> Optional[Timezone]:
167-
if not isinstance(self.tzinfo, Timezone):
168+
if not isinstance(self.tzinfo, (Timezone, FixedTimezone)):
168169
return
169170

170171
return self.tzinfo

pendulum/tz/__init__.py

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,38 @@
1-
from typing import Tuple
1+
import os
2+
23
from typing import Union
34

4-
import pytzdata
5+
import tzdata
56

67
from .local_timezone import get_local_timezone
78
from .local_timezone import set_local_timezone
89
from .local_timezone import test_local_timezone
910
from .timezone import UTC
10-
from .timezone import FixedTimezone as _FixedTimezone
11-
from .timezone import Timezone as _Timezone
11+
from .timezone import FixedTimezone
12+
from .timezone import Timezone
1213

1314

1415
PRE_TRANSITION = "pre"
1516
POST_TRANSITION = "post"
1617
TRANSITION_ERROR = "error"
1718

18-
timezones = pytzdata.timezones # type: Tuple[str, ...]
19+
_timezones = None
1920

2021

2122
_tz_cache = {}
2223

2324

24-
def timezone(name, extended=True): # type: (Union[str, int], bool) -> _Timezone
25+
def timezones():
26+
global _timezones
27+
28+
if _timezones is None:
29+
with open(os.path.join(os.path.dirname(tzdata.__file__), "zones")) as f:
30+
_timezones = tuple(tz.strip() for tz in f.readlines())
31+
32+
return _timezones
33+
34+
35+
def timezone(name: Union[str, int]) -> Union[Timezone, FixedTimezone]:
2536
"""
2637
Return a Timezone instance given its name.
2738
"""
@@ -31,29 +42,23 @@ def timezone(name, extended=True): # type: (Union[str, int], bool) -> _Timezone
3142
if name.lower() == "utc":
3243
return UTC
3344

34-
if name in _tz_cache:
35-
return _tz_cache[name]
36-
37-
tz = _Timezone(name, extended=extended)
38-
_tz_cache[name] = tz
39-
40-
return tz
45+
return Timezone(name)
4146

4247

43-
def fixed_timezone(offset): # type: (int) -> _FixedTimezone
48+
def fixed_timezone(offset: int) -> FixedTimezone:
4449
"""
4550
Return a Timezone instance given its offset in seconds.
4651
"""
4752
if offset in _tz_cache:
48-
return _tz_cache[offset] # type: ignore
53+
return _tz_cache[offset]
4954

50-
tz = _FixedTimezone(offset)
55+
tz = FixedTimezone(offset)
5156
_tz_cache[offset] = tz
5257

5358
return tz
5459

5560

56-
def local_timezone(): # type: () -> _Timezone
61+
def local_timezone() -> Timezone:
5762
"""
5863
Return the local timezone.
5964
"""

pendulum/tz/exceptions.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ class TimezoneError(ValueError):
33
pass
44

55

6+
class InvalidTimezone(TimezoneError):
7+
8+
pass
9+
10+
611
class NonExistingTime(TimezoneError):
712

813
message = "The datetime {} does not exist."

pendulum/tz/local_timezone.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77
from typing import Optional
88
from typing import Union
99

10+
from pendulum.utils._compat import zoneinfo
11+
12+
from .timezone import FixedTimezone
1013
from .timezone import Timezone
11-
from .timezone import TimezoneFile
12-
from .zoneinfo.exceptions import InvalidTimezone
1314

1415

1516
try:
@@ -25,7 +26,7 @@
2526
_local_timezone = None
2627

2728

28-
def get_local_timezone(): # type: () -> Timezone
29+
def get_local_timezone() -> Union[Timezone, FixedTimezone]:
2930
global _local_timezone
3031

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

4748

4849
@contextmanager
49-
def test_local_timezone(mock): # type: (Timezone) -> Iterator[None]
50+
def test_local_timezone(mock: Timezone) -> Iterator[None]:
5051
set_local_timezone(mock)
5152

5253
yield
5354

5455
set_local_timezone()
5556

5657

57-
def _get_system_timezone(): # type: () -> Timezone
58+
def _get_system_timezone() -> Timezone:
5859
if sys.platform == "win32":
5960
return _get_windows_timezone()
6061
elif "darwin" in sys.platform:
@@ -63,7 +64,7 @@ def _get_system_timezone(): # type: () -> Timezone
6364
return _get_unix_timezone()
6465

6566

66-
def _get_windows_timezone(): # type: () -> Timezone
67+
def _get_windows_timezone() -> Timezone:
6768
from .data.windows import windows_timezones
6869

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

144145

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

213214
try:
214215
return Timezone(os.path.join(*tzpath))
215-
except InvalidTimezone:
216+
except zoneinfo.ZoneInfoNotFoundError:
216217
pass
217218

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

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

240-
return TimezoneFile(tzpath)
241+
with open(tzpath, "rb") as f:
242+
return Timezone.from_file(f)
241243

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

244246

245-
def _tz_from_env(tzenv): # type: (str) -> Timezone
247+
def _tz_from_env(tzenv: str) -> Timezone:
246248
if tzenv[0] == ":":
247249
tzenv = tzenv[1:]
248250

249251
# TZ specifies a file
250252
if os.path.isfile(tzenv):
251-
return TimezoneFile(tzenv)
253+
with open(tzenv, "rb") as f:
254+
return Timezone.from_file(f)
252255

253256
# TZ specifies a zoneinfo zone.
254257
try:

0 commit comments

Comments
 (0)