[Zope3-checkins] SVN: Zope3/trunk/src/ Add a picklable offset-based timezone to pytz, a la zope.app.datetimeutils. Add tests in zope.i18n to show that we need something like it, and then actually use it in zope.18n.format.

Gary Poster gary at zope.com
Mon Dec 19 14:31:05 EST 2005


Log message for revision 40888:
  Add a picklable offset-based timezone to pytz, a la zope.app.datetimeutils.  Add tests in zope.i18n to show that we need something like it, and then actually use it in zope.18n.format.
  
  The changes to pytz need to be approved by Stuart Bishop.  This fix needs to then be ported to the 3.2 branch.
  
  

Changed:
  U   Zope3/trunk/src/pytz/__init__.py
  U   Zope3/trunk/src/zope/i18n/format.py
  U   Zope3/trunk/src/zope/i18n/tests/test_formats.py

-=-
Modified: Zope3/trunk/src/pytz/__init__.py
===================================================================
--- Zope3/trunk/src/pytz/__init__.py	2005-12-19 18:03:34 UTC (rev 40887)
+++ Zope3/trunk/src/pytz/__init__.py	2005-12-19 19:31:05 UTC (rev 40888)
@@ -192,6 +192,99 @@
                 _country_timezones_cache[code] = [zone]
     return _country_timezones_cache[iso3166_code]
 
+# Time-zone info based solely on fixed offsets
+
+class _FixedOffset(datetime.tzinfo):
+
+    zone = None # to match the standard pytz API
+
+    def __init__(self, minutes):
+        if abs(minutes) >= 1440:
+            raise ValueError("absolute offset is too large", minutes)
+        self._minutes = minutes
+        self._offset = datetime.timedelta(minutes=minutes)
+
+    def utcoffset(self, dt):
+        return self._offset
+
+    def __reduce__(self):
+        return FixedOffset, (self._minutes, )
+
+    def dst(self, dt):
+        return None
+    
+    def tzname(self, dt):
+        return None
+
+    def __repr__(self):
+        return 'pytz.FixedOffset(%d)' % self._minutes
+
+def FixedOffset(offset, _tzinfos = {}):
+    """return a fixed-offset timezone based off a number of minutes.
+    
+        >>> one = FixedOffset(-330)
+        >>> one
+        pytz.FixedOffset(-330)
+        >>> one.utcoffset(datetime.datetime.now())
+        datetime.timedelta(-1, 66600)
+
+        >>> two = FixedOffset(1380)
+        >>> two
+        pytz.FixedOffset(1380)
+        >>> two.utcoffset(datetime.datetime.now())
+        datetime.timedelta(0, 82800)
+    
+    The datetime.timedelta must be between the range of -1 and 1 day,
+    non-inclusive.
+
+        >>> FixedOffset(1440)
+        Traceback (most recent call last):
+        ...
+        ValueError: ('absolute offset is too large', 1440)
+
+        >>> FixedOffset(-1440)
+        Traceback (most recent call last):
+        ...
+        ValueError: ('absolute offset is too large', -1440)
+
+    An offset of 0 is special-cased to return UTC.
+
+        >>> FixedOffset(0) is UTC
+        True
+
+    There should always be only one instance of a FixedOffset per timedelta.
+    This should be true for multiple creation calls.
+    
+        >>> FixedOffset(-330) is one
+        True
+        >>> FixedOffset(1380) is two
+        True
+
+    It should also be true for pickling.
+
+        >>> import pickle
+        >>> pickle.loads(pickle.dumps(one)) is one
+        True
+        >>> pickle.loads(pickle.dumps(two)) is two
+        True
+
+    """
+
+    if offset == 0:
+        return UTC
+
+    info = _tzinfos.get(offset)
+    if info is None:
+        # We haven't seen this one before. we need to save it.
+
+        # Use setdefault to avoid a race condition and make sure we have
+        # only one
+        info = _tzinfos.setdefault(offset, _FixedOffset(offset))
+
+    return info
+
+tzinfo.__safe_for_unpickling__ = True
+
 def _test():
     import doctest, os, sys
     sys.path.insert(0, os.pardir)

