[Checkins] SVN: Zope/trunk/ Merged amos-datetime-pytz branch to trunk (r80618:80906)

Amos Latteier amos at latteier.com
Wed Oct 17 18:20:18 EDT 2007


Log message for revision 80907:
  Merged amos-datetime-pytz branch to trunk (r80618:80906)
  
  Now DateTime will use pytz for timezone data. The main advantage of this 
  is that pytz has up to date daylight savings data.
  
  

Changed:
  U   Zope/trunk/doc/CHANGES.txt
  U   Zope/trunk/lib/python/DateTime/DateTime.py
  U   Zope/trunk/lib/python/DateTime/DateTime.txt
  A   Zope/trunk/lib/python/DateTime/pytz.txt
  A   Zope/trunk/lib/python/DateTime/pytz_support.py
  U   Zope/trunk/lib/python/DateTime/tests/testDateTime.py

-=-
Modified: Zope/trunk/doc/CHANGES.txt
===================================================================
--- Zope/trunk/doc/CHANGES.txt	2007-10-17 19:59:06 UTC (rev 80906)
+++ Zope/trunk/doc/CHANGES.txt	2007-10-17 22:20:17 UTC (rev 80907)
@@ -173,6 +173,10 @@
         view to work. (patch by Sidnei da Silva from Enfold,
         integration by Martijn Faassen (Startifact) for Infrae)
 
+      - DateTime now uses pytz for time zone data if available. This
+        means support for more time zones and up to date daylight
+        saving time information.
+
     Bugs Fixed
 
       - Launchpad #147201: treat container-class in zope.conf as a string,

Modified: Zope/trunk/lib/python/DateTime/DateTime.py
===================================================================
--- Zope/trunk/lib/python/DateTime/DateTime.py	2007-10-17 19:59:06 UTC (rev 80906)
+++ Zope/trunk/lib/python/DateTime/DateTime.py	2007-10-17 22:20:17 UTC (rev 80907)
@@ -22,6 +22,11 @@
 from interfaces import IDateTime
 from interfaces import DateTimeError, SyntaxError, DateError, TimeError
 from zope.interface import implements
+try:
+    from pytz_support import PytzCache    
+except ImportError:
+    # pytz not available, use legacy timezone support
+    PytzCache = None
 
 default_datefmt = None
 
@@ -951,7 +956,10 @@
     _isDST = localtime(time())[8]
     _localzone  = _isDST and _localzone1 or _localzone0
 
