test_tzinfo.py

Go to the documentation of this file.
00001 # -*- coding: ascii -*-
00002 
00003 import sys, os, os.path
00004 import unittest, doctest
00005 import cPickle as pickle
00006 from datetime import datetime, tzinfo, timedelta
00007 
00008 if __name__ == '__main__':
00009     # Only munge path if invoked as a script. Testrunners should have setup
00010     # the paths already
00011     sys.path.insert(0, os.path.abspath(os.path.join(os.pardir, os.pardir)))
00012 
00013 import pytz
00014 from pytz import reference
00015 
00016 # I test for expected version to ensure the correct version of pytz is
00017 # actually being tested.
00018 EXPECTED_VERSION='2008c'
00019 
00020 fmt = '%Y-%m-%d %H:%M:%S %Z%z'
00021 
00022 NOTIME = timedelta(0)
00023 
00024 # GMT is a tzinfo.StaticTzInfo--the class we primarily want to test--while
00025 # UTC is reference implementation.  They both have the same timezone meaning.
00026 UTC = pytz.timezone('UTC')
00027 GMT = pytz.timezone('GMT')
00028 
00029 class BasicTest(unittest.TestCase):
00030 
00031     def testVersion(self):
00032         # Ensuring the correct version of pytz has been loaded
00033         self.failUnlessEqual(EXPECTED_VERSION, pytz.__version__,
00034                 'Incorrect pytz version loaded. Import path is stuffed '
00035                 'or this test needs updating. (Wanted %s, got %s)'
00036                 % (EXPECTED_VERSION, pytz.__version__)
00037                 )
00038 
00039     def testGMT(self):
00040         now = datetime.now(tz=GMT)
00041         self.failUnless(now.utcoffset() == NOTIME)
00042         self.failUnless(now.dst() == NOTIME)
00043         self.failUnless(now.timetuple() == now.utctimetuple())
00044         self.failUnless(now==now.replace(tzinfo=UTC))
00045 
00046     def testReferenceUTC(self):
00047         now = datetime.now(tz=UTC)
00048         self.failUnless(now.utcoffset() == NOTIME)
00049         self.failUnless(now.dst() == NOTIME)
00050         self.failUnless(now.timetuple() == now.utctimetuple())
00051 
00052 
00053 class PicklingTest(unittest.TestCase):
00054 
00055     def _roundtrip_tzinfo(self, tz):
00056         p = pickle.dumps(tz)
00057         unpickled_tz = pickle.loads(p)
00058         self.failUnless(tz is unpickled_tz, '%s did not roundtrip' % tz.zone)
00059 
00060     def _roundtrip_datetime(self, dt):
00061         # Ensure that the tzinfo attached to a datetime instance
00062         # is identical to the one returned. This is important for
00063         # DST timezones, as some state is stored in the tzinfo.
00064         tz = dt.tzinfo
00065         p = pickle.dumps(dt)
00066         unpickled_dt = pickle.loads(p)
00067         unpickled_tz = unpickled_dt.tzinfo
00068         self.failUnless(tz is unpickled_tz, '%s did not roundtrip' % tz.zone)
00069 
00070     def testDst(self):
00071         tz = pytz.timezone('Europe/Amsterdam')
00072         dt = datetime(2004, 2, 1, 0, 0, 0)
00073 
00074         for localized_tz in tz._tzinfos.values():
00075             self._roundtrip_tzinfo(localized_tz)
00076             self._roundtrip_datetime(dt.replace(tzinfo=localized_tz))
00077 
00078     def testRoundtrip(self):
00079         dt = datetime(2004, 2, 1, 0, 0, 0)
00080         for zone in pytz.all_timezones:
00081             tz = pytz.timezone(zone)
00082             self._roundtrip_tzinfo(tz)
00083 
00084     def testDatabaseFixes(self):
00085         # Hack the pickle to make it refer to a timezone abbreviation
00086         # that does not match anything. The unpickler should be able
00087         # to repair this case
00088         tz = pytz.timezone('Australia/Melbourne')
00089         p = pickle.dumps(tz)
00090         tzname = tz._tzname
00091         hacked_p = p.replace(tzname, '???')
00092         self.failIfEqual(p, hacked_p)
00093         unpickled_tz = pickle.loads(hacked_p)
00094         self.failUnless(tz is unpickled_tz)
00095 
00096         # Simulate a database correction. In this case, the incorrect
00097         # data will continue to be used.
00098         p = pickle.dumps(tz)
00099         new_utcoffset = tz._utcoffset.seconds + 42
00100         hacked_p = p.replace(str(tz._utcoffset.seconds), str(new_utcoffset))
00101         self.failIfEqual(p, hacked_p)
00102         unpickled_tz = pickle.loads(hacked_p)
00103         self.failUnlessEqual(unpickled_tz._utcoffset.seconds, new_utcoffset)
00104         self.failUnless(tz is not unpickled_tz)
00105 
00106     def testOldPickles(self):
00107         # Ensure that applications serializing pytz instances as pickles
00108         # have no troubles upgrading to a new pytz release. These pickles
00109         # where created with pytz2006j
00110         east1 = pickle.loads(
00111                 "cpytz\n_p\np1\n(S'US/Eastern'\np2\nI-18000\n"
00112                 "I0\nS'EST'\np3\ntRp4\n."
00113                 )
00114         east2 = pytz.timezone('US/Eastern')
00115         self.failUnless(east1 is east2)
00116 
00117         # Confirm changes in name munging between 2006j and 2007c cause
00118         # no problems.
00119         pap1 = pickle.loads(
00120                 "cpytz\n_p\np1\n(S'America/Port_minus_au_minus_Prince'"
00121                 "\np2\nI-17340\nI0\nS'PPMT'\np3\ntRp4\n."
00122                 )
00123         pap2 = pytz.timezone('America/Port-au-Prince')
00124         self.failUnless(pap1 is pap2)
00125 
00126         gmt1 = pickle.loads("cpytz\n_p\np1\n(S'Etc/GMT_plus_10'\np2\ntRp3\n.")
00127         gmt2 = pytz.timezone('Etc/GMT+10')
00128         self.failUnless(gmt1 is gmt2)
00129 
00130 
00131 class USEasternDSTStartTestCase(unittest.TestCase):
00132     tzinfo = pytz.timezone('US/Eastern')
00133 
00134     # 24 hours before DST changeover
00135     transition_time = datetime(2002, 4, 7, 7, 0, 0, tzinfo=UTC)
00136 
00137     # Increase for 'flexible' DST transitions due to 1 minute granularity
00138     # of Python's datetime library
00139     instant = timedelta(seconds=1)
00140 
00141     # before transition
00142     before = {
00143         'tzname': 'EST',
00144         'utcoffset': timedelta(hours = -5),
00145         'dst': timedelta(hours = 0),
00146         }
00147 
00148     # after transition
00149     after = {
00150         'tzname': 'EDT',
00151         'utcoffset': timedelta(hours = -4),
00152         'dst': timedelta(hours = 1),
00153         }
00154 
00155     def _test_tzname(self, utc_dt, wanted):
00156         tzname = wanted['tzname']
00157         dt = utc_dt.astimezone(self.tzinfo)
00158         self.failUnlessEqual(dt.tzname(), tzname,
00159             'Expected %s as tzname for %s. Got %s' % (
00160                 tzname, str(utc_dt), dt.tzname()
00161                 )
00162             )
00163 
00164     def _test_utcoffset(self, utc_dt, wanted):
00165         utcoffset = wanted['utcoffset']
00166         dt = utc_dt.astimezone(self.tzinfo)
00167         self.failUnlessEqual(
00168                 dt.utcoffset(), wanted['utcoffset'],
00169                 'Expected %s as utcoffset for %s. Got %s' % (
00170                     utcoffset, utc_dt, dt.utcoffset()
00171                     )
00172                 )
00173 
00174     def _test_dst(self, utc_dt, wanted):
00175         dst = wanted['dst']
00176         dt = utc_dt.astimezone(self.tzinfo)
00177         self.failUnlessEqual(dt.dst(),dst,
00178             'Expected %s as dst for %s. Got %s' % (
00179                 dst, utc_dt, dt.dst()
00180                 )
00181             )
00182 
00183     def test_arithmetic(self):
00184         utc_dt = self.transition_time
00185 
00186         for days in range(-420, 720, 20):
00187             delta = timedelta(days=days)
00188 
00189             # Make sure we can get back where we started
00190             dt = utc_dt.astimezone(self.tzinfo)
00191             dt2 = dt + delta
00192             dt2 = dt2 - delta
00193             self.failUnlessEqual(dt, dt2)
00194 
00195             # Make sure arithmetic crossing DST boundaries ends
00196             # up in the correct timezone after normalization
00197             self.failUnlessEqual(
00198                     (utc_dt + delta).astimezone(self.tzinfo).strftime(fmt),
00199                     self.tzinfo.normalize(dt + delta).strftime(fmt),
00200                     'Incorrect result for delta==%d days.  Wanted %r. Got %r'%(
00201                         days,
00202                         (utc_dt + delta).astimezone(self.tzinfo).strftime(fmt),
00203                         self.tzinfo.normalize(dt + delta).strftime(fmt),
00204                         )
00205                     )
00206 
00207     def _test_all(self, utc_dt, wanted):
00208         self._test_utcoffset(utc_dt, wanted)
00209         self._test_tzname(utc_dt, wanted)
00210         self._test_dst(utc_dt, wanted)
00211 
00212     def testDayBefore(self):
00213         self._test_all(
00214                 self.transition_time - timedelta(days=1), self.before
00215                 )
00216 
00217     def testTwoHoursBefore(self):
00218         self._test_all(
00219                 self.transition_time - timedelta(hours=2), self.before
00220                 )
00221 
00222     def testHourBefore(self):
00223         self._test_all(
00224                 self.transition_time - timedelta(hours=1), self.before
00225                 )
00226 
00227     def testInstantBefore(self):
00228         self._test_all(
00229                 self.transition_time - self.instant, self.before
00230                 )
00231 
00232     def testTransition(self):
00233         self._test_all(
00234                 self.transition_time, self.after
00235                 )
00236 
00237     def testInstantAfter(self):
00238         self._test_all(
00239                 self.transition_time + self.instant, self.after
00240                 )
00241 
00242     def testHourAfter(self):
00243         self._test_all(
00244                 self.transition_time + timedelta(hours=1), self.after
00245                 )
00246 
00247     def testTwoHoursAfter(self):
00248         self._test_all(
00249                 self.transition_time + timedelta(hours=1), self.after
00250                 )
00251 
00252     def testDayAfter(self):
00253         self._test_all(
00254                 self.transition_time + timedelta(days=1), self.after
00255                 )
00256 
00257 
00258 class USEasternDSTEndTestCase(USEasternDSTStartTestCase):
00259     tzinfo = pytz.timezone('US/Eastern')
00260     transition_time = datetime(2002, 10, 27, 6, 0, 0, tzinfo=UTC)
00261     before = {
00262         'tzname': 'EDT',
00263         'utcoffset': timedelta(hours = -4),
00264         'dst': timedelta(hours = 1),
00265         }
00266     after = {
00267         'tzname': 'EST',
00268         'utcoffset': timedelta(hours = -5),
00269         'dst': timedelta(hours = 0),
00270         }
00271 
00272 
00273 class USEasternEPTStartTestCase(USEasternDSTStartTestCase):
00274     transition_time = datetime(1945, 8, 14, 23, 0, 0, tzinfo=UTC)
00275     before = {
00276         'tzname': 'EWT',
00277         'utcoffset': timedelta(hours = -4),
00278         'dst': timedelta(hours = 1),
00279         }
00280     after = {
00281         'tzname': 'EPT',
00282         'utcoffset': timedelta(hours = -4),
00283         'dst': timedelta(hours = 1),
00284         }
00285 
00286 
00287 class USEasternEPTEndTestCase(USEasternDSTStartTestCase):
00288     transition_time = datetime(1945, 9, 30, 6, 0, 0, tzinfo=UTC)
00289     before = {
00290         'tzname': 'EPT',
00291         'utcoffset': timedelta(hours = -4),
00292         'dst': timedelta(hours = 1),
00293         }
00294     after = {
00295         'tzname': 'EST',
00296         'utcoffset': timedelta(hours = -5),
00297         'dst': timedelta(hours = 0),
00298         }
00299 
00300 
00301 class WarsawWMTEndTestCase(USEasternDSTStartTestCase):
00302     # In 1915, Warsaw changed from Warsaw to Central European time.
00303     # This involved the clocks being set backwards, causing a end-of-DST
00304     # like situation without DST being involved.
00305     tzinfo = pytz.timezone('Europe/Warsaw')
00306     transition_time = datetime(1915, 8, 4, 22, 36, 0, tzinfo=UTC)
00307     before = {
00308         'tzname': 'WMT',
00309         'utcoffset': timedelta(hours=1, minutes=24),
00310         'dst': timedelta(0),
00311         }
00312     after = {
00313         'tzname': 'CET',
00314         'utcoffset': timedelta(hours=1),
00315         'dst': timedelta(0),
00316         }
00317 
00318 
00319 class VilniusWMTEndTestCase(USEasternDSTStartTestCase):
00320     # At the end of 1916, Vilnius changed timezones putting its clock
00321     # forward by 11 minutes 35 seconds. Neither timezone was in DST mode.
00322     tzinfo = pytz.timezone('Europe/Vilnius')
00323     instant = timedelta(seconds=31)
00324     transition_time = datetime(1916, 12, 31, 22, 36, 00, tzinfo=UTC)
00325     before = {
00326         'tzname': 'WMT',
00327         'utcoffset': timedelta(hours=1, minutes=24),
00328         'dst': timedelta(0),
00329         }
00330     after = {
00331         'tzname': 'KMT',
00332         'utcoffset': timedelta(hours=1, minutes=36), # Really 1:35:36
00333         'dst': timedelta(0),
00334         }
00335 
00336 
00337 class ReferenceUSEasternDSTStartTestCase(USEasternDSTStartTestCase):
00338     tzinfo = reference.Eastern
00339     def test_arithmetic(self):
00340         # Reference implementation cannot handle this
00341         pass
00342 
00343 
00344 class ReferenceUSEasternDSTEndTestCase(USEasternDSTEndTestCase):
00345     tzinfo = reference.Eastern
00346 
00347     def testHourBefore(self):
00348         # Python's datetime library has a bug, where the hour before
00349         # a daylight savings transition is one hour out. For example,
00350         # at the end of US/Eastern daylight savings time, 01:00 EST
00351         # occurs twice (once at 05:00 UTC and once at 06:00 UTC),
00352         # whereas the first should actually be 01:00 EDT.
00353         # Note that this bug is by design - by accepting this ambiguity
00354         # for one hour one hour per year, an is_dst flag on datetime.time
00355         # became unnecessary.
00356         self._test_all(
00357                 self.transition_time - timedelta(hours=1), self.after
00358                 )
00359 
00360     def testInstantBefore(self):
00361         self._test_all(
00362                 self.transition_time - timedelta(seconds=1), self.after
00363                 )
00364 
00365     def test_arithmetic(self):
00366         # Reference implementation cannot handle this
00367         pass
00368 
00369 
00370 class LocalTestCase(unittest.TestCase):
00371     def testLocalize(self):
00372         loc_tz = pytz.timezone('Europe/Amsterdam')
00373 
00374         loc_time = loc_tz.localize(datetime(1930, 5, 10, 0, 0, 0))
00375         # Actually +00:19:32, but Python datetime rounds this
00376         self.failUnlessEqual(loc_time.strftime('%Z%z'), 'AMT+0020')
00377 
00378         loc_time = loc_tz.localize(datetime(1930, 5, 20, 0, 0, 0))
00379         # Actually +00:19:32, but Python datetime rounds this
00380         self.failUnlessEqual(loc_time.strftime('%Z%z'), 'NST+0120')
00381 
00382         loc_time = loc_tz.localize(datetime(1940, 5, 10, 0, 0, 0))
00383         self.failUnlessEqual(loc_time.strftime('%Z%z'), 'NET+0020')
00384 
00385         loc_time = loc_tz.localize(datetime(1940, 5, 20, 0, 0, 0))
00386         self.failUnlessEqual(loc_time.strftime('%Z%z'), 'CEST+0200')
00387 
00388         loc_time = loc_tz.localize(datetime(2004, 2, 1, 0, 0, 0))
00389         self.failUnlessEqual(loc_time.strftime('%Z%z'), 'CET+0100')
00390 
00391         loc_time = loc_tz.localize(datetime(2004, 4, 1, 0, 0, 0))
00392         self.failUnlessEqual(loc_time.strftime('%Z%z'), 'CEST+0200')
00393 
00394         tz = pytz.timezone('Europe/Amsterdam')
00395         loc_time = loc_tz.localize(datetime(1943, 3, 29, 1, 59, 59))
00396         self.failUnlessEqual(loc_time.strftime('%Z%z'), 'CET+0100')
00397 
00398 
00399         # Switch to US
00400         loc_tz = pytz.timezone('US/Eastern')
00401 
00402         # End of DST ambiguity check
00403         loc_time = loc_tz.localize(datetime(1918, 10, 27, 1, 59, 59), is_dst=1)
00404         self.failUnlessEqual(loc_time.strftime('%Z%z'), 'EDT-0400')
00405 
00406         loc_time = loc_tz.localize(datetime(1918, 10, 27, 1, 59, 59), is_dst=0)
00407         self.failUnlessEqual(loc_time.strftime('%Z%z'), 'EST-0500')
00408 
00409         self.failUnlessRaises(pytz.AmbiguousTimeError,
00410                 loc_tz.localize, datetime(1918, 10, 27, 1, 59, 59), is_dst=None
00411                 )
00412 
00413         # Weird changes - war time and peace time both is_dst==True
00414 
00415         loc_time = loc_tz.localize(datetime(1942, 2, 9, 3, 0, 0))
00416         self.failUnlessEqual(loc_time.strftime('%Z%z'), 'EWT-0400')
00417 
00418         loc_time = loc_tz.localize(datetime(1945, 8, 14, 19, 0, 0))
00419         self.failUnlessEqual(loc_time.strftime('%Z%z'), 'EPT-0400')
00420 
00421         loc_time = loc_tz.localize(datetime(1945, 9, 30, 1, 0, 0), is_dst=1)
00422         self.failUnlessEqual(loc_time.strftime('%Z%z'), 'EPT-0400')
00423 
00424         loc_time = loc_tz.localize(datetime(1945, 9, 30, 1, 0, 0), is_dst=0)
00425         self.failUnlessEqual(loc_time.strftime('%Z%z'), 'EST-0500')
00426 
00427     def testNormalize(self):
00428         tz = pytz.timezone('US/Eastern')
00429         dt = datetime(2004, 4, 4, 7, 0, 0, tzinfo=UTC).astimezone(tz)
00430         dt2 = dt - timedelta(minutes=10)
00431         self.failUnlessEqual(
00432                 dt2.strftime('%Y-%m-%d %H:%M:%S %Z%z'),
00433                 '2004-04-04 02:50:00 EDT-0400'
00434                 )
00435 
00436         dt2 = tz.normalize(dt2)
00437         self.failUnlessEqual(
00438                 dt2.strftime('%Y-%m-%d %H:%M:%S %Z%z'),
00439                 '2004-04-04 01:50:00 EST-0500'
00440                 )
00441 
00442     def testPartialMinuteOffsets(self):
00443         # utcoffset in Amsterdam was not a whole minute until 1937
00444         # However, we fudge this by rounding them, as the Python
00445         # datetime library 
00446         tz = pytz.timezone('Europe/Amsterdam')
00447         utc_dt = datetime(1914, 1, 1, 13, 40, 28, tzinfo=UTC) # correct
00448         utc_dt = utc_dt.replace(second=0) # But we need to fudge it
00449         loc_dt = utc_dt.astimezone(tz)
00450         self.failUnlessEqual(
00451                 loc_dt.strftime('%Y-%m-%d %H:%M:%S %Z%z'),
00452                 '1914-01-01 14:00:00 AMT+0020'
00453                 )
00454 
00455         # And get back...
00456         utc_dt = loc_dt.astimezone(UTC)
00457         self.failUnlessEqual(
00458                 utc_dt.strftime('%Y-%m-%d %H:%M:%S %Z%z'),
00459                 '1914-01-01 13:40:00 UTC+0000'
00460                 )
00461 
00462     def no_testCreateLocaltime(self):
00463         # It would be nice if this worked, but it doesn't.
00464         tz = pytz.timezone('Europe/Amsterdam')
00465         dt = datetime(2004, 10, 31, 2, 0, 0, tzinfo=tz)
00466         self.failUnlessEqual(
00467                 dt.strftime(fmt),
00468                 '2004-10-31 02:00:00 CET+0100'
00469                 )
00470 
00471 def test_suite():
00472     suite = unittest.TestSuite()
00473     suite.addTest(doctest.DocTestSuite('pytz'))
00474     suite.addTest(doctest.DocTestSuite('pytz.tzinfo'))
00475     import test_tzinfo
00476     suite.addTest(unittest.defaultTestLoader.loadTestsFromModule(test_tzinfo))
00477     return suite
00478 
00479 if __name__ == '__main__':
00480     unittest.main(defaultTest='test_suite')
00481 
00482 
00483 

© Copyright 2008-2009 Vyper Logix Corp., All Right Reserved; If you reference this document or any part of this document you must use the citation verbatim (including the link) "© Copyright 2008-2009 Vyper Logix Corp., All Right Reserved."

Notice: This source code contained in this document is NOT open source and is NOT being distributed as open source.

122,241 lines of code and growing...