Skip to content

Commit ccc12b7

Browse files
committed
Improves performances of timezone switching
1 parent 86117d4 commit ccc12b7

File tree

6 files changed

+60
-39
lines changed

6 files changed

+60
-39
lines changed

pendulum/tz/timezone.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from datetime import datetime
55

66
from .loader import Loader
7-
from .timezone_info import TimezoneInfo
7+
from .timezone_info import TimezoneInfo, UTC
88
from .breakdown import local_time as _local_time
99
from .transition_type import TransitionType
1010

@@ -73,6 +73,9 @@ def convert(self, dt):
7373
else:
7474
converted = self._convert(dt)
7575

76+
if not isinstance(converted, tuple):
77+
return converted
78+
7679
return dt.__class__(*converted)
7780

7881
def _normalize(self, dt):
@@ -95,7 +98,6 @@ def _normalize(self, dt):
9598
# Find the first transition after our target date/time
9699
begin = self._transitions[0]
97100
end = self._transitions[-1]
98-
offset = None
99101

100102
if dt < begin.time:
101103
tr = begin
@@ -118,6 +120,10 @@ def _normalize(self, dt):
118120
# Before first transition, so use the default offset.
119121
offset = self._default_transition_type.utc_offset
120122
unix_time = (dt - datetime(1970, 1, 1)).total_seconds() - offset
123+
124+
return self._to_local_time(
125+
unix_time, self._default_transition_type
126+
)
121127
else:
122128
# tr.pre_time < dt < tr.time
123129
# Skipped time
@@ -152,7 +158,7 @@ def _normalize(self, dt):
152158

153159
transition_type = tr.pre_transition_type
154160

155-
return self._to_local_time(unix_time, transition_type, offset)
161+
return self._to_local_time(unix_time, transition_type)
156162

