Repository: sdispater/pendulum
Pull request #161 (2017-11-14 23:06:41)

Add deepcopy / pickle compatibility
Python's doc say: "A tzinfo subclass must have an __init__() method that can
be called with no arguments". TimezoneInfo doesn't honor this
requirement. Defining __getinitargs__ is sufficient to fix copy/deepcopy as
well as pickling/unpickling.

This is https://github.com/sdispater/pendulum/pull/134 implemented

bolkedebruin: @sdispater Can you please take a look?
sdispater: @bolkedebruin Looks good to me 👍 

I will merge this and make a new bugfix release later today.
bolkedebruin: That’s much appreciated!

Patch:
diff --git a/pendulum/tz/timezone.py b/pendulum/tz/timezone.py
index 33cd042..8859239 100644
--- a/pendulum/tz/timezone.py
+++ b/pendulum/tz/timezone.py
@@ -48,6 +48,7 @@ class Timezone(tzinfo):
         """
         self._name = name
         self._transitions = transitions
+        self.__tzinfos = tzinfos
         self._tzinfos = tuple(
             map(lambda tzinfo: TimezoneInfo(self, *tzinfo), tzinfos)
         )
@@ -449,6 +450,11 @@ class Timezone(tzinfo):
     def __repr__(self):
         return '<Timezone [{}]>'.format(self._name)
 
+    def __getinitargs__(self):
+        return (self._name, self._transitions,
+                self.__tzinfos, self._default_tzinfo_index,
+                self._utc_transition_times)
+
 
 class FixedTimezone(Timezone):
     """
@@ -486,6 +492,8 @@ class FixedTimezone(Timezone):
         )
         self._tzinfo = self._tzinfos[0]
 
+        self._offset = offset
+
     @classmethod
     def load(cls, name):
         if name not in cls._cache:
@@ -513,6 +521,9 @@ class FixedTimezone(Timezone):
 
         return (dt + self._tzinfo.adjusted_offset).replace(tzinfo=self._tzinfo)
 
+    def __getinitargs__(self):
+        return self._offset
+
 
 class _UTC(FixedTimezone):
 
@@ -524,4 +535,8 @@ class _UTC(FixedTimezone):
     def fromutc(self, dt):
         return dt.replace(tzinfo=UTC)
 
+    def __getinitargs__(self):
+        return ()
+
+
 UTCTimezone = _UTC()
diff --git a/pendulum/tz/timezone_info.py b/pendulum/tz/timezone_info.py
index 51d5afa..8dc1f4a 100644
--- a/pendulum/tz/timezone_info.py
+++ b/pendulum/tz/timezone_info.py
@@ -113,6 +113,8 @@ class TimezoneInfo(tzinfo):
             'DST' if self.is_dst else 'STD',
         )
 
+    def __getinitargs__(self):
+        return self._tz, self._utc_offset, self._is_dst, self._dst, self._abbrev
 
 class _UTC(TimezoneInfo):
 


Test patch:
diff --git a/tests/pendulum_tests/test_behavior.py b/tests/pendulum_tests/test_behavior.py
index ce8a817..12a1285 100644
--- a/tests/pendulum_tests/test_behavior.py
+++ b/tests/pendulum_tests/test_behavior.py
@@ -2,8 +2,10 @@
 
 import pickle
 import pendulum
+from copy import deepcopy
 from datetime import datetime, date, time, timedelta
 from pendulum import Pendulum, timezone
+from pendulum.tz.timezone import Timezone
 from .. import AbstractTestCase
 
 
@@ -103,3 +105,26 @@ class BehaviorTest(AbstractTestCase):
         dt = pendulum.create(1941, 7, 1, tz='Europe/Amsterdam')
 
         self.assertEqual(timedelta(0, 6000), dt.dst())
+
+    def test_deepcopy(self):
+        dt = pendulum.create(1941, 7, 1, tz='Europe/Amsterdam')
+
+        self.assertEqual(dt, deepcopy(dt))
+
+    def test_deepcopy_datetime(self):
+        dt = pendulum.create(1941, 7, 1, tz='Europe/Amsterdam')
+
+        self.assertEqual(dt._datetime, deepcopy(dt._datetime))
+
+    def test_pickle_timezone(self):
+        dt1 = pendulum.timezone('Europe/Amsterdam')
+        s = pickle.dumps(dt1)
+        dt2 = pickle.loads(s)
+
+        self.assertTrue(isinstance(dt2, Timezone))
+
+        dt1 = pendulum.timezone('UTC')
+        s = pickle.dumps(dt1)
+        dt2 = pickle.loads(s)
+
+        self.assertTrue(isinstance(dt2, Timezone))