[Zope3-checkins] SVN: Zope3/branches/3.2/src/ Merge revisions 40888 and 40903 from trunk.

Gary Poster gary at zope.com
Tue Dec 20 10:06:54 EST 2005


Log message for revision 40907:
  Merge revisions 40888 and 40903 from trunk.
  
  Merge trunk 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.
  
  Also merge trunk revision 40903:
    Incorporate Stuart Bishop's feedback to pytz changes.
  
  

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

-=-
Modified: Zope3/branches/3.2/src/pytz/__init__.py
===================================================================
--- Zope3/branches/3.2/src/pytz/__init__.py	2005-12-20 15:05:46 UTC (rev 40906)
+++ Zope3/branches/3.2/src/pytz/__init__.py	2005-12-20 15:06:53 UTC (rev 40907)
@@ -192,6 +192,111 @@
                 _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 localize(self, dt, is_dst=False):
+        '''Convert naive time to local time'''
+        if dt.tzinfo is not None:
+            raise ValueError, 'Not naive datetime (tzinfo is already set)'
+        return dt.replace(tzinfo=self)
+
+    def normalize(self, dt, is_dst=False):
+        '''Correct the timezone information on the given datetime'''
+        if dt.tzinfo is None:
+            raise ValueError, 'Naive time - no tzinfo set'
+        return dt.replace(tzinfo=self)
+
+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
+
+FixedOffset.__safe_for_unpickling__ = True
+
 def _test():
     import doctest, os, sys
     sys.path.insert(0, os.pardir)

Modified: Zope3/branches/3.2/src/zope/i18n/format.py
===================================================================
--- Zope3/branches/3.2/src/zope/i18n/format.py	2005-12-20 15:05:46 UTC (rev 40906)
+++ Zope3/branches/3.2/src/zope/i18n/format.py	2005-12-20 15:06:53 UTC (rev 40907)
@@ -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/branches/3.2/src/zope/i18n/tests/test_formats.py
===================================================================
--- Zope3/branches/3.2/src/zope/i18n/tests/test_formats.py	2005-12-20 15:05:46 UTC (rev 40906)
+++ Zope3/branches/3.2/src/zope/i18n/tests/test_formats.py	2005-12-20 15:06:53 UTC (rev 40907)
@@ -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