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...