Skip to content

Commit 7b9a181

Browse files
bolkedebruinsdispater
authored andcommitted
Add deepcopy / pickle compatibility (#161)
Python's doc say: "A tzinfo subclass must have an __init__() method that can be called with no arguments". Timezone and TimezoneInfo don't honor this requirement. Defining __getinitargs__ is sufficient to fix copy/deepcopy as well as pickling/unpickling. This introduces __tzinfos to Timezone as otherwise recursion errors will happen due to TimezoneInfo having a reference to Timezone.
1 parent a39d8ec commit 7b9a181

File tree

3 files changed

+42
-0
lines changed

3 files changed

+42
-0
lines changed

pendulum/tz/timezone.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ def __init__(self, name, transitions,
4848
"""
4949
self._name = name
5050
self._transitions = transitions
51+
self.__tzinfos = tzinfos
5152
self._tzinfos = tuple(
5253
map(lambda tzinfo: TimezoneInfo(self, *tzinfo), tzinfos)
5354
)
@@ -449,6 +450,11 @@ def _find_utc_index(self, dt):
449450
def __repr__(self):
450451
return '<Timezone [{}]>'.format(self._name)
451452

453+
def __getinitargs__(self):
454+
return (self._name, self._transitions,
455+
self.__tzinfos, self._default_tzinfo_index,
456+
self._utc_transition_times)
457+
452458

453459
class FixedTimezone(Timezone):
454460
"""
@@ -486,6 +492,8 @@ def __init__(self, offset, name=None, transition_type=None):
486492
)
487493
self._tzinfo = self._tzinfos[0]
488494

495+
self._offset = offset
496+
489497
@classmethod
490498
def load(cls, name):
491499
if name not in cls._cache:
@@ -513,6 +521,9 @@ def fromutc(self, dt):
513521

514522
return (dt + self._tzinfo.adjusted_offset).replace(tzinfo=self._tzinfo)
515523

524+
def __getinitargs__(self):
525+
return self._offset
526+
516527

517528
class _UTC(FixedTimezone):
518529

@@ -524,4 +535,8 @@ def __init__(self):
524535
def fromutc(self, dt):
525536
return dt.replace(tzinfo=UTC)
526537

538+
def __getinitargs__(self):
539+
return ()
540+
541+
527542
UTCTimezone = _UTC()

pendulum/tz/timezone_info.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ def __repr__(self):
113113
'DST' if self.is_dst else 'STD',
114114
)
115115

116+
def __getinitargs__(self):
117+
return self._tz, self._utc_offset, self._is_dst, self._dst, self._abbrev
116118

117119
class _UTC(TimezoneInfo):
118120

tests/pendulum_tests/test_behavior.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
import pickle
44
import pendulum
5+
from copy import deepcopy
56
from datetime import datetime, date, time, timedelta
67
from pendulum import Pendulum, timezone
8+
from pendulum.tz.timezone import Timezone
79
from .. import AbstractTestCase
810

911

@@ -103,3 +105,26 @@ def test_proper_dst(self):
103105
dt = pendulum.create(1941, 7, 1, tz='Europe/Amsterdam')
104106

105107
self.assertEqual(timedelta(0, 6000), dt.dst())
108+
109+
def test_deepcopy(self):
110+
dt = pendulum.create(1941, 7, 1, tz='Europe/Amsterdam')
111+
112+
self.assertEqual(dt, deepcopy(dt))
113+
114+
def test_deepcopy_datetime(self):
115+
dt = pendulum.create(1941, 7, 1, tz='Europe/Amsterdam')
116+
117+
self.assertEqual(dt._datetime, deepcopy(dt._datetime))
118+
119+
def test_pickle_timezone(self):
120+
dt1 = pendulum.timezone('Europe/Amsterdam')
121+
s = pickle.dumps(dt1)
122+
dt2 = pickle.loads(s)
123+
124+
self.assertTrue(isinstance(dt2, Timezone))
125+
126+
dt1 = pendulum.timezone('UTC')
127+
s = pickle.dumps(dt1)
128+
dt2 = pickle.loads(s)
129+
130+
self.assertTrue(isinstance(dt2, Timezone))

0 commit comments

Comments
 (0)