Modified: Zope3/trunk/src/zope/i18n/format.py
===================================================================
--- Zope3/trunk/src/zope/i18n/format.py	2005-12-19 18:03:34 UTC (rev 40887)
+++ Zope3/trunk/src/zope/i18n/format.py	2005-12-19 19:31:05 UTC (rev 40888)
@@ -137,20 +137,10 @@
             value = results[bin_pattern.index(tz_entry[0])]
             if length == 1:
                 hours, mins = int(value[:-2]), int(value[-2:])
-                delta = datetime.timedelta(hours=hours, minutes=mins)
-                # XXX: I think this is making an unpicklable tzinfo.
-                # Note that StaticTzInfo is not part of the exposed pytz API.
-                tzinfo = pytz.tzinfo.StaticTzInfo()
-                tzinfo._utcoffset = delta
-                pytz_tzinfo = True
+                tzinfo = pytz.FixedOffset(hours * 60 + mins)
             elif length == 2:
                 hours, mins = int(value[:-3]), int(value[-2:])
-                delta = datetime.timedelta(hours=hours, minutes=mins)
-                # XXX: I think this is making an unpicklable tzinfo.
-                # Note that StaticTzInfo is not part of the exposed pytz API.
-                tzinfo = pytz.tzinfo.StaticTzInfo()
-                tzinfo._utcoffset = delta
-                pytz_tzinfo = True
+                tzinfo = pytz.FixedOffset(hours * 60 + mins)
             else:
                 try:
                     tzinfo = pytz.timezone(value)

Modified: Zope3/trunk/src/zope/i18n/tests/test_formats.py
===================================================================
--- Zope3/trunk/src/zope/i18n/tests/test_formats.py	2005-12-19 18:03:34 UTC (rev 40887)
+++ Zope3/trunk/src/zope/i18n/tests/test_formats.py	2005-12-19 19:31:05 UTC (rev 40888)
@@ -18,6 +18,7 @@
 import os
 import datetime
 import pytz
+import pickle
 from unittest import TestCase, TestSuite, makeSuite
 
 from zope.i18n.interfaces import IDateTimeFormat
@@ -292,11 +293,13 @@
 
     def testParseTimeZone(self):
         dt = self.format.parse('09:48 -600', 'HH:mm z')
+        pickle.loads(pickle.dumps(dt)) == dt
         self.assertEqual(dt.tzinfo.utcoffset(dt), datetime.timedelta(hours=-6))
         self.assertEqual(dt.tzinfo.zone, None)
         self.assertEqual(dt.tzinfo.tzname(dt), None)
 
         dt = self.format.parse('09:48 -06:00', 'HH:mm zz')
+        pickle.loads(pickle.dumps(dt)) == dt
         self.assertEqual(dt.tzinfo.utcoffset(dt), datetime.timedelta(hours=-6))
         self.assertEqual(dt.tzinfo.zone, None)
         self.assertEqual(dt.tzinfo.tzname(dt), None)
@@ -306,12 +309,14 @@
         # interpretation (other countries also use the EST timezone
         # abbreviation)
         dt = self.format.parse('01.01.2003 09:48 EST', 'dd.MM.yyyy HH:mm zzz')
+        pickle.loads(pickle.dumps(dt)) == dt
         self.assertEqual(dt.tzinfo.utcoffset(dt), datetime.timedelta(hours=-5))
         self.assertEqual(dt.tzinfo.zone, 'EST')
         self.assertEqual(dt.tzinfo.tzname(dt), 'EST')
 
         dt = self.format.parse('01.01.2003 09:48 US/Eastern',
                                'dd.MM.yyyy HH:mm zzzz')
+        pickle.loads(pickle.dumps(dt)) == dt
         self.assertEqual(dt.tzinfo.utcoffset(dt), datetime.timedelta(hours=-5))
         self.assertEqual(dt.tzinfo.zone, 'US/Eastern')
         self.assertEqual(dt.tzinfo.tzname(dt), 'EST')



More information about the Zope3-Checkins mailing list