157163
def _convert(self, dt):
158164
"""
@@ -173,23 +179,21 @@ def _convert(self, dt):
173179
if not self._transitions:
174180
transition_type = self._default_transition_type
175181
else:
176-
idx = max(0, self._find_transition_index(unix_time) - 1)
182+
idx = max(0, self._find_transition_index(unix_time, '_unix_time') - 1)
177183
tr = self._transitions[idx]
178184
transition_type = tr.transition_type
179185

180186
return self._to_local_time(unix_time, transition_type)
181187

182-
def _to_local_time(self, unix_time, transition_type, offset=None):
188+
def _to_local_time(self, unix_time, transition_type):
183189
local_time = _local_time(
184190
unix_time,
185191
transition_type
186192
)
187193

188194
tzinfo = TimezoneInfo(
189195
self,
190-
offset if offset is not None else transition_type.utc_offset,
191-
transition_type.is_dst,
192-
transition_type.abbrev
196+
transition_type
193197
)
194198

195199
return local_time[:7] + (tzinfo,)
@@ -211,13 +215,9 @@ def _get_timestamp(self, dt):
211215

212216
return t
213217

214-
def _find_transition_index(self, dt):
218+
def _find_transition_index(self, dt, prop='_time'):
215219
lo, hi = 0, len(self._transitions)
216220

217-
prop = '_time'
218-
if isinstance(dt, (int, float)):
219-
prop = '_unix_time'
220-
221221
while lo < hi:
222222
mid = (lo + hi) // 2
223223
if dt < getattr(self._transitions[mid], prop):
@@ -258,11 +258,11 @@ class _UTC(Timezone):
258258
def __init__(self):
259259
super(_UTC, self).__init__('UTC', [], [], TransitionType(0, False, 'GMT'))
260260

261-
self._tzinfo = TimezoneInfo(self, 0, False, 'UTC')
261+
UTC._tz = self
262+
self._tzinfo = UTC
262263

263264
@property
264265
def tzinfo(self):
265266
return self._tzinfo
266267

267268
UTCTimezone = _UTC()
268-
UTC = UTCTimezone.tzinfo

pendulum/tz/timezone_info.py

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,49 @@
11
# -*- coding: utf-8 -*-
22

3-
from datetime import tzinfo, timedelta
3+
from datetime import tzinfo
4+
5+
from .transition_type import TransitionType
46

57

68
class TimezoneInfo(tzinfo):
79

8-
def __init__(self, tz, offset, is_dst, abbrev):
10+
def __init__(self, tz, transition_type):
911
"""
1012
:type tz: Timezone
1113
12-
:type offset: int
14+
:type transition_type: TransitionType
1315
1416
:type is_dst: bool
1517
"""
1618
self._tz = tz
17-
self._offset = offset
19+
self._transition_type = transition_type
1820

19-
# Rounded to the nearest minute
20-
# This is a fix so that it works
21-
# with the datetime objects
22-
self._adjusted_offset = round(offset / 60) * 60
23-
self._is_dst = is_dst
24-
self._abbrev = abbrev
21+
@classmethod
22+
def create(cls, tz, utc_offset, is_dst, abbrev):
23+
return cls(tz, TransitionType(utc_offset, is_dst, abbrev))
2524

2625
@property
2726
def tz(self):
2827
return self._tz
2928

3029
@property
3130
def name(self):
32-
return self.tz._name
31+
return self._tz._name
3332

3433
@property
3534
def offset(self):
36-
return self._offset
35+
return self._transition_type.utc_offset
3736

3837
@property
3938
def is_dst(self):
40-
return self._is_dst
39+
return self._transition_type.is_dst
4140

4241
@property
4342
def abbrev(self):
44-
return self._abbrev
43+
return self._transition_type.abbrev
4544

4645
def tzname(self, dt):
47-
return self._abbrev
46+
return self.abbrev
4847

4948
def utcoffset(self, dt):
5049
if dt is None:
@@ -54,7 +53,7 @@ def utcoffset(self, dt):
5453

5554
return dt.tzinfo._adjusted_offset
5655
else:
57-
return timedelta(seconds=self._adjusted_offset)
56+
return self._transition_type.adjusted_offset
5857

5958
def dst(self, dt):
6059
if not self.is_dst:
@@ -65,15 +64,13 @@ def dst(self, dt):
6564
elif dt.tzinfo is not self:
6665
dt = self.tz.convert(dt)
6766

68-
offset = dt.tzinfo._adjusted_offset
67+
offset = dt.tzinfo._transition_type.adjusted_offset
6968
else:
70-
offset = self._adjusted_offset
69+
offset = self._transition_type.adjusted_offset
7170

72-
return timedelta(seconds=offset)
71+
return offset
7372

7473
def fromutc(self, dt):
75-
from .timezone import UTC
76-
7774
dt = dt.replace(tzinfo=UTC)
7875

7976
return self.tz.convert(dt)
@@ -84,3 +81,24 @@ def __repr__(self):
8481
self.offset,
8582
self.is_dst
8683
)
84+
85+
86+
class _UTC(TimezoneInfo):
87+
88+
def __init__(self):
89+
super(_UTC, self).__init__(None, TransitionType(0, False, 'GMT'))
90+
91+
@property
92+
def name(self):
93+
return 'UTC'
94+
95+
def utcoffset(self, dt):
96+
return self._transition_type.adjusted_offset
97+
98+
def dst(self, dt):
99+
return None
100+
101+
def fromutc(self, dt):
102+
return dt.replace(tzinfo=self)
103+
104+
UTC = _UTC()

pendulum/tz/transition.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,5 +106,5 @@ def __repr__(self):
106106
return '<Transition [{}, {} -> {}]>'.format(
107107
self._unix_time,
108108
self._pre_time,
109-
self._time
109+
self.time
110110
)

pendulum/tz/transition_type.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
# -*- coding: utf-8 -*-
22

3+
from datetime import timedelta
4+
35

46
class TransitionType(object):
57

68
def __init__(self, utc_offset, is_dst, abbrev):
79
self.utc_offset = utc_offset
10+
self.adjusted_offset = timedelta(seconds=round(utc_offset / 60) * 60)
811
self.is_dst = is_dst
912
self.abbrev = abbrev
1013

tests/pendulum_tests/test_construct.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,5 +112,5 @@ def test_instance_naive_datetime_defaults_to_utc(self):
112112
self.assertEqual('UTC', now.timezone_name)
113113

114114
def test_instance_timezone_aware_datetime(self):
115-
now = Pendulum.instance(datetime.now(TimezoneInfo(timezone('Europe/Paris'), 7200, True, 'EST')))
115+
now = Pendulum.instance(datetime.now(TimezoneInfo.create(timezone('Europe/Paris'), 7200, True, 'EST')))
116116
self.assertEqual('Europe/Paris', now.timezone_name)

tests/pendulum_tests/test_create_from_date.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,6 @@ def test_create_from_date_with_timezone(self):
4444

4545
def test_create_from_date_with_tzinfo(self):
4646
tz = timezone('Europe/London')
47-
d = Pendulum.create_from_date(1975, 12, 25, tz=TimezoneInfo(tz, 3600, True, ''))
47+
d = Pendulum.create_from_date(1975, 12, 25, tz=TimezoneInfo.create(tz, 3600, True, ''))
4848
self.assertPendulum(d, 1975, 12, 25)
4949
self.assertEqual('Europe/London', d.timezone_name)

0 commit comments

Comments
 (0)