-    _tzinfo     = _cache()
+    if PytzCache is not None:
+        _tzinfo = PytzCache(_cache())
+    else:
+        _tzinfo = _cache()
 
     def localZone(self, ltm=None):
         '''Returns the time zone on the given date.  The time zone
@@ -1935,4 +1943,6 @@
 # Module methods
 def Timezones():
     """Return the list of recognized timezone names"""
+    if PytzCache is not None:
+        return list(PytzCache(_cache())._zlst)
     return _cache._zlst

Modified: Zope/trunk/lib/python/DateTime/DateTime.txt
===================================================================
--- Zope/trunk/lib/python/DateTime/DateTime.txt	2007-10-17 19:59:06 UTC (rev 80906)
+++ Zope/trunk/lib/python/DateTime/DateTime.txt	2007-10-17 22:20:17 UTC (rev 80907)
@@ -12,7 +12,7 @@
 
   >>> from DateTime import Timezones
   >>> Timezones() # doctest: +ELLIPSIS
-  ['Brazil/Acre', 'Brazil/DeNoronha', ..., 'NZST', 'IDLE']
+  [...'Brazil/Acre', 'Brazil/DeNoronha', ..., 'NZST', 'IDLE']
 
 
 Class DateTime
@@ -229,7 +229,7 @@
   object, represented in the indicated timezone:
 
     >>> dt.toZone('UTC')
-    DateTime('1997/03/09 18:45:00 Universal')
+    DateTime('1997/03/09 18:45:00 UTC')
 
     >>> dt.toZone('UTC') == dt
     True

Copied: Zope/trunk/lib/python/DateTime/pytz.txt (from rev 80906, Zope/branches/amos-datetime-pytz/lib/python/DateTime/pytz.txt)
===================================================================
--- Zope/trunk/lib/python/DateTime/pytz.txt	                        (rev 0)
+++ Zope/trunk/lib/python/DateTime/pytz.txt	2007-10-17 22:20:17 UTC (rev 80907)
@@ -0,0 +1,193 @@
+Pytz Support
+============
+If the pytz package is importable, it will be used for time zone
+information, otherwise, DateTime's own time zone database is used. The
+advantage of using pytz is that it has a more complete and up to date
+time zone and daylight savings time database.
+
+This test assumes that pytz is available.
+
+    >>> import pytz
+
+Usage
+-----
+If pytz is available, then DateTime will use it. You don't have to do
+anything special to make it work.
+
+    >>> from DateTime import DateTime, Timezones
+    >>> d = DateTime('March 11, 2007 US/Eastern')
+
+Daylight Savings
+----------------
+In 2007 daylight savings time in the US was changed. The Energy Policy
+Act of 2005 mandates that DST will start on the second Sunday in March
+and end on the first Sunday in November.
+
+In 2007, the start and stop dates are March 11 and November 4,
+respectively. These dates are different from previous DST start and
+stop dates. In 2006, the dates were the first Sunday in April (April
+2, 2006) and the last Sunday in October (October 29, 2006).
+
+Let's make sure that DateTime can deal with this, since the primary
+motivation to use pytz for time zone information is the fact that it
+is kept up to date with daylight savings changes.
+
+    >>> DateTime('March 11, 2007 US/Eastern').tzoffset()
+    -18000
+    >>> DateTime('March 12, 2007 US/Eastern').tzoffset()
+    -14400
+    >>> DateTime('November 4, 2007 US/Eastern').tzoffset()
+    -14400
+    >>> DateTime('November 5, 2007 US/Eastern').tzoffset()
+    -18000
+
+Let's compare this to 2006.
+
+    >>> DateTime('April 2, 2006 US/Eastern').tzoffset()
+    -18000
+    >>> DateTime('April 3, 2006 US/Eastern').tzoffset()
+    -14400
+    >>> DateTime('October 29, 2006 US/Eastern').tzoffset()
+    -14400
+    >>> DateTime('October 30, 2006 US/Eastern').tzoffset()
+    -18000
+
+Time Zones
+----------
+When pytz is available, DateTime can use its large database of time
+zones. Here are some examples:
+
+    >>> d = DateTime('Pacific/Kwajalein')
+    >>> d = DateTime('America/Shiprock')
+    >>> d = DateTime('Africa/Ouagadougou')
+
+Of course pytz doesn't know about everything.
+
+    >>> d = DateTime('July 21, 1969 Moon/Eastern')
+    Traceback (most recent call last):
+    ...
+    SyntaxError: July 21, 1969 Moon/Eastern
+
+You can still use zone names that DateTime defines that aren't part of
+the pytz database.
+
+    >>> d = DateTime('eet')
+    >>> d = DateTime('iceland')
+
+These time zones use DateTimes database. So it's preferable to use the
+official time zone name when you have pytz installed.
+
+One trickiness is that DateTime supports some zone name
+abbreviations. Some of these map to pytz names, so when pytz is
+present these abbreviations will give you time zone date from
+pytz. Notable among abbreviations that work this way are 'est', 'cst',
+'mst', and 'pst'.
+
+Let's verify that 'est' picks up the 2007 daylight savings time changes.
+
+    >>> DateTime('March 11, 2007 est').tzoffset()
+    -18000
+    >>> DateTime('March 12, 2007 est').tzoffset()
+    -14400
+    >>> DateTime('November 4, 2007 est').tzoffset()
+    -14400
+    >>> DateTime('November 5, 2007 est').tzoffset()
+    -18000
+
+You can get a list of time zones supported by calling the Timezones() function.
+
+    >>> Timezones() #doctest: +ELLIPSIS
+    ['Africa/Abidjan', 'Africa/Accra', 'Africa/Addis_Ababa', ...]
+
+Note that you can mess with this list without hurting things.
+
+    >>> t = Timezones()
+    >>> t.remove('US/Eastern')
+    >>> d = DateTime('US/Eastern')
+
+Python Versions
+---------------
+Both pytz and DateTime should work under Python 2.3 as well as Python 2.4.
+
+Internal Components
+-------------------
+The following are tests of internal components.
+
+Cache
+~~~~~
+When pytz is present, the DateTime class uses a different time zone cache.
+
+    >>> DateTime._tzinfo #doctest: +ELLIPSIS
+    <DateTime.pytz_support.PytzCache instance at ...>
+
+The cache maps time zone names to time zone instances.
+
+    >>> cache = DateTime._tzinfo
+    >>> tz = cache['GMT+730']   
+    >>> tz = cache['US/Mountain']
+
+The cache also must provide a few attributes for use by the DateTime
+class.
+
+The _zlst attribute is a list of supported time zone names.
+
+    >>> cache._zlst #doctest: +ELLIPSIS
+    ['Africa/Abidjan', 'Africa/Accra', ... 'NZT', 'NZST', 'IDLE']
+
+The _zidx attribute is a list of lower-case and possibly abbreviated
+time zone names that can be mapped to offical zone names.
+
+    >>> cache._zidx #doctest: +ELLIPSIS
+    ['australia/yancowinna', 'gmt+0500', ... 'europe/isle_of_man']
+
+Note that there are more items in _zidx than in _zlst since there are
+multiple names for some time zones.
+
+    >>> len(cache._zidx) > len(cache._zlst)
+    True
+
+Each entry in _zlst should also be present in _zidx in lower case form.
+
+    >>> for name in cache._zlst:
+    ...     if not name.lower() in cache._zidx:
+    ...         print "Error %s not in _zidx" % name.lower()
+
+The _zmap attribute maps the names in _zidx to official names in _zlst.
+
+    >>> cache._zmap['africa/abidjan']
+    'Africa/Abidjan'
+    >>> cache._zmap['gmt+1']
+    'GMT+1'
+    >>> cache._zmap['gmt+0100']
+    'GMT+1'
+    >>> cache._zmap['utc']
+    'UTC'
+
+Let's make sure that _zmap and _zidx agree.
+
+    >>> idx = list(cache._zidx)
+    >>> idx.sort()
+    >>> keys = cache._zmap.keys()
+    >>> keys.sort()
+    >>> idx == keys
+    True
+
+Timezone objects
+~~~~~~~~~~~~~~~~
+The timezone instances have only one public method info(). It returns
+a tuple of (offset, is_dst, name). The method takes a timestamp, which
+is used to determine dst information.
+
+    >>> t1 = DateTime('November 4, 00:00 2007 US/Mountain').timeTime()
+    >>> t2 = DateTime('November 4, 02:00 2007 US/Mountain').timeTime()
+    >>> tz.info(t1)
+    (-21600, 1, 'MDT')
+    >>> tz.info(t2)
+    (-25200, 0, 'MST')
+
+If you don't pass any arguments to info it provides daylight savings
+time information as of today.
+
+    >>> tz.info() in ((-21600, 1, 'MDT'), (-25200, 0, 'MST'))
+    True
+

Copied: Zope/trunk/lib/python/DateTime/pytz_support.py (from rev 80906, Zope/branches/amos-datetime-pytz/lib/python/DateTime/pytz_support.py)
===================================================================
--- Zope/trunk/lib/python/DateTime/pytz_support.py	                        (rev 0)
+++ Zope/trunk/lib/python/DateTime/pytz_support.py	2007-10-17 22:20:17 UTC (rev 80907)
@@ -0,0 +1,73 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+"""
+Pytz timezone support.
+"""
+
+import pytz
+import pytz.reference
+from datetime import datetime, timedelta
+import re
+
+class Timezone:
+    """
+    Timezone information returned by PytzCache.__getitem__
+    Adapts datetime.tzinfo object to DateTime._timezone interface
+    """
+    def __init__(self, tzinfo):
+        self.tzinfo = tzinfo
+        
+    def info(self, t=None):
+        if t is None:
+            dt = datetime.utcnow().replace(tzinfo=pytz.utc)
+        else:
+            dt = datetime.utcfromtimestamp(t).replace(tzinfo=pytz.utc)
+
+        # need to normalize tzinfo for the datetime to deal with
+        # daylight savings time.
+        normalized_dt = self.tzinfo.normalize(dt.astimezone(self.tzinfo))
+        normalized_tzinfo = normalized_dt.tzinfo
+        
+        offset = normalized_tzinfo.utcoffset(dt)
+        secs = offset.days * 24 * 60 * 60 + offset.seconds
+        dst = normalized_tzinfo.dst(dt)
+        if dst == timedelta(0):
+            is_dst = 0
+        else:
+            is_dst = 1
+        return secs, is_dst, normalized_tzinfo.tzname(dt)
+
+
+class PytzCache:
+    """
+    Wrapper for the DateTime._cache class that uses pytz for
+    timezone information where possible.
+    """
+    def __init__(self, cache):
+        self.cache = cache
+        self._zlst = list(pytz.common_timezones)
+        for name in cache._zlst:
+            if name not in self._zlst:
+                self._zlst.append(name)
+        self._zmap = dict(cache._zmap)
+        self._zmap.update(dict([(name.lower(), name)
+                            for name in pytz.common_timezones]))
+        self._zidx = self._zmap.keys()
+        
+    def __getitem__(self, key):
+        try:
+            name = self._zmap[key.lower()]
+            return Timezone(pytz.timezone(name))
+        except (KeyError, pytz.UnknownTimeZoneError):
+            return self.cache[key]
+    

Modified: Zope/trunk/lib/python/DateTime/tests/testDateTime.py
===================================================================
--- Zope/trunk/lib/python/DateTime/tests/testDateTime.py	2007-10-17 19:59:06 UTC (rev 80906)
+++ Zope/trunk/lib/python/DateTime/tests/testDateTime.py	2007-10-17 22:20:17 UTC (rev 80907)
@@ -463,10 +463,20 @@
 
 def test_suite():
     from zope.testing import doctest
-    return unittest.TestSuite([
-        unittest.makeSuite(DateTimeTests),
-        doctest.DocFileSuite('DateTime.txt', package='DateTime')
-        ])
+    try:
+        # if pytz is available include a test for it
+        import pytz
+        return unittest.TestSuite([
+            unittest.makeSuite(DateTimeTests),
+            doctest.DocFileSuite('DateTime.txt', package='DateTime'),
+            doctest.DocFileSuite('pytz.txt', package='DateTime'),
+            ])
 
+    except ImportError:
+        return unittest.TestSuite([
+            unittest.makeSuite(DateTimeTests),
+            doctest.DocFileSuite('DateTime.txt', package='DateTime')
+            ])
+
 if __name__=="__main__":
     unittest.main(defaultTest='test_suite')



More information about the Checkins mailing list