[Zope-Checkins] SVN: Zope/branches/amos-datetime-pytz/lib/python/DateTime/ Added pytz support to DateTime. If pytz is present then its time zone

Amos Latteier amos at latteier.com
Thu Oct 4 12:31:30 EDT 2007


Log message for revision 80620:
  Added pytz support to DateTime. If pytz is present then its time zone 
  data is used in preference of DateTime's data. However, all time zones 
  that DateTime defines are still supported.
  

Changed:
  U   Zope/branches/amos-datetime-pytz/lib/python/DateTime/DateTime.py
  U   Zope/branches/amos-datetime-pytz/lib/python/DateTime/DateTime.txt
  A   Zope/branches/amos-datetime-pytz/lib/python/DateTime/pytz.txt
  A   Zope/branches/amos-datetime-pytz/lib/python/DateTime/pytz_support.py
  U   Zope/branches/amos-datetime-pytz/lib/python/DateTime/tests/testDateTime.py

-=-
Modified: Zope/branches/amos-datetime-pytz/lib/python/DateTime/DateTime.py
===================================================================
--- Zope/branches/amos-datetime-pytz/lib/python/DateTime/DateTime.py	2007-10-04 15:17:58 UTC (rev 80619)
+++ Zope/branches/amos-datetime-pytz/lib/python/DateTime/DateTime.py	2007-10-04 16:31:30 UTC (rev 80620)
@@ -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/branches/amos-datetime-pytz/lib/python/DateTime/DateTime.txt
===================================================================
--- Zope/branches/amos-datetime-pytz/lib/python/DateTime/DateTime.txt	2007-10-04 15:17:58 UTC (rev 80619)
+++ Zope/branches/amos-datetime-pytz/lib/python/DateTime/DateTime.txt	2007-10-04 16:31:30 UTC (rev 80620)
@@ -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

Added: Zope/branches/amos-datetime-pytz/lib/python/DateTime/pytz.txt
===================================================================
--- Zope/branches/amos-datetime-pytz/lib/python/DateTime/pytz.txt	                        (rev 0)
+++ Zope/branches/amos-datetime-pytz/lib/python/DateTime/pytz.txt	2007-10-04 16:31:30 UTC (rev 80620)
@@ -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
+

Added: Zope/branches/amos-datetime-pytz/lib/python/DateTime/pytz_support.py
===================================================================
--- Zope/branches/amos-datetime-pytz/lib/python/DateTime/pytz_support.py	                        (rev 0)
+++ Zope/branches/amos-datetime-pytz/lib/python/DateTime/pytz_support.py	2007-10-04 16:31:30 UTC (rev 80620)
@@ -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/branches/amos-datetime-pytz/lib/python/DateTime/tests/testDateTime.py
===================================================================
--- Zope/branches/amos-datetime-pytz/lib/python/DateTime/tests/testDateTime.py	2007-10-04 15:17:58 UTC (rev 80619)
+++ Zope/branches/amos-datetime-pytz/lib/python/DateTime/tests/testDateTime.py	2007-10-04 16:31:30 UTC (rev 80620)
@@ -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 Zope-Checkins mailing list