blob: 7d503e019e4a9c2b5e243c6f0c2ba0a91c6f0f3f [file] [log] [blame]
Tim Peters0bf60bd2003-01-08 20:40:01 +00001"""Test date/time type.
2
3See http://www.zope.org/Members/fdrake/DateTimeWiki/TestCases
4"""
Tim Peters2a799bf2002-12-16 20:18:38 +00005
6import sys
7import unittest
8
9from test import test_support
10
11from datetime import MINYEAR, MAXYEAR
12from datetime import timedelta
13from datetime import tzinfo
Tim Peters0bf60bd2003-01-08 20:40:01 +000014from datetime import time
15from datetime import date, datetime
16
17
18# XXX The test suite uncovered a bug in Python 2.2.2: if x and y are
19# XXX instances of new-style classes (like date and time) that both
20# XXX define __cmp__, and x is compared to y, and one of the __cmp__
21# XXX implementations raises an exception, the exception can get dropped
22# XXX on the floor when it occurs, and pop up again at some "random" time
23# XXX later (it depends on when the next opcode gets executed that
24# XXX bothers to check). There isn't a workaround for this, so instead
25# XXX we disable the parts of the tests that trigger it unless
26# XXX CMP_BUG_FIXED is true. The bug is still there, we simply avoid
27# XXX provoking it here.
28# XXX Guido checked into a fix that will go into 2.2.3. The bug was
29# XXX already fixed in 2.3 CVS via a different means.
30CMP_BUG_FIXED = sys.version_info >= (2, 2, 3)
31
Tim Peters2a799bf2002-12-16 20:18:38 +000032
33#############################################################################
34# module tests
35
36class TestModule(unittest.TestCase):
37
38 def test_constants(self):
39 import datetime
40 self.assertEqual(datetime.MINYEAR, 1)
41 self.assertEqual(datetime.MAXYEAR, 9999)
42
43#############################################################################
44# tzinfo tests
45
46class FixedOffset(tzinfo):
47 def __init__(self, offset, name, dstoffset=42):
Tim Peters397301e2003-01-02 21:28:08 +000048 if isinstance(offset, int):
49 offset = timedelta(minutes=offset)
50 if isinstance(dstoffset, int):
51 dstoffset = timedelta(minutes=dstoffset)
Tim Peters2a799bf2002-12-16 20:18:38 +000052 self.__offset = offset
53 self.__name = name
54 self.__dstoffset = dstoffset
55 def __repr__(self):
56 return self.__name.lower()
57 def utcoffset(self, dt):
58 return self.__offset
59 def tzname(self, dt):
60 return self.__name
61 def dst(self, dt):
62 return self.__dstoffset
63
Tim Petersfb8472c2002-12-21 05:04:42 +000064class PicklableFixedOffset(FixedOffset):
65 def __init__(self, offset=None, name=None, dstoffset=None):
66 FixedOffset.__init__(self, offset, name, dstoffset)
67
Tim Peters2a799bf2002-12-16 20:18:38 +000068class TestTZInfo(unittest.TestCase):
69
70 def test_non_abstractness(self):
71 # In order to allow subclasses to get pickled, the C implementation
72 # wasn't able to get away with having __init__ raise
73 # NotImplementedError.
74 useless = tzinfo()
75 dt = datetime.max
76 self.assertRaises(NotImplementedError, useless.tzname, dt)
77 self.assertRaises(NotImplementedError, useless.utcoffset, dt)
78 self.assertRaises(NotImplementedError, useless.dst, dt)
79
80 def test_subclass_must_override(self):
81 class NotEnough(tzinfo):
82 def __init__(self, offset, name):
83 self.__offset = offset
84 self.__name = name
85 self.failUnless(issubclass(NotEnough, tzinfo))
86 ne = NotEnough(3, "NotByALongShot")
87 self.failUnless(isinstance(ne, tzinfo))
88
89 dt = datetime.now()
90 self.assertRaises(NotImplementedError, ne.tzname, dt)
91 self.assertRaises(NotImplementedError, ne.utcoffset, dt)
92 self.assertRaises(NotImplementedError, ne.dst, dt)
93
94 def test_normal(self):
95 fo = FixedOffset(3, "Three")
96 self.failUnless(isinstance(fo, tzinfo))
97 for dt in datetime.now(), None:
Tim Peters397301e2003-01-02 21:28:08 +000098 self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +000099 self.assertEqual(fo.tzname(dt), "Three")
Tim Peters397301e2003-01-02 21:28:08 +0000100 self.assertEqual(fo.dst(dt), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +0000101
102 def test_pickling_base(self):
103 import pickle, cPickle
104
105 # There's no point to pickling tzinfo objects on their own (they
106 # carry no data), but they need to be picklable anyway else
107 # concrete subclasses can't be pickled.
108 orig = tzinfo.__new__(tzinfo)
109 self.failUnless(type(orig) is tzinfo)
110 for pickler in pickle, cPickle:
111 for binary in 0, 1:
112 green = pickler.dumps(orig, binary)
113 derived = pickler.loads(green)
114 self.failUnless(type(derived) is tzinfo)
115
116 def test_pickling_subclass(self):
117 import pickle, cPickle
118
119 # Make sure we can pickle/unpickle an instance of a subclass.
Tim Peters397301e2003-01-02 21:28:08 +0000120 offset = timedelta(minutes=-300)
121 orig = PicklableFixedOffset(offset, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +0000122 self.failUnless(isinstance(orig, tzinfo))
Tim Petersfb8472c2002-12-21 05:04:42 +0000123 self.failUnless(type(orig) is PicklableFixedOffset)
Tim Peters397301e2003-01-02 21:28:08 +0000124 self.assertEqual(orig.utcoffset(None), offset)
Tim Peters2a799bf2002-12-16 20:18:38 +0000125 self.assertEqual(orig.tzname(None), 'cookie')
126 for pickler in pickle, cPickle:
127 for binary in 0, 1:
128 green = pickler.dumps(orig, binary)
129 derived = pickler.loads(green)
130 self.failUnless(isinstance(derived, tzinfo))
Tim Petersfb8472c2002-12-21 05:04:42 +0000131 self.failUnless(type(derived) is PicklableFixedOffset)
Tim Peters397301e2003-01-02 21:28:08 +0000132 self.assertEqual(derived.utcoffset(None), offset)
Tim Peters2a799bf2002-12-16 20:18:38 +0000133 self.assertEqual(derived.tzname(None), 'cookie')
134
135#############################################################################
136# timedelta tests
137
138class TestTimeDelta(unittest.TestCase):
139
140 def test_constructor(self):
141 eq = self.assertEqual
142 td = timedelta
143
144 # Check keyword args to constructor
145 eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0,
146 milliseconds=0, microseconds=0))
147 eq(td(1), td(days=1))
148 eq(td(0, 1), td(seconds=1))
149 eq(td(0, 0, 1), td(microseconds=1))
150 eq(td(weeks=1), td(days=7))
151 eq(td(days=1), td(hours=24))
152 eq(td(hours=1), td(minutes=60))
153 eq(td(minutes=1), td(seconds=60))
154 eq(td(seconds=1), td(milliseconds=1000))
155 eq(td(milliseconds=1), td(microseconds=1000))
156
157 # Check float args to constructor
158 eq(td(weeks=1.0/7), td(days=1))
159 eq(td(days=1.0/24), td(hours=1))
160 eq(td(hours=1.0/60), td(minutes=1))
161 eq(td(minutes=1.0/60), td(seconds=1))
162 eq(td(seconds=0.001), td(milliseconds=1))
163 eq(td(milliseconds=0.001), td(microseconds=1))
164
165 def test_computations(self):
166 eq = self.assertEqual
167 td = timedelta
168
169 a = td(7) # One week
170 b = td(0, 60) # One minute
171 c = td(0, 0, 1000) # One millisecond
172 eq(a+b+c, td(7, 60, 1000))
173 eq(a-b, td(6, 24*3600 - 60))
174 eq(-a, td(-7))
175 eq(+a, td(7))
176 eq(-b, td(-1, 24*3600 - 60))
177 eq(-c, td(-1, 24*3600 - 1, 999000))
178 eq(abs(a), a)
179 eq(abs(-a), a)
180 eq(td(6, 24*3600), a)
181 eq(td(0, 0, 60*1000000), b)
182 eq(a*10, td(70))
183 eq(a*10, 10*a)
184 eq(a*10L, 10*a)
185 eq(b*10, td(0, 600))
186 eq(10*b, td(0, 600))
187 eq(b*10L, td(0, 600))
188 eq(c*10, td(0, 0, 10000))
189 eq(10*c, td(0, 0, 10000))
190 eq(c*10L, td(0, 0, 10000))
191 eq(a*-1, -a)
192 eq(b*-2, -b-b)
193 eq(c*-2, -c+-c)
194 eq(b*(60*24), (b*60)*24)
195 eq(b*(60*24), (60*b)*24)
196 eq(c*1000, td(0, 1))
197 eq(1000*c, td(0, 1))
198 eq(a//7, td(1))
199 eq(b//10, td(0, 6))
200 eq(c//1000, td(0, 0, 1))
201 eq(a//10, td(0, 7*24*360))
202 eq(a//3600000, td(0, 0, 7*24*1000))
203
204 def test_disallowed_computations(self):
205 a = timedelta(42)
206
207 # Add/sub ints, longs, floats should be illegal
208 for i in 1, 1L, 1.0:
209 self.assertRaises(TypeError, lambda: a+i)
210 self.assertRaises(TypeError, lambda: a-i)
211 self.assertRaises(TypeError, lambda: i+a)
212 self.assertRaises(TypeError, lambda: i-a)
213
214 # Mul/div by float isn't supported.
215 x = 2.3
216 self.assertRaises(TypeError, lambda: a*x)
217 self.assertRaises(TypeError, lambda: x*a)
218 self.assertRaises(TypeError, lambda: a/x)
219 self.assertRaises(TypeError, lambda: x/a)
220 self.assertRaises(TypeError, lambda: a // x)
221 self.assertRaises(TypeError, lambda: x // a)
222
223 # Divison of int by timedelta doesn't make sense.
224 # Division by zero doesn't make sense.
225 for zero in 0, 0L:
226 self.assertRaises(TypeError, lambda: zero // a)
227 self.assertRaises(ZeroDivisionError, lambda: a // zero)
228
229 def test_basic_attributes(self):
230 days, seconds, us = 1, 7, 31
231 td = timedelta(days, seconds, us)
232 self.assertEqual(td.days, days)
233 self.assertEqual(td.seconds, seconds)
234 self.assertEqual(td.microseconds, us)
235
236 def test_carries(self):
237 t1 = timedelta(days=100,
238 weeks=-7,
239 hours=-24*(100-49),
240 minutes=-3,
241 seconds=12,
242 microseconds=(3*60 - 12) * 1e6 + 1)
243 t2 = timedelta(microseconds=1)
244 self.assertEqual(t1, t2)
245
246 def test_hash_equality(self):
247 t1 = timedelta(days=100,
248 weeks=-7,
249 hours=-24*(100-49),
250 minutes=-3,
251 seconds=12,
252 microseconds=(3*60 - 12) * 1000000)
253 t2 = timedelta()
254 self.assertEqual(hash(t1), hash(t2))
255
256 t1 += timedelta(weeks=7)
257 t2 += timedelta(days=7*7)
258 self.assertEqual(t1, t2)
259 self.assertEqual(hash(t1), hash(t2))
260
261 d = {t1: 1}
262 d[t2] = 2
263 self.assertEqual(len(d), 1)
264 self.assertEqual(d[t1], 2)
265
266 def test_pickling(self):
267 import pickle, cPickle
268 args = 12, 34, 56
269 orig = timedelta(*args)
270 state = orig.__getstate__()
271 self.assertEqual(args, state)
272 derived = timedelta()
273 derived.__setstate__(state)
274 self.assertEqual(orig, derived)
275 for pickler in pickle, cPickle:
276 for binary in 0, 1:
277 green = pickler.dumps(orig, binary)
278 derived = pickler.loads(green)
279 self.assertEqual(orig, derived)
280
281 def test_compare(self):
282 t1 = timedelta(2, 3, 4)
283 t2 = timedelta(2, 3, 4)
284 self.failUnless(t1 == t2)
285 self.failUnless(t1 <= t2)
286 self.failUnless(t1 >= t2)
287 self.failUnless(not t1 != t2)
288 self.failUnless(not t1 < t2)
289 self.failUnless(not t1 > t2)
290 self.assertEqual(cmp(t1, t2), 0)
291 self.assertEqual(cmp(t2, t1), 0)
292
293 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
294 t2 = timedelta(*args) # this is larger than t1
295 self.failUnless(t1 < t2)
296 self.failUnless(t2 > t1)
297 self.failUnless(t1 <= t2)
298 self.failUnless(t2 >= t1)
299 self.failUnless(t1 != t2)
300 self.failUnless(t2 != t1)
301 self.failUnless(not t1 == t2)
302 self.failUnless(not t2 == t1)
303 self.failUnless(not t1 > t2)
304 self.failUnless(not t2 < t1)
305 self.failUnless(not t1 >= t2)
306 self.failUnless(not t2 <= t1)
307 self.assertEqual(cmp(t1, t2), -1)
308 self.assertEqual(cmp(t2, t1), 1)
309
310 for badarg in 10, 10L, 34.5, "abc", {}, [], ():
311 self.assertRaises(TypeError, lambda: t1 == badarg)
312 self.assertRaises(TypeError, lambda: t1 != badarg)
313 self.assertRaises(TypeError, lambda: t1 <= badarg)
314 self.assertRaises(TypeError, lambda: t1 < badarg)
315 self.assertRaises(TypeError, lambda: t1 > badarg)
316 self.assertRaises(TypeError, lambda: t1 >= badarg)
317 self.assertRaises(TypeError, lambda: badarg == t1)
318 self.assertRaises(TypeError, lambda: badarg != t1)
319 self.assertRaises(TypeError, lambda: badarg <= t1)
320 self.assertRaises(TypeError, lambda: badarg < t1)
321 self.assertRaises(TypeError, lambda: badarg > t1)
322 self.assertRaises(TypeError, lambda: badarg >= t1)
323
324 def test_str(self):
325 td = timedelta
326 eq = self.assertEqual
327
328 eq(str(td(1)), "1 day, 0:00:00")
329 eq(str(td(-1)), "-1 day, 0:00:00")
330 eq(str(td(2)), "2 days, 0:00:00")
331 eq(str(td(-2)), "-2 days, 0:00:00")
332
333 eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
334 eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
335 eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
336 "-210 days, 23:12:34")
337
338 eq(str(td(milliseconds=1)), "0:00:00.001000")
339 eq(str(td(microseconds=3)), "0:00:00.000003")
340
341 eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
342 microseconds=999999)),
343 "999999999 days, 23:59:59.999999")
344
345 def test_roundtrip(self):
346 for td in (timedelta(days=999999999, hours=23, minutes=59,
347 seconds=59, microseconds=999999),
348 timedelta(days=-999999999),
349 timedelta(days=1, seconds=2, microseconds=3)):
350
351 # Verify td -> string -> td identity.
352 s = repr(td)
353 self.failUnless(s.startswith('datetime.'))
354 s = s[9:]
355 td2 = eval(s)
356 self.assertEqual(td, td2)
357
358 # Verify identity via reconstructing from pieces.
359 td2 = timedelta(td.days, td.seconds, td.microseconds)
360 self.assertEqual(td, td2)
361
362 def test_resolution_info(self):
363 self.assert_(isinstance(timedelta.min, timedelta))
364 self.assert_(isinstance(timedelta.max, timedelta))
365 self.assert_(isinstance(timedelta.resolution, timedelta))
366 self.assert_(timedelta.max > timedelta.min)
367 self.assertEqual(timedelta.min, timedelta(-999999999))
368 self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
369 self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
370
371 def test_overflow(self):
372 tiny = timedelta.resolution
373
374 td = timedelta.min + tiny
375 td -= tiny # no problem
376 self.assertRaises(OverflowError, td.__sub__, tiny)
377 self.assertRaises(OverflowError, td.__add__, -tiny)
378
379 td = timedelta.max - tiny
380 td += tiny # no problem
381 self.assertRaises(OverflowError, td.__add__, tiny)
382 self.assertRaises(OverflowError, td.__sub__, -tiny)
383
384 self.assertRaises(OverflowError, lambda: -timedelta.max)
385
386 def test_microsecond_rounding(self):
387 td = timedelta
388 eq = self.assertEqual
389
390 # Single-field rounding.
391 eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
392 eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
393 eq(td(milliseconds=0.6/1000), td(microseconds=1))
394 eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
395
396 # Rounding due to contributions from more than one field.
397 us_per_hour = 3600e6
398 us_per_day = us_per_hour * 24
399 eq(td(days=.4/us_per_day), td(0))
400 eq(td(hours=.2/us_per_hour), td(0))
401 eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
402
403 eq(td(days=-.4/us_per_day), td(0))
404 eq(td(hours=-.2/us_per_hour), td(0))
405 eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
406
407 def test_massive_normalization(self):
408 td = timedelta(microseconds=-1)
409 self.assertEqual((td.days, td.seconds, td.microseconds),
410 (-1, 24*3600-1, 999999))
411
412 def test_bool(self):
413 self.failUnless(timedelta(1))
414 self.failUnless(timedelta(0, 1))
415 self.failUnless(timedelta(0, 0, 1))
416 self.failUnless(timedelta(microseconds=1))
417 self.failUnless(not timedelta(0))
418
419#############################################################################
420# date tests
421
422class TestDateOnly(unittest.TestCase):
423 # Tests here won't pass if also run on datetime objects, so don't
424 # subclass this to test datetimes too.
425
426 def test_delta_non_days_ignored(self):
427 dt = date(2000, 1, 2)
428 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
429 microseconds=5)
430 days = timedelta(delta.days)
431 self.assertEqual(days, timedelta(1))
432
433 dt2 = dt + delta
434 self.assertEqual(dt2, dt + days)
435
436 dt2 = delta + dt
437 self.assertEqual(dt2, dt + days)
438
439 dt2 = dt - delta
440 self.assertEqual(dt2, dt - days)
441
442 delta = -delta
443 days = timedelta(delta.days)
444 self.assertEqual(days, timedelta(-2))
445
446 dt2 = dt + delta
447 self.assertEqual(dt2, dt + days)
448
449 dt2 = delta + dt
450 self.assertEqual(dt2, dt + days)
451
452 dt2 = dt - delta
453 self.assertEqual(dt2, dt - days)
454
455class TestDate(unittest.TestCase):
456 # Tests here should pass for both dates and datetimes, except for a
457 # few tests that TestDateTime overrides.
458
459 theclass = date
460
461 def test_basic_attributes(self):
462 dt = self.theclass(2002, 3, 1)
463 self.assertEqual(dt.year, 2002)
464 self.assertEqual(dt.month, 3)
465 self.assertEqual(dt.day, 1)
466
467 def test_roundtrip(self):
468 for dt in (self.theclass(1, 2, 3),
469 self.theclass.today()):
470 # Verify dt -> string -> date identity.
471 s = repr(dt)
472 self.failUnless(s.startswith('datetime.'))
473 s = s[9:]
474 dt2 = eval(s)
475 self.assertEqual(dt, dt2)
476
477 # Verify identity via reconstructing from pieces.
478 dt2 = self.theclass(dt.year, dt.month, dt.day)
479 self.assertEqual(dt, dt2)
480
481 def test_ordinal_conversions(self):
482 # Check some fixed values.
483 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
484 (1, 12, 31, 365),
485 (2, 1, 1, 366),
486 # first example from "Calendrical Calculations"
487 (1945, 11, 12, 710347)]:
488 d = self.theclass(y, m, d)
489 self.assertEqual(n, d.toordinal())
490 fromord = self.theclass.fromordinal(n)
491 self.assertEqual(d, fromord)
492 if hasattr(fromord, "hour"):
493 # if we're checking something fancier than a date, verify
494 # the extra fields have been zeroed out
495 self.assertEqual(fromord.hour, 0)
496 self.assertEqual(fromord.minute, 0)
497 self.assertEqual(fromord.second, 0)
498 self.assertEqual(fromord.microsecond, 0)
499
Tim Peters0bf60bd2003-01-08 20:40:01 +0000500 # Check first and last days of year spottily across the whole
501 # range of years supported.
502 for year in xrange(MINYEAR, MAXYEAR+1, 7):
Tim Peters2a799bf2002-12-16 20:18:38 +0000503 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
504 d = self.theclass(year, 1, 1)
505 n = d.toordinal()
506 d2 = self.theclass.fromordinal(n)
507 self.assertEqual(d, d2)
Tim Peters0bf60bd2003-01-08 20:40:01 +0000508 # Verify that moving back a day gets to the end of year-1.
509 if year > 1:
510 d = self.theclass.fromordinal(n-1)
511 d2 = self.theclass(year-1, 12, 31)
512 self.assertEqual(d, d2)
513 self.assertEqual(d2.toordinal(), n-1)
Tim Peters2a799bf2002-12-16 20:18:38 +0000514
515 # Test every day in a leap-year and a non-leap year.
516 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
517 for year, isleap in (2000, True), (2002, False):
518 n = self.theclass(year, 1, 1).toordinal()
519 for month, maxday in zip(range(1, 13), dim):
520 if month == 2 and isleap:
521 maxday += 1
522 for day in range(1, maxday+1):
523 d = self.theclass(year, month, day)
524 self.assertEqual(d.toordinal(), n)
525 self.assertEqual(d, self.theclass.fromordinal(n))
526 n += 1
527
528 def test_extreme_ordinals(self):
529 a = self.theclass.min
530 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
531 aord = a.toordinal()
532 b = a.fromordinal(aord)
533 self.assertEqual(a, b)
534
535 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
536
537 b = a + timedelta(days=1)
538 self.assertEqual(b.toordinal(), aord + 1)
539 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
540
541 a = self.theclass.max
542 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
543 aord = a.toordinal()
544 b = a.fromordinal(aord)
545 self.assertEqual(a, b)
546
547 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
548
549 b = a - timedelta(days=1)
550 self.assertEqual(b.toordinal(), aord - 1)
551 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
552
553 def test_bad_constructor_arguments(self):
554 # bad years
555 self.theclass(MINYEAR, 1, 1) # no exception
556 self.theclass(MAXYEAR, 1, 1) # no exception
557 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
558 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
559 # bad months
560 self.theclass(2000, 1, 1) # no exception
561 self.theclass(2000, 12, 1) # no exception
562 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
563 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
564 # bad days
565 self.theclass(2000, 2, 29) # no exception
566 self.theclass(2004, 2, 29) # no exception
567 self.theclass(2400, 2, 29) # no exception
568 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
569 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
570 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
571 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
572 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
573 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
574
575 def test_hash_equality(self):
576 d = self.theclass(2000, 12, 31)
577 # same thing
578 e = self.theclass(2000, 12, 31)
579 self.assertEqual(d, e)
580 self.assertEqual(hash(d), hash(e))
581
582 dic = {d: 1}
583 dic[e] = 2
584 self.assertEqual(len(dic), 1)
585 self.assertEqual(dic[d], 2)
586 self.assertEqual(dic[e], 2)
587
588 d = self.theclass(2001, 1, 1)
589 # same thing
590 e = self.theclass(2001, 1, 1)
591 self.assertEqual(d, e)
592 self.assertEqual(hash(d), hash(e))
593
594 dic = {d: 1}
595 dic[e] = 2
596 self.assertEqual(len(dic), 1)
597 self.assertEqual(dic[d], 2)
598 self.assertEqual(dic[e], 2)
599
600 def test_computations(self):
601 a = self.theclass(2002, 1, 31)
602 b = self.theclass(1956, 1, 31)
603
604 diff = a-b
605 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
606 self.assertEqual(diff.seconds, 0)
607 self.assertEqual(diff.microseconds, 0)
608
609 day = timedelta(1)
610 week = timedelta(7)
611 a = self.theclass(2002, 3, 2)
612 self.assertEqual(a + day, self.theclass(2002, 3, 3))
613 self.assertEqual(day + a, self.theclass(2002, 3, 3))
614 self.assertEqual(a - day, self.theclass(2002, 3, 1))
615 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
616 self.assertEqual(a + week, self.theclass(2002, 3, 9))
617 self.assertEqual(a - week, self.theclass(2002, 2, 23))
618 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
619 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
620 self.assertEqual((a + week) - a, week)
621 self.assertEqual((a + day) - a, day)
622 self.assertEqual((a - week) - a, -week)
623 self.assertEqual((a - day) - a, -day)
624 self.assertEqual(a - (a + week), -week)
625 self.assertEqual(a - (a + day), -day)
626 self.assertEqual(a - (a - week), week)
627 self.assertEqual(a - (a - day), day)
628
629 # Add/sub ints, longs, floats should be illegal
630 for i in 1, 1L, 1.0:
631 self.assertRaises(TypeError, lambda: a+i)
632 self.assertRaises(TypeError, lambda: a-i)
633 self.assertRaises(TypeError, lambda: i+a)
634 self.assertRaises(TypeError, lambda: i-a)
635
636 # delta - date is senseless.
637 self.assertRaises(TypeError, lambda: day - a)
638 # mixing date and (delta or date) via * or // is senseless
639 self.assertRaises(TypeError, lambda: day * a)
640 self.assertRaises(TypeError, lambda: a * day)
641 self.assertRaises(TypeError, lambda: day // a)
642 self.assertRaises(TypeError, lambda: a // day)
643 self.assertRaises(TypeError, lambda: a * a)
644 self.assertRaises(TypeError, lambda: a // a)
645 # date + date is senseless
646 self.assertRaises(TypeError, lambda: a + a)
647
648 def test_overflow(self):
649 tiny = self.theclass.resolution
650
651 dt = self.theclass.min + tiny
652 dt -= tiny # no problem
653 self.assertRaises(OverflowError, dt.__sub__, tiny)
654 self.assertRaises(OverflowError, dt.__add__, -tiny)
655
656 dt = self.theclass.max - tiny
657 dt += tiny # no problem
658 self.assertRaises(OverflowError, dt.__add__, tiny)
659 self.assertRaises(OverflowError, dt.__sub__, -tiny)
660
661 def test_fromtimestamp(self):
662 import time
663
664 # Try an arbitrary fixed value.
665 year, month, day = 1999, 9, 19
666 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
667 d = self.theclass.fromtimestamp(ts)
668 self.assertEqual(d.year, year)
669 self.assertEqual(d.month, month)
670 self.assertEqual(d.day, day)
671
672 def test_today(self):
673 import time
674
675 # We claim that today() is like fromtimestamp(time.time()), so
676 # prove it.
677 for dummy in range(3):
678 today = self.theclass.today()
679 ts = time.time()
680 todayagain = self.theclass.fromtimestamp(ts)
681 if today == todayagain:
682 break
683 # There are several legit reasons that could fail:
684 # 1. It recently became midnight, between the today() and the
685 # time() calls.
686 # 2. The platform time() has such fine resolution that we'll
687 # never get the same value twice.
688 # 3. The platform time() has poor resolution, and we just
689 # happened to call today() right before a resolution quantum
690 # boundary.
691 # 4. The system clock got fiddled between calls.
692 # In any case, wait a little while and try again.
693 time.sleep(0.1)
694
695 # It worked or it didn't. If it didn't, assume it's reason #2, and
696 # let the test pass if they're within half a second of each other.
697 self.failUnless(today == todayagain or
698 abs(todayagain - today) < timedelta(seconds=0.5))
699
700 def test_weekday(self):
701 for i in range(7):
702 # March 4, 2002 is a Monday
703 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
704 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
705 # January 2, 1956 is a Monday
706 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
707 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
708
709 def test_isocalendar(self):
710 # Check examples from
711 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
712 for i in range(7):
713 d = self.theclass(2003, 12, 22+i)
714 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
715 d = self.theclass(2003, 12, 29) + timedelta(i)
716 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
717 d = self.theclass(2004, 1, 5+i)
718 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
719 d = self.theclass(2009, 12, 21+i)
720 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
721 d = self.theclass(2009, 12, 28) + timedelta(i)
722 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
723 d = self.theclass(2010, 1, 4+i)
724 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
725
726 def test_iso_long_years(self):
727 # Calculate long ISO years and compare to table from
728 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
729 ISO_LONG_YEARS_TABLE = """
730 4 32 60 88
731 9 37 65 93
732 15 43 71 99
733 20 48 76
734 26 54 82
735
736 105 133 161 189
737 111 139 167 195
738 116 144 172
739 122 150 178
740 128 156 184
741
742 201 229 257 285
743 207 235 263 291
744 212 240 268 296
745 218 246 274
746 224 252 280
747
748 303 331 359 387
749 308 336 364 392
750 314 342 370 398
751 320 348 376
752 325 353 381
753 """
754 iso_long_years = map(int, ISO_LONG_YEARS_TABLE.split())
755 iso_long_years.sort()
756 L = []
757 for i in range(400):
758 d = self.theclass(2000+i, 12, 31)
759 d1 = self.theclass(1600+i, 12, 31)
760 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
761 if d.isocalendar()[1] == 53:
762 L.append(i)
763 self.assertEqual(L, iso_long_years)
764
765 def test_isoformat(self):
766 t = self.theclass(2, 3, 2)
767 self.assertEqual(t.isoformat(), "0002-03-02")
768
769 def test_ctime(self):
770 t = self.theclass(2002, 3, 2)
771 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
772
773 def test_strftime(self):
774 t = self.theclass(2005, 3, 2)
775 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
776
777 self.assertRaises(TypeError, t.strftime) # needs an arg
778 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
779 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
780
781 # A naive object replaces %z and %Z w/ empty strings.
782 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
783
784 def test_resolution_info(self):
785 self.assert_(isinstance(self.theclass.min, self.theclass))
786 self.assert_(isinstance(self.theclass.max, self.theclass))
787 self.assert_(isinstance(self.theclass.resolution, timedelta))
788 self.assert_(self.theclass.max > self.theclass.min)
789
790 def test_extreme_timedelta(self):
791 big = self.theclass.max - self.theclass.min
792 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
793 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
794 # n == 315537897599999999 ~= 2**58.13
795 justasbig = timedelta(0, 0, n)
796 self.assertEqual(big, justasbig)
797 self.assertEqual(self.theclass.min + big, self.theclass.max)
798 self.assertEqual(self.theclass.max - big, self.theclass.min)
799
800 def test_timetuple(self):
801 for i in range(7):
802 # January 2, 1956 is a Monday (0)
803 d = self.theclass(1956, 1, 2+i)
804 t = d.timetuple()
805 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
806 # February 1, 1956 is a Wednesday (2)
807 d = self.theclass(1956, 2, 1+i)
808 t = d.timetuple()
809 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
810 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
811 # of the year.
812 d = self.theclass(1956, 3, 1+i)
813 t = d.timetuple()
814 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
815 self.assertEqual(t.tm_year, 1956)
816 self.assertEqual(t.tm_mon, 3)
817 self.assertEqual(t.tm_mday, 1+i)
818 self.assertEqual(t.tm_hour, 0)
819 self.assertEqual(t.tm_min, 0)
820 self.assertEqual(t.tm_sec, 0)
821 self.assertEqual(t.tm_wday, (3+i)%7)
822 self.assertEqual(t.tm_yday, 61+i)
823 self.assertEqual(t.tm_isdst, -1)
824
825 def test_pickling(self):
826 import pickle, cPickle
827 args = 6, 7, 23
828 orig = self.theclass(*args)
829 state = orig.__getstate__()
830 self.assertEqual(state, '\x00\x06\x07\x17')
831 derived = self.theclass(1, 1, 1)
832 derived.__setstate__(state)
833 self.assertEqual(orig, derived)
834 for pickler in pickle, cPickle:
835 for binary in 0, 1:
836 green = pickler.dumps(orig, binary)
837 derived = pickler.loads(green)
838 self.assertEqual(orig, derived)
839
840 def test_compare(self):
841 t1 = self.theclass(2, 3, 4)
842 t2 = self.theclass(2, 3, 4)
843 self.failUnless(t1 == t2)
844 self.failUnless(t1 <= t2)
845 self.failUnless(t1 >= t2)
846 self.failUnless(not t1 != t2)
847 self.failUnless(not t1 < t2)
848 self.failUnless(not t1 > t2)
849 self.assertEqual(cmp(t1, t2), 0)
850 self.assertEqual(cmp(t2, t1), 0)
851
852 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
853 t2 = self.theclass(*args) # this is larger than t1
854 self.failUnless(t1 < t2)
855 self.failUnless(t2 > t1)
856 self.failUnless(t1 <= t2)
857 self.failUnless(t2 >= t1)
858 self.failUnless(t1 != t2)
859 self.failUnless(t2 != t1)
860 self.failUnless(not t1 == t2)
861 self.failUnless(not t2 == t1)
862 self.failUnless(not t1 > t2)
863 self.failUnless(not t2 < t1)
864 self.failUnless(not t1 >= t2)
865 self.failUnless(not t2 <= t1)
866 self.assertEqual(cmp(t1, t2), -1)
867 self.assertEqual(cmp(t2, t1), 1)
868
869 for badarg in 10, 10L, 34.5, "abc", {}, [], ():
870 self.assertRaises(TypeError, lambda: t1 == badarg)
871 self.assertRaises(TypeError, lambda: t1 != badarg)
872 self.assertRaises(TypeError, lambda: t1 <= badarg)
873 self.assertRaises(TypeError, lambda: t1 < badarg)
874 self.assertRaises(TypeError, lambda: t1 > badarg)
875 self.assertRaises(TypeError, lambda: t1 >= badarg)
876 self.assertRaises(TypeError, lambda: badarg == t1)
877 self.assertRaises(TypeError, lambda: badarg != t1)
878 self.assertRaises(TypeError, lambda: badarg <= t1)
879 self.assertRaises(TypeError, lambda: badarg < t1)
880 self.assertRaises(TypeError, lambda: badarg > t1)
881 self.assertRaises(TypeError, lambda: badarg >= t1)
882
883 def test_bool(self):
884 # All dates are considered true.
885 self.failUnless(self.theclass.min)
886 self.failUnless(self.theclass.max)
887
Tim Petersd6844152002-12-22 20:58:42 +0000888 def test_srftime_out_of_range(self):
889 # For nasty technical reasons, we can't handle years before 1900.
890 cls = self.theclass
891 self.assertEqual(cls(1900, 1, 1).strftime("%Y"), "1900")
892 for y in 1, 49, 51, 99, 100, 1000, 1899:
893 self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y")
Tim Peters12bf3392002-12-24 05:41:27 +0000894
895 def test_replace(self):
896 cls = self.theclass
897 args = [1, 2, 3]
898 base = cls(*args)
899 self.assertEqual(base, base.replace())
900
901 i = 0
902 for name, newval in (("year", 2),
903 ("month", 3),
904 ("day", 4)):
905 newargs = args[:]
906 newargs[i] = newval
907 expected = cls(*newargs)
908 got = base.replace(**{name: newval})
909 self.assertEqual(expected, got)
910 i += 1
911
912 # Out of bounds.
913 base = cls(2000, 2, 29)
914 self.assertRaises(ValueError, base.replace, year=2001)
915
Tim Peters2a799bf2002-12-16 20:18:38 +0000916#############################################################################
917# datetime tests
918
919class TestDateTime(TestDate):
920
921 theclass = datetime
922
923 def test_basic_attributes(self):
924 dt = self.theclass(2002, 3, 1, 12, 0)
925 self.assertEqual(dt.year, 2002)
926 self.assertEqual(dt.month, 3)
927 self.assertEqual(dt.day, 1)
928 self.assertEqual(dt.hour, 12)
929 self.assertEqual(dt.minute, 0)
930 self.assertEqual(dt.second, 0)
931 self.assertEqual(dt.microsecond, 0)
932
933 def test_basic_attributes_nonzero(self):
934 # Make sure all attributes are non-zero so bugs in
935 # bit-shifting access show up.
936 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
937 self.assertEqual(dt.year, 2002)
938 self.assertEqual(dt.month, 3)
939 self.assertEqual(dt.day, 1)
940 self.assertEqual(dt.hour, 12)
941 self.assertEqual(dt.minute, 59)
942 self.assertEqual(dt.second, 59)
943 self.assertEqual(dt.microsecond, 8000)
944
945 def test_roundtrip(self):
946 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
947 self.theclass.now()):
948 # Verify dt -> string -> datetime identity.
949 s = repr(dt)
950 self.failUnless(s.startswith('datetime.'))
951 s = s[9:]
952 dt2 = eval(s)
953 self.assertEqual(dt, dt2)
954
955 # Verify identity via reconstructing from pieces.
956 dt2 = self.theclass(dt.year, dt.month, dt.day,
957 dt.hour, dt.minute, dt.second,
958 dt.microsecond)
959 self.assertEqual(dt, dt2)
960
961 def test_isoformat(self):
962 t = self.theclass(2, 3, 2, 4, 5, 1, 123)
963 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123")
964 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123")
965 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123")
966 # str is ISO format with the separator forced to a blank.
967 self.assertEqual(str(t), "0002-03-02 04:05:01.000123")
968
969 t = self.theclass(2, 3, 2)
970 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
971 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
972 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
973 # str is ISO format with the separator forced to a blank.
974 self.assertEqual(str(t), "0002-03-02 00:00:00")
975
976 def test_more_ctime(self):
977 # Test fields that TestDate doesn't touch.
978 import time
979
980 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
981 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
982 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
983 # out. The difference is that t.ctime() produces " 2" for the day,
984 # but platform ctime() produces "02" for the day. According to
985 # C99, t.ctime() is correct here.
986 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
987
988 # So test a case where that difference doesn't matter.
989 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
990 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
991
992 def test_tz_independent_comparing(self):
993 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
994 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
995 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
996 self.assertEqual(dt1, dt3)
997 self.assert_(dt2 > dt3)
998
999 # Make sure comparison doesn't forget microseconds, and isn't done
1000 # via comparing a float timestamp (an IEEE double doesn't have enough
1001 # precision to span microsecond resolution across years 1 thru 9999,
1002 # so comparing via timestamp necessarily calls some distinct values
1003 # equal).
1004 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1005 us = timedelta(microseconds=1)
1006 dt2 = dt1 + us
1007 self.assertEqual(dt2 - dt1, us)
1008 self.assert_(dt1 < dt2)
1009
1010 def test_bad_constructor_arguments(self):
1011 # bad years
1012 self.theclass(MINYEAR, 1, 1) # no exception
1013 self.theclass(MAXYEAR, 1, 1) # no exception
1014 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1015 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1016 # bad months
1017 self.theclass(2000, 1, 1) # no exception
1018 self.theclass(2000, 12, 1) # no exception
1019 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1020 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1021 # bad days
1022 self.theclass(2000, 2, 29) # no exception
1023 self.theclass(2004, 2, 29) # no exception
1024 self.theclass(2400, 2, 29) # no exception
1025 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1026 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1027 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1028 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1029 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1030 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1031 # bad hours
1032 self.theclass(2000, 1, 31, 0) # no exception
1033 self.theclass(2000, 1, 31, 23) # no exception
1034 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1035 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1036 # bad minutes
1037 self.theclass(2000, 1, 31, 23, 0) # no exception
1038 self.theclass(2000, 1, 31, 23, 59) # no exception
1039 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1040 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1041 # bad seconds
1042 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1043 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1044 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1045 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1046 # bad microseconds
1047 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1048 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1049 self.assertRaises(ValueError, self.theclass,
1050 2000, 1, 31, 23, 59, 59, -1)
1051 self.assertRaises(ValueError, self.theclass,
1052 2000, 1, 31, 23, 59, 59,
1053 1000000)
1054
1055 def test_hash_equality(self):
1056 d = self.theclass(2000, 12, 31, 23, 30, 17)
1057 e = self.theclass(2000, 12, 31, 23, 30, 17)
1058 self.assertEqual(d, e)
1059 self.assertEqual(hash(d), hash(e))
1060
1061 dic = {d: 1}
1062 dic[e] = 2
1063 self.assertEqual(len(dic), 1)
1064 self.assertEqual(dic[d], 2)
1065 self.assertEqual(dic[e], 2)
1066
1067 d = self.theclass(2001, 1, 1, 0, 5, 17)
1068 e = self.theclass(2001, 1, 1, 0, 5, 17)
1069 self.assertEqual(d, e)
1070 self.assertEqual(hash(d), hash(e))
1071
1072 dic = {d: 1}
1073 dic[e] = 2
1074 self.assertEqual(len(dic), 1)
1075 self.assertEqual(dic[d], 2)
1076 self.assertEqual(dic[e], 2)
1077
1078 def test_computations(self):
1079 a = self.theclass(2002, 1, 31)
1080 b = self.theclass(1956, 1, 31)
1081 diff = a-b
1082 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1083 self.assertEqual(diff.seconds, 0)
1084 self.assertEqual(diff.microseconds, 0)
1085 a = self.theclass(2002, 3, 2, 17, 6)
1086 millisec = timedelta(0, 0, 1000)
1087 hour = timedelta(0, 3600)
1088 day = timedelta(1)
1089 week = timedelta(7)
1090 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1091 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1092 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1093 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1094 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1095 self.assertEqual(a - hour, a + -hour)
1096 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1097 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1098 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1099 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1100 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1101 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1102 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1103 self.assertEqual((a + week) - a, week)
1104 self.assertEqual((a + day) - a, day)
1105 self.assertEqual((a + hour) - a, hour)
1106 self.assertEqual((a + millisec) - a, millisec)
1107 self.assertEqual((a - week) - a, -week)
1108 self.assertEqual((a - day) - a, -day)
1109 self.assertEqual((a - hour) - a, -hour)
1110 self.assertEqual((a - millisec) - a, -millisec)
1111 self.assertEqual(a - (a + week), -week)
1112 self.assertEqual(a - (a + day), -day)
1113 self.assertEqual(a - (a + hour), -hour)
1114 self.assertEqual(a - (a + millisec), -millisec)
1115 self.assertEqual(a - (a - week), week)
1116 self.assertEqual(a - (a - day), day)
1117 self.assertEqual(a - (a - hour), hour)
1118 self.assertEqual(a - (a - millisec), millisec)
1119 self.assertEqual(a + (week + day + hour + millisec),
1120 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1121 self.assertEqual(a + (week + day + hour + millisec),
1122 (((a + week) + day) + hour) + millisec)
1123 self.assertEqual(a - (week + day + hour + millisec),
1124 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1125 self.assertEqual(a - (week + day + hour + millisec),
1126 (((a - week) - day) - hour) - millisec)
1127 # Add/sub ints, longs, floats should be illegal
1128 for i in 1, 1L, 1.0:
1129 self.assertRaises(TypeError, lambda: a+i)
1130 self.assertRaises(TypeError, lambda: a-i)
1131 self.assertRaises(TypeError, lambda: i+a)
1132 self.assertRaises(TypeError, lambda: i-a)
1133
1134 # delta - datetime is senseless.
1135 self.assertRaises(TypeError, lambda: day - a)
1136 # mixing datetime and (delta or datetime) via * or // is senseless
1137 self.assertRaises(TypeError, lambda: day * a)
1138 self.assertRaises(TypeError, lambda: a * day)
1139 self.assertRaises(TypeError, lambda: day // a)
1140 self.assertRaises(TypeError, lambda: a // day)
1141 self.assertRaises(TypeError, lambda: a * a)
1142 self.assertRaises(TypeError, lambda: a // a)
1143 # datetime + datetime is senseless
1144 self.assertRaises(TypeError, lambda: a + a)
1145
1146 def test_pickling(self):
1147 import pickle, cPickle
1148 args = 6, 7, 23, 20, 59, 1, 64**2
1149 orig = self.theclass(*args)
1150 state = orig.__getstate__()
Tim Peters0bf60bd2003-01-08 20:40:01 +00001151 self.assertEqual(state, ('\x00\x06\x07\x17\x14\x3b\x01\x00\x10\x00',))
Tim Peters2a799bf2002-12-16 20:18:38 +00001152 derived = self.theclass(1, 1, 1)
1153 derived.__setstate__(state)
1154 self.assertEqual(orig, derived)
1155 for pickler in pickle, cPickle:
1156 for binary in 0, 1:
1157 green = pickler.dumps(orig, binary)
1158 derived = pickler.loads(green)
1159 self.assertEqual(orig, derived)
1160
1161 def test_more_compare(self):
1162 # The test_compare() inherited from TestDate covers the error cases.
1163 # We just want to test lexicographic ordering on the members datetime
1164 # has that date lacks.
1165 args = [2000, 11, 29, 20, 58, 16, 999998]
1166 t1 = self.theclass(*args)
1167 t2 = self.theclass(*args)
1168 self.failUnless(t1 == t2)
1169 self.failUnless(t1 <= t2)
1170 self.failUnless(t1 >= t2)
1171 self.failUnless(not t1 != t2)
1172 self.failUnless(not t1 < t2)
1173 self.failUnless(not t1 > t2)
1174 self.assertEqual(cmp(t1, t2), 0)
1175 self.assertEqual(cmp(t2, t1), 0)
1176
1177 for i in range(len(args)):
1178 newargs = args[:]
1179 newargs[i] = args[i] + 1
1180 t2 = self.theclass(*newargs) # this is larger than t1
1181 self.failUnless(t1 < t2)
1182 self.failUnless(t2 > t1)
1183 self.failUnless(t1 <= t2)
1184 self.failUnless(t2 >= t1)
1185 self.failUnless(t1 != t2)
1186 self.failUnless(t2 != t1)
1187 self.failUnless(not t1 == t2)
1188 self.failUnless(not t2 == t1)
1189 self.failUnless(not t1 > t2)
1190 self.failUnless(not t2 < t1)
1191 self.failUnless(not t1 >= t2)
1192 self.failUnless(not t2 <= t1)
1193 self.assertEqual(cmp(t1, t2), -1)
1194 self.assertEqual(cmp(t2, t1), 1)
1195
1196
1197 # A helper for timestamp constructor tests.
1198 def verify_field_equality(self, expected, got):
1199 self.assertEqual(expected.tm_year, got.year)
1200 self.assertEqual(expected.tm_mon, got.month)
1201 self.assertEqual(expected.tm_mday, got.day)
1202 self.assertEqual(expected.tm_hour, got.hour)
1203 self.assertEqual(expected.tm_min, got.minute)
1204 self.assertEqual(expected.tm_sec, got.second)
1205
1206 def test_fromtimestamp(self):
1207 import time
1208
1209 ts = time.time()
1210 expected = time.localtime(ts)
1211 got = self.theclass.fromtimestamp(ts)
1212 self.verify_field_equality(expected, got)
1213
1214 def test_utcfromtimestamp(self):
1215 import time
1216
1217 ts = time.time()
1218 expected = time.gmtime(ts)
1219 got = self.theclass.utcfromtimestamp(ts)
1220 self.verify_field_equality(expected, got)
1221
1222 def test_utcnow(self):
1223 import time
1224
1225 # Call it a success if utcnow() and utcfromtimestamp() are within
1226 # a second of each other.
1227 tolerance = timedelta(seconds=1)
1228 for dummy in range(3):
1229 from_now = self.theclass.utcnow()
1230 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1231 if abs(from_timestamp - from_now) <= tolerance:
1232 break
1233 # Else try again a few times.
1234 self.failUnless(abs(from_timestamp - from_now) <= tolerance)
1235
1236 def test_more_timetuple(self):
1237 # This tests fields beyond those tested by the TestDate.test_timetuple.
1238 t = self.theclass(2004, 12, 31, 6, 22, 33)
1239 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
1240 self.assertEqual(t.timetuple(),
1241 (t.year, t.month, t.day,
1242 t.hour, t.minute, t.second,
1243 t.weekday(),
1244 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
1245 -1))
1246 tt = t.timetuple()
1247 self.assertEqual(tt.tm_year, t.year)
1248 self.assertEqual(tt.tm_mon, t.month)
1249 self.assertEqual(tt.tm_mday, t.day)
1250 self.assertEqual(tt.tm_hour, t.hour)
1251 self.assertEqual(tt.tm_min, t.minute)
1252 self.assertEqual(tt.tm_sec, t.second)
1253 self.assertEqual(tt.tm_wday, t.weekday())
1254 self.assertEqual(tt.tm_yday, t.toordinal() -
1255 date(t.year, 1, 1).toordinal() + 1)
1256 self.assertEqual(tt.tm_isdst, -1)
1257
1258 def test_more_strftime(self):
1259 # This tests fields beyond those tested by the TestDate.test_strftime.
1260 t = self.theclass(2004, 12, 31, 6, 22, 33)
1261 self.assertEqual(t.strftime("%m %d %y %S %M %H %j"),
1262 "12 31 04 33 22 06 366")
1263
1264 def test_extract(self):
1265 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1266 self.assertEqual(dt.date(), date(2002, 3, 4))
1267 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
1268
1269 def test_combine(self):
1270 d = date(2002, 3, 4)
1271 t = time(18, 45, 3, 1234)
1272 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1273 combine = self.theclass.combine
1274 dt = combine(d, t)
1275 self.assertEqual(dt, expected)
1276
1277 dt = combine(time=t, date=d)
1278 self.assertEqual(dt, expected)
1279
1280 self.assertEqual(d, dt.date())
1281 self.assertEqual(t, dt.time())
1282 self.assertEqual(dt, combine(dt.date(), dt.time()))
1283
1284 self.assertRaises(TypeError, combine) # need an arg
1285 self.assertRaises(TypeError, combine, d) # need two args
1286 self.assertRaises(TypeError, combine, t, d) # args reversed
1287 self.assertRaises(TypeError, combine, d, t, 1) # too many args
1288 self.assertRaises(TypeError, combine, "date", "time") # wrong types
1289
Tim Peters12bf3392002-12-24 05:41:27 +00001290 def test_replace(self):
1291 cls = self.theclass
1292 args = [1, 2, 3, 4, 5, 6, 7]
1293 base = cls(*args)
1294 self.assertEqual(base, base.replace())
1295
1296 i = 0
1297 for name, newval in (("year", 2),
1298 ("month", 3),
1299 ("day", 4),
1300 ("hour", 5),
1301 ("minute", 6),
1302 ("second", 7),
1303 ("microsecond", 8)):
1304 newargs = args[:]
1305 newargs[i] = newval
1306 expected = cls(*newargs)
1307 got = base.replace(**{name: newval})
1308 self.assertEqual(expected, got)
1309 i += 1
1310
1311 # Out of bounds.
1312 base = cls(2000, 2, 29)
1313 self.assertRaises(ValueError, base.replace, year=2001)
1314
Tim Peters80475bb2002-12-25 07:40:55 +00001315 def test_astimezone(self):
Tim Peters52dcce22003-01-23 16:36:11 +00001316 # Pretty boring! The TZ test is more interesting here. astimezone()
1317 # simply can't be applied to a naive object.
Tim Peters80475bb2002-12-25 07:40:55 +00001318 dt = self.theclass.now()
1319 f = FixedOffset(44, "")
Tim Peters80475bb2002-12-25 07:40:55 +00001320 self.assertRaises(TypeError, dt.astimezone) # not enough args
1321 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
1322 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
Tim Peters52dcce22003-01-23 16:36:11 +00001323 self.assertRaises(ValueError, dt.astimezone, f) # naive
1324 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
Tim Peters80475bb2002-12-25 07:40:55 +00001325
Tim Peters52dcce22003-01-23 16:36:11 +00001326 class Bogus(tzinfo):
1327 def utcoffset(self, dt): return None
1328 def dst(self, dt): return timedelta(0)
1329 bog = Bogus()
1330 self.assertRaises(ValueError, dt.astimezone, bog) # naive
1331
1332 class AlsoBogus(tzinfo):
1333 def utcoffset(self, dt): return timedelta(0)
1334 def dst(self, dt): return None
1335 alsobog = AlsoBogus()
1336 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
Tim Peters12bf3392002-12-24 05:41:27 +00001337
Tim Peters2a799bf2002-12-16 20:18:38 +00001338class TestTime(unittest.TestCase):
1339
1340 theclass = time
1341
1342 def test_basic_attributes(self):
1343 t = self.theclass(12, 0)
1344 self.assertEqual(t.hour, 12)
1345 self.assertEqual(t.minute, 0)
1346 self.assertEqual(t.second, 0)
1347 self.assertEqual(t.microsecond, 0)
1348
1349 def test_basic_attributes_nonzero(self):
1350 # Make sure all attributes are non-zero so bugs in
1351 # bit-shifting access show up.
1352 t = self.theclass(12, 59, 59, 8000)
1353 self.assertEqual(t.hour, 12)
1354 self.assertEqual(t.minute, 59)
1355 self.assertEqual(t.second, 59)
1356 self.assertEqual(t.microsecond, 8000)
1357
1358 def test_roundtrip(self):
1359 t = self.theclass(1, 2, 3, 4)
1360
1361 # Verify t -> string -> time identity.
1362 s = repr(t)
1363 self.failUnless(s.startswith('datetime.'))
1364 s = s[9:]
1365 t2 = eval(s)
1366 self.assertEqual(t, t2)
1367
1368 # Verify identity via reconstructing from pieces.
1369 t2 = self.theclass(t.hour, t.minute, t.second,
1370 t.microsecond)
1371 self.assertEqual(t, t2)
1372
1373 def test_comparing(self):
1374 args = [1, 2, 3, 4]
1375 t1 = self.theclass(*args)
1376 t2 = self.theclass(*args)
1377 self.failUnless(t1 == t2)
1378 self.failUnless(t1 <= t2)
1379 self.failUnless(t1 >= t2)
1380 self.failUnless(not t1 != t2)
1381 self.failUnless(not t1 < t2)
1382 self.failUnless(not t1 > t2)
1383 self.assertEqual(cmp(t1, t2), 0)
1384 self.assertEqual(cmp(t2, t1), 0)
1385
1386 for i in range(len(args)):
1387 newargs = args[:]
1388 newargs[i] = args[i] + 1
1389 t2 = self.theclass(*newargs) # this is larger than t1
1390 self.failUnless(t1 < t2)
1391 self.failUnless(t2 > t1)
1392 self.failUnless(t1 <= t2)
1393 self.failUnless(t2 >= t1)
1394 self.failUnless(t1 != t2)
1395 self.failUnless(t2 != t1)
1396 self.failUnless(not t1 == t2)
1397 self.failUnless(not t2 == t1)
1398 self.failUnless(not t1 > t2)
1399 self.failUnless(not t2 < t1)
1400 self.failUnless(not t1 >= t2)
1401 self.failUnless(not t2 <= t1)
1402 self.assertEqual(cmp(t1, t2), -1)
1403 self.assertEqual(cmp(t2, t1), 1)
1404
Tim Peters0bf60bd2003-01-08 20:40:01 +00001405 badargs = (10, 10L, 34.5, "abc", {}, [], ())
1406 if CMP_BUG_FIXED:
1407 badargs += (date(1, 1, 1), datetime(1, 1, 1, 1, 1), timedelta(9))
1408 for badarg in badargs:
Tim Peters2a799bf2002-12-16 20:18:38 +00001409 self.assertRaises(TypeError, lambda: t1 == badarg)
1410 self.assertRaises(TypeError, lambda: t1 != badarg)
1411 self.assertRaises(TypeError, lambda: t1 <= badarg)
1412 self.assertRaises(TypeError, lambda: t1 < badarg)
1413 self.assertRaises(TypeError, lambda: t1 > badarg)
1414 self.assertRaises(TypeError, lambda: t1 >= badarg)
1415 self.assertRaises(TypeError, lambda: badarg == t1)
1416 self.assertRaises(TypeError, lambda: badarg != t1)
1417 self.assertRaises(TypeError, lambda: badarg <= t1)
1418 self.assertRaises(TypeError, lambda: badarg < t1)
1419 self.assertRaises(TypeError, lambda: badarg > t1)
1420 self.assertRaises(TypeError, lambda: badarg >= t1)
1421
1422 def test_bad_constructor_arguments(self):
1423 # bad hours
1424 self.theclass(0, 0) # no exception
1425 self.theclass(23, 0) # no exception
1426 self.assertRaises(ValueError, self.theclass, -1, 0)
1427 self.assertRaises(ValueError, self.theclass, 24, 0)
1428 # bad minutes
1429 self.theclass(23, 0) # no exception
1430 self.theclass(23, 59) # no exception
1431 self.assertRaises(ValueError, self.theclass, 23, -1)
1432 self.assertRaises(ValueError, self.theclass, 23, 60)
1433 # bad seconds
1434 self.theclass(23, 59, 0) # no exception
1435 self.theclass(23, 59, 59) # no exception
1436 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
1437 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
1438 # bad microseconds
1439 self.theclass(23, 59, 59, 0) # no exception
1440 self.theclass(23, 59, 59, 999999) # no exception
1441 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
1442 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
1443
1444 def test_hash_equality(self):
1445 d = self.theclass(23, 30, 17)
1446 e = self.theclass(23, 30, 17)
1447 self.assertEqual(d, e)
1448 self.assertEqual(hash(d), hash(e))
1449
1450 dic = {d: 1}
1451 dic[e] = 2
1452 self.assertEqual(len(dic), 1)
1453 self.assertEqual(dic[d], 2)
1454 self.assertEqual(dic[e], 2)
1455
1456 d = self.theclass(0, 5, 17)
1457 e = self.theclass(0, 5, 17)
1458 self.assertEqual(d, e)
1459 self.assertEqual(hash(d), hash(e))
1460
1461 dic = {d: 1}
1462 dic[e] = 2
1463 self.assertEqual(len(dic), 1)
1464 self.assertEqual(dic[d], 2)
1465 self.assertEqual(dic[e], 2)
1466
1467 def test_isoformat(self):
1468 t = self.theclass(4, 5, 1, 123)
1469 self.assertEqual(t.isoformat(), "04:05:01.000123")
1470 self.assertEqual(t.isoformat(), str(t))
1471
1472 t = self.theclass()
1473 self.assertEqual(t.isoformat(), "00:00:00")
1474 self.assertEqual(t.isoformat(), str(t))
1475
1476 t = self.theclass(microsecond=1)
1477 self.assertEqual(t.isoformat(), "00:00:00.000001")
1478 self.assertEqual(t.isoformat(), str(t))
1479
1480 t = self.theclass(microsecond=10)
1481 self.assertEqual(t.isoformat(), "00:00:00.000010")
1482 self.assertEqual(t.isoformat(), str(t))
1483
1484 t = self.theclass(microsecond=100)
1485 self.assertEqual(t.isoformat(), "00:00:00.000100")
1486 self.assertEqual(t.isoformat(), str(t))
1487
1488 t = self.theclass(microsecond=1000)
1489 self.assertEqual(t.isoformat(), "00:00:00.001000")
1490 self.assertEqual(t.isoformat(), str(t))
1491
1492 t = self.theclass(microsecond=10000)
1493 self.assertEqual(t.isoformat(), "00:00:00.010000")
1494 self.assertEqual(t.isoformat(), str(t))
1495
1496 t = self.theclass(microsecond=100000)
1497 self.assertEqual(t.isoformat(), "00:00:00.100000")
1498 self.assertEqual(t.isoformat(), str(t))
1499
1500 def test_strftime(self):
1501 t = self.theclass(1, 2, 3, 4)
1502 self.assertEqual(t.strftime('%H %M %S'), "01 02 03")
1503 # A naive object replaces %z and %Z with empty strings.
1504 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1505
1506 def test_str(self):
1507 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
1508 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
1509 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
1510 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
1511 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
1512
1513 def test_repr(self):
1514 name = 'datetime.' + self.theclass.__name__
1515 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
1516 "%s(1, 2, 3, 4)" % name)
1517 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
1518 "%s(10, 2, 3, 4000)" % name)
1519 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
1520 "%s(0, 2, 3, 400000)" % name)
1521 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
1522 "%s(12, 2, 3)" % name)
1523 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
1524 "%s(23, 15)" % name)
1525
1526 def test_resolution_info(self):
1527 self.assert_(isinstance(self.theclass.min, self.theclass))
1528 self.assert_(isinstance(self.theclass.max, self.theclass))
1529 self.assert_(isinstance(self.theclass.resolution, timedelta))
1530 self.assert_(self.theclass.max > self.theclass.min)
1531
1532 def test_pickling(self):
1533 import pickle, cPickle
1534 args = 20, 59, 16, 64**2
1535 orig = self.theclass(*args)
1536 state = orig.__getstate__()
Tim Peters0bf60bd2003-01-08 20:40:01 +00001537 self.assertEqual(state, ('\x14\x3b\x10\x00\x10\x00',))
Tim Peters2a799bf2002-12-16 20:18:38 +00001538 derived = self.theclass()
1539 derived.__setstate__(state)
1540 self.assertEqual(orig, derived)
1541 for pickler in pickle, cPickle:
1542 for binary in 0, 1:
1543 green = pickler.dumps(orig, binary)
1544 derived = pickler.loads(green)
1545 self.assertEqual(orig, derived)
1546
1547 def test_bool(self):
1548 cls = self.theclass
1549 self.failUnless(cls(1))
1550 self.failUnless(cls(0, 1))
1551 self.failUnless(cls(0, 0, 1))
1552 self.failUnless(cls(0, 0, 0, 1))
1553 self.failUnless(not cls(0))
1554 self.failUnless(not cls())
1555
Tim Peters12bf3392002-12-24 05:41:27 +00001556 def test_replace(self):
1557 cls = self.theclass
1558 args = [1, 2, 3, 4]
1559 base = cls(*args)
1560 self.assertEqual(base, base.replace())
1561
1562 i = 0
1563 for name, newval in (("hour", 5),
1564 ("minute", 6),
1565 ("second", 7),
1566 ("microsecond", 8)):
1567 newargs = args[:]
1568 newargs[i] = newval
1569 expected = cls(*newargs)
1570 got = base.replace(**{name: newval})
1571 self.assertEqual(expected, got)
1572 i += 1
1573
1574 # Out of bounds.
1575 base = cls(1)
1576 self.assertRaises(ValueError, base.replace, hour=24)
1577 self.assertRaises(ValueError, base.replace, minute=-1)
1578 self.assertRaises(ValueError, base.replace, second=100)
1579 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1580
Tim Peters855fe882002-12-22 03:43:39 +00001581# A mixin for classes with a tzinfo= argument. Subclasses must define
1582# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001583# must be legit (which is true for time and datetime).
Tim Peters855fe882002-12-22 03:43:39 +00001584class TZInfoBase(unittest.TestCase):
Tim Peters2a799bf2002-12-16 20:18:38 +00001585
Tim Petersbad8ff02002-12-30 20:52:32 +00001586 def test_argument_passing(self):
1587 cls = self.theclass
Tim Peters0bf60bd2003-01-08 20:40:01 +00001588 # A datetime passes itself on, a time passes None.
Tim Petersbad8ff02002-12-30 20:52:32 +00001589 class introspective(tzinfo):
1590 def tzname(self, dt): return dt and "real" or "none"
Tim Peters397301e2003-01-02 21:28:08 +00001591 def utcoffset(self, dt):
1592 return timedelta(minutes = dt and 42 or -42)
Tim Petersbad8ff02002-12-30 20:52:32 +00001593 dst = utcoffset
1594
1595 obj = cls(1, 2, 3, tzinfo=introspective())
1596
Tim Peters0bf60bd2003-01-08 20:40:01 +00001597 expected = cls is time and "none" or "real"
Tim Petersbad8ff02002-12-30 20:52:32 +00001598 self.assertEqual(obj.tzname(), expected)
1599
Tim Peters0bf60bd2003-01-08 20:40:01 +00001600 expected = timedelta(minutes=(cls is time and -42 or 42))
Tim Petersbad8ff02002-12-30 20:52:32 +00001601 self.assertEqual(obj.utcoffset(), expected)
1602 self.assertEqual(obj.dst(), expected)
1603
Tim Peters855fe882002-12-22 03:43:39 +00001604 def test_bad_tzinfo_classes(self):
1605 cls = self.theclass
1606 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
Tim Peters2a799bf2002-12-16 20:18:38 +00001607
Tim Peters855fe882002-12-22 03:43:39 +00001608 class NiceTry(object):
1609 def __init__(self): pass
1610 def utcoffset(self, dt): pass
1611 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
1612
1613 class BetterTry(tzinfo):
1614 def __init__(self): pass
1615 def utcoffset(self, dt): pass
1616 b = BetterTry()
1617 t = cls(1, 1, 1, tzinfo=b)
1618 self.failUnless(t.tzinfo is b)
1619
1620 def test_utc_offset_out_of_bounds(self):
1621 class Edgy(tzinfo):
1622 def __init__(self, offset):
Tim Peters397301e2003-01-02 21:28:08 +00001623 self.offset = timedelta(minutes=offset)
Tim Peters855fe882002-12-22 03:43:39 +00001624 def utcoffset(self, dt):
1625 return self.offset
1626
1627 cls = self.theclass
1628 for offset, legit in ((-1440, False),
1629 (-1439, True),
1630 (1439, True),
1631 (1440, False)):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001632 if cls is time:
Tim Peters855fe882002-12-22 03:43:39 +00001633 t = cls(1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001634 elif cls is datetime:
Tim Peters855fe882002-12-22 03:43:39 +00001635 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001636 else:
1637 assert 0, "impossible"
Tim Peters855fe882002-12-22 03:43:39 +00001638 if legit:
1639 aofs = abs(offset)
1640 h, m = divmod(aofs, 60)
1641 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001642 if isinstance(t, datetime):
Tim Peters855fe882002-12-22 03:43:39 +00001643 t = t.timetz()
1644 self.assertEqual(str(t), "01:02:03" + tag)
1645 else:
1646 self.assertRaises(ValueError, str, t)
1647
1648 def test_tzinfo_classes(self):
1649 cls = self.theclass
1650 class C1(tzinfo):
1651 def utcoffset(self, dt): return None
1652 def dst(self, dt): return None
1653 def tzname(self, dt): return None
1654 for t in (cls(1, 1, 1),
1655 cls(1, 1, 1, tzinfo=None),
1656 cls(1, 1, 1, tzinfo=C1())):
1657 self.failUnless(t.utcoffset() is None)
1658 self.failUnless(t.dst() is None)
1659 self.failUnless(t.tzname() is None)
1660
Tim Peters855fe882002-12-22 03:43:39 +00001661 class C3(tzinfo):
1662 def utcoffset(self, dt): return timedelta(minutes=-1439)
1663 def dst(self, dt): return timedelta(minutes=1439)
1664 def tzname(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001665 t = cls(1, 1, 1, tzinfo=C3())
1666 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
1667 self.assertEqual(t.dst(), timedelta(minutes=1439))
1668 self.assertEqual(t.tzname(), "aname")
Tim Peters855fe882002-12-22 03:43:39 +00001669
1670 # Wrong types.
1671 class C4(tzinfo):
1672 def utcoffset(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001673 def dst(self, dt): return 7
Tim Peters855fe882002-12-22 03:43:39 +00001674 def tzname(self, dt): return 0
1675 t = cls(1, 1, 1, tzinfo=C4())
1676 self.assertRaises(TypeError, t.utcoffset)
1677 self.assertRaises(TypeError, t.dst)
1678 self.assertRaises(TypeError, t.tzname)
1679
1680 # Offset out of range.
Tim Peters855fe882002-12-22 03:43:39 +00001681 class C6(tzinfo):
1682 def utcoffset(self, dt): return timedelta(hours=-24)
1683 def dst(self, dt): return timedelta(hours=24)
Tim Peters397301e2003-01-02 21:28:08 +00001684 t = cls(1, 1, 1, tzinfo=C6())
1685 self.assertRaises(ValueError, t.utcoffset)
1686 self.assertRaises(ValueError, t.dst)
Tim Peters855fe882002-12-22 03:43:39 +00001687
1688 # Not a whole number of minutes.
1689 class C7(tzinfo):
1690 def utcoffset(self, dt): return timedelta(seconds=61)
1691 def dst(self, dt): return timedelta(microseconds=-81)
1692 t = cls(1, 1, 1, tzinfo=C7())
1693 self.assertRaises(ValueError, t.utcoffset)
1694 self.assertRaises(ValueError, t.dst)
1695
Tim Peters4c0db782002-12-26 05:01:19 +00001696 def test_aware_compare(self):
1697 cls = self.theclass
1698
Tim Peters60c76e42002-12-27 00:41:11 +00001699 # Ensure that utcoffset() gets ignored if the comparands have
1700 # the same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00001701 class OperandDependentOffset(tzinfo):
1702 def utcoffset(self, t):
1703 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00001704 # d0 and d1 equal after adjustment
1705 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00001706 else:
Tim Peters397301e2003-01-02 21:28:08 +00001707 # d2 off in the weeds
1708 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00001709
1710 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
1711 d0 = base.replace(minute=3)
1712 d1 = base.replace(minute=9)
1713 d2 = base.replace(minute=11)
1714 for x in d0, d1, d2:
1715 for y in d0, d1, d2:
1716 got = cmp(x, y)
Tim Peters60c76e42002-12-27 00:41:11 +00001717 expected = cmp(x.minute, y.minute)
1718 self.assertEqual(got, expected)
1719
1720 # However, if they're different members, uctoffset is not ignored.
Tim Peters0bf60bd2003-01-08 20:40:01 +00001721 # Note that a time can't actually have an operand-depedent offset,
1722 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
1723 # so skip this test for time.
1724 if cls is not time:
Tim Petersbad8ff02002-12-30 20:52:32 +00001725 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
1726 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
1727 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
1728 for x in d0, d1, d2:
1729 for y in d0, d1, d2:
1730 got = cmp(x, y)
1731 if (x is d0 or x is d1) and (y is d0 or y is d1):
1732 expected = 0
1733 elif x is y is d2:
1734 expected = 0
1735 elif x is d2:
1736 expected = -1
1737 else:
1738 assert y is d2
1739 expected = 1
1740 self.assertEqual(got, expected)
Tim Peters4c0db782002-12-26 05:01:19 +00001741
Tim Peters855fe882002-12-22 03:43:39 +00001742
Tim Peters0bf60bd2003-01-08 20:40:01 +00001743# Testing time objects with a non-None tzinfo.
Tim Peters855fe882002-12-22 03:43:39 +00001744class TestTimeTZ(TestTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001745 theclass = time
Tim Peters2a799bf2002-12-16 20:18:38 +00001746
1747 def test_empty(self):
1748 t = self.theclass()
1749 self.assertEqual(t.hour, 0)
1750 self.assertEqual(t.minute, 0)
1751 self.assertEqual(t.second, 0)
1752 self.assertEqual(t.microsecond, 0)
1753 self.failUnless(t.tzinfo is None)
1754
Tim Peters2a799bf2002-12-16 20:18:38 +00001755 def test_zones(self):
1756 est = FixedOffset(-300, "EST", 1)
1757 utc = FixedOffset(0, "UTC", -2)
1758 met = FixedOffset(60, "MET", 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001759 t1 = time( 7, 47, tzinfo=est)
1760 t2 = time(12, 47, tzinfo=utc)
1761 t3 = time(13, 47, tzinfo=met)
1762 t4 = time(microsecond=40)
1763 t5 = time(microsecond=40, tzinfo=utc)
Tim Peters2a799bf2002-12-16 20:18:38 +00001764
1765 self.assertEqual(t1.tzinfo, est)
1766 self.assertEqual(t2.tzinfo, utc)
1767 self.assertEqual(t3.tzinfo, met)
1768 self.failUnless(t4.tzinfo is None)
1769 self.assertEqual(t5.tzinfo, utc)
1770
Tim Peters855fe882002-12-22 03:43:39 +00001771 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
1772 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
1773 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00001774 self.failUnless(t4.utcoffset() is None)
1775 self.assertRaises(TypeError, t1.utcoffset, "no args")
1776
1777 self.assertEqual(t1.tzname(), "EST")
1778 self.assertEqual(t2.tzname(), "UTC")
1779 self.assertEqual(t3.tzname(), "MET")
1780 self.failUnless(t4.tzname() is None)
1781 self.assertRaises(TypeError, t1.tzname, "no args")
1782
Tim Peters855fe882002-12-22 03:43:39 +00001783 self.assertEqual(t1.dst(), timedelta(minutes=1))
1784 self.assertEqual(t2.dst(), timedelta(minutes=-2))
1785 self.assertEqual(t3.dst(), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +00001786 self.failUnless(t4.dst() is None)
1787 self.assertRaises(TypeError, t1.dst, "no args")
1788
1789 self.assertEqual(hash(t1), hash(t2))
1790 self.assertEqual(hash(t1), hash(t3))
1791 self.assertEqual(hash(t2), hash(t3))
1792
1793 self.assertEqual(t1, t2)
1794 self.assertEqual(t1, t3)
1795 self.assertEqual(t2, t3)
1796 self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
1797 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
1798 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
1799
1800 self.assertEqual(str(t1), "07:47:00-05:00")
1801 self.assertEqual(str(t2), "12:47:00+00:00")
1802 self.assertEqual(str(t3), "13:47:00+01:00")
1803 self.assertEqual(str(t4), "00:00:00.000040")
1804 self.assertEqual(str(t5), "00:00:00.000040+00:00")
1805
1806 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
1807 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
1808 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
1809 self.assertEqual(t4.isoformat(), "00:00:00.000040")
1810 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
1811
Tim Peters0bf60bd2003-01-08 20:40:01 +00001812 d = 'datetime.time'
Tim Peters2a799bf2002-12-16 20:18:38 +00001813 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
1814 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
1815 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
1816 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
1817 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
1818
1819 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
1820 "07:47:00 %Z=EST %z=-0500")
1821 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
1822 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
1823
1824 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
Tim Peters0bf60bd2003-01-08 20:40:01 +00001825 t1 = time(23, 59, tzinfo=yuck)
Tim Peters2a799bf2002-12-16 20:18:38 +00001826 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
1827 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
1828
Tim Petersb92bb712002-12-21 17:44:07 +00001829 # Check that an invalid tzname result raises an exception.
1830 class Badtzname(tzinfo):
1831 def tzname(self, dt): return 42
Tim Peters0bf60bd2003-01-08 20:40:01 +00001832 t = time(2, 3, 4, tzinfo=Badtzname())
Tim Petersb92bb712002-12-21 17:44:07 +00001833 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
1834 self.assertRaises(TypeError, t.strftime, "%Z")
Tim Peters2a799bf2002-12-16 20:18:38 +00001835
1836 def test_hash_edge_cases(self):
1837 # Offsets that overflow a basic time.
1838 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
1839 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
1840 self.assertEqual(hash(t1), hash(t2))
1841
1842 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
1843 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
1844 self.assertEqual(hash(t1), hash(t2))
1845
Tim Peters2a799bf2002-12-16 20:18:38 +00001846 def test_pickling(self):
1847 import pickle, cPickle
1848
1849 # Try one without a tzinfo.
1850 args = 20, 59, 16, 64**2
1851 orig = self.theclass(*args)
1852 state = orig.__getstate__()
1853 self.assertEqual(state, ('\x14\x3b\x10\x00\x10\x00',))
1854 derived = self.theclass()
1855 derived.__setstate__(state)
1856 self.assertEqual(orig, derived)
1857 for pickler in pickle, cPickle:
1858 for binary in 0, 1:
1859 green = pickler.dumps(orig, binary)
1860 derived = pickler.loads(green)
1861 self.assertEqual(orig, derived)
1862
1863 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00001864 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00001865 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
1866 state = orig.__getstate__()
Tim Peters37f39822003-01-10 03:49:02 +00001867 derived = self.theclass(tzinfo=FixedOffset(0, "UTC", 0))
Tim Peters2a799bf2002-12-16 20:18:38 +00001868 derived.__setstate__(state)
1869 self.assertEqual(orig, derived)
Tim Petersfb8472c2002-12-21 05:04:42 +00001870 self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
Tim Peters855fe882002-12-22 03:43:39 +00001871 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
Tim Peters2a799bf2002-12-16 20:18:38 +00001872 self.assertEqual(derived.tzname(), 'cookie')
1873
1874 for pickler in pickle, cPickle:
1875 for binary in 0, 1:
1876 green = pickler.dumps(orig, binary)
1877 derived = pickler.loads(green)
1878 self.assertEqual(orig, derived)
Tim Petersfb8472c2002-12-21 05:04:42 +00001879 self.failUnless(isinstance(derived.tzinfo,
1880 PicklableFixedOffset))
Tim Peters855fe882002-12-22 03:43:39 +00001881 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
Tim Peters2a799bf2002-12-16 20:18:38 +00001882 self.assertEqual(derived.tzname(), 'cookie')
1883
1884 def test_more_bool(self):
1885 # Test cases with non-None tzinfo.
1886 cls = self.theclass
1887
1888 t = cls(0, tzinfo=FixedOffset(-300, ""))
1889 self.failUnless(t)
1890
1891 t = cls(5, tzinfo=FixedOffset(-300, ""))
1892 self.failUnless(t)
1893
1894 t = cls(5, tzinfo=FixedOffset(300, ""))
1895 self.failUnless(not t)
1896
1897 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
1898 self.failUnless(not t)
1899
1900 # Mostly ensuring this doesn't overflow internally.
1901 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
1902 self.failUnless(t)
1903
1904 # But this should yield a value error -- the utcoffset is bogus.
1905 t = cls(0, tzinfo=FixedOffset(24*60, ""))
1906 self.assertRaises(ValueError, lambda: bool(t))
1907
1908 # Likewise.
1909 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
1910 self.assertRaises(ValueError, lambda: bool(t))
1911
Tim Peters12bf3392002-12-24 05:41:27 +00001912 def test_replace(self):
1913 cls = self.theclass
1914 z100 = FixedOffset(100, "+100")
1915 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
1916 args = [1, 2, 3, 4, z100]
1917 base = cls(*args)
1918 self.assertEqual(base, base.replace())
1919
1920 i = 0
1921 for name, newval in (("hour", 5),
1922 ("minute", 6),
1923 ("second", 7),
1924 ("microsecond", 8),
1925 ("tzinfo", zm200)):
1926 newargs = args[:]
1927 newargs[i] = newval
1928 expected = cls(*newargs)
1929 got = base.replace(**{name: newval})
1930 self.assertEqual(expected, got)
1931 i += 1
1932
1933 # Ensure we can get rid of a tzinfo.
1934 self.assertEqual(base.tzname(), "+100")
1935 base2 = base.replace(tzinfo=None)
1936 self.failUnless(base2.tzinfo is None)
1937 self.failUnless(base2.tzname() is None)
1938
1939 # Ensure we can add one.
1940 base3 = base2.replace(tzinfo=z100)
1941 self.assertEqual(base, base3)
1942 self.failUnless(base.tzinfo is base3.tzinfo)
1943
1944 # Out of bounds.
1945 base = cls(1)
1946 self.assertRaises(ValueError, base.replace, hour=24)
1947 self.assertRaises(ValueError, base.replace, minute=-1)
1948 self.assertRaises(ValueError, base.replace, second=100)
1949 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1950
Tim Peters60c76e42002-12-27 00:41:11 +00001951 def test_mixed_compare(self):
1952 t1 = time(1, 2, 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001953 t2 = time(1, 2, 3)
Tim Peters60c76e42002-12-27 00:41:11 +00001954 self.assertEqual(t1, t2)
1955 t2 = t2.replace(tzinfo=None)
1956 self.assertEqual(t1, t2)
1957 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
1958 self.assertEqual(t1, t2)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001959 if CMP_BUG_FIXED:
1960 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
1961 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00001962
Tim Peters0bf60bd2003-01-08 20:40:01 +00001963 # In time w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00001964 class Varies(tzinfo):
1965 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00001966 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00001967 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00001968 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00001969 return self.offset
1970
1971 v = Varies()
1972 t1 = t2.replace(tzinfo=v)
1973 t2 = t2.replace(tzinfo=v)
1974 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
1975 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
1976 self.assertEqual(t1, t2)
1977
1978 # But if they're not identical, it isn't ignored.
1979 t2 = t2.replace(tzinfo=Varies())
1980 self.failUnless(t1 < t2) # t1's offset counter still going up
1981
Tim Peters4c0db782002-12-26 05:01:19 +00001982
Tim Peters0bf60bd2003-01-08 20:40:01 +00001983# Testing datetime objects with a non-None tzinfo.
1984
Tim Peters855fe882002-12-22 03:43:39 +00001985class TestDateTimeTZ(TestDateTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001986 theclass = datetime
Tim Peters2a799bf2002-12-16 20:18:38 +00001987
1988 def test_trivial(self):
1989 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
1990 self.assertEqual(dt.year, 1)
1991 self.assertEqual(dt.month, 2)
1992 self.assertEqual(dt.day, 3)
1993 self.assertEqual(dt.hour, 4)
1994 self.assertEqual(dt.minute, 5)
1995 self.assertEqual(dt.second, 6)
1996 self.assertEqual(dt.microsecond, 7)
1997 self.assertEqual(dt.tzinfo, None)
1998
1999 def test_even_more_compare(self):
2000 # The test_compare() and test_more_compare() inherited from TestDate
2001 # and TestDateTime covered non-tzinfo cases.
2002
2003 # Smallest possible after UTC adjustment.
2004 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2005 # Largest possible after UTC adjustment.
2006 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2007 tzinfo=FixedOffset(-1439, ""))
2008
2009 # Make sure those compare correctly, and w/o overflow.
2010 self.failUnless(t1 < t2)
2011 self.failUnless(t1 != t2)
2012 self.failUnless(t2 > t1)
2013
2014 self.failUnless(t1 == t1)
2015 self.failUnless(t2 == t2)
2016
2017 # Equal afer adjustment.
2018 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2019 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2020 self.assertEqual(t1, t2)
2021
2022 # Change t1 not to subtract a minute, and t1 should be larger.
2023 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2024 self.failUnless(t1 > t2)
2025
2026 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2027 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2028 self.failUnless(t1 < t2)
2029
2030 # Back to the original t1, but make seconds resolve it.
2031 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2032 second=1)
2033 self.failUnless(t1 > t2)
2034
2035 # Likewise, but make microseconds resolve it.
2036 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2037 microsecond=1)
2038 self.failUnless(t1 > t2)
2039
2040 # Make t2 naive and it should fail.
2041 t2 = self.theclass.min
2042 self.assertRaises(TypeError, lambda: t1 == t2)
2043 self.assertEqual(t2, t2)
2044
2045 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2046 class Naive(tzinfo):
2047 def utcoffset(self, dt): return None
2048 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
2049 self.assertRaises(TypeError, lambda: t1 == t2)
2050 self.assertEqual(t2, t2)
2051
2052 # OTOH, it's OK to compare two of these mixing the two ways of being
2053 # naive.
2054 t1 = self.theclass(5, 6, 7)
2055 self.assertEqual(t1, t2)
2056
2057 # Try a bogus uctoffset.
2058 class Bogus(tzinfo):
Tim Peters397301e2003-01-02 21:28:08 +00002059 def utcoffset(self, dt):
2060 return timedelta(minutes=1440) # out of bounds
Tim Peters2a799bf2002-12-16 20:18:38 +00002061 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2062 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
Tim Peters60c76e42002-12-27 00:41:11 +00002063 self.assertRaises(ValueError, lambda: t1 == t2)
Tim Peters2a799bf2002-12-16 20:18:38 +00002064
Tim Peters2a799bf2002-12-16 20:18:38 +00002065 def test_pickling(self):
2066 import pickle, cPickle
2067
2068 # Try one without a tzinfo.
2069 args = 6, 7, 23, 20, 59, 1, 64**2
2070 orig = self.theclass(*args)
2071 state = orig.__getstate__()
2072 self.assertEqual(state, ('\x00\x06\x07\x17\x14\x3b\x01\x00\x10\x00',))
2073 derived = self.theclass(1, 1, 1)
2074 derived.__setstate__(state)
2075 self.assertEqual(orig, derived)
2076 for pickler in pickle, cPickle:
2077 for binary in 0, 1:
2078 green = pickler.dumps(orig, binary)
2079 derived = pickler.loads(green)
2080 self.assertEqual(orig, derived)
2081
2082 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002083 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002084 orig = self.theclass(*args, **{'tzinfo': tinfo})
2085 state = orig.__getstate__()
Tim Petersa9bc1682003-01-11 03:39:11 +00002086 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
Tim Peters2a799bf2002-12-16 20:18:38 +00002087 derived.__setstate__(state)
2088 self.assertEqual(orig, derived)
Tim Petersfb8472c2002-12-21 05:04:42 +00002089 self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
Tim Peters855fe882002-12-22 03:43:39 +00002090 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
Tim Peters2a799bf2002-12-16 20:18:38 +00002091 self.assertEqual(derived.tzname(), 'cookie')
2092
2093 for pickler in pickle, cPickle:
2094 for binary in 0, 1:
2095 green = pickler.dumps(orig, binary)
2096 derived = pickler.loads(green)
2097 self.assertEqual(orig, derived)
Tim Petersfb8472c2002-12-21 05:04:42 +00002098 self.failUnless(isinstance(derived.tzinfo,
2099 PicklableFixedOffset))
Tim Peters855fe882002-12-22 03:43:39 +00002100 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
Tim Peters2a799bf2002-12-16 20:18:38 +00002101 self.assertEqual(derived.tzname(), 'cookie')
2102
2103 def test_extreme_hashes(self):
2104 # If an attempt is made to hash these via subtracting the offset
2105 # then hashing a datetime object, OverflowError results. The
2106 # Python implementation used to blow up here.
2107 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2108 hash(t)
2109 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2110 tzinfo=FixedOffset(-1439, ""))
2111 hash(t)
2112
2113 # OTOH, an OOB offset should blow up.
2114 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2115 self.assertRaises(ValueError, hash, t)
2116
2117 def test_zones(self):
2118 est = FixedOffset(-300, "EST")
2119 utc = FixedOffset(0, "UTC")
2120 met = FixedOffset(60, "MET")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002121 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2122 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2123 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
Tim Peters2a799bf2002-12-16 20:18:38 +00002124 self.assertEqual(t1.tzinfo, est)
2125 self.assertEqual(t2.tzinfo, utc)
2126 self.assertEqual(t3.tzinfo, met)
Tim Peters855fe882002-12-22 03:43:39 +00002127 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2128 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2129 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002130 self.assertEqual(t1.tzname(), "EST")
2131 self.assertEqual(t2.tzname(), "UTC")
2132 self.assertEqual(t3.tzname(), "MET")
2133 self.assertEqual(hash(t1), hash(t2))
2134 self.assertEqual(hash(t1), hash(t3))
2135 self.assertEqual(hash(t2), hash(t3))
2136 self.assertEqual(t1, t2)
2137 self.assertEqual(t1, t3)
2138 self.assertEqual(t2, t3)
2139 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2140 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2141 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002142 d = 'datetime.datetime(2002, 3, 19, '
Tim Peters2a799bf2002-12-16 20:18:38 +00002143 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2144 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2145 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2146
2147 def test_combine(self):
2148 met = FixedOffset(60, "MET")
2149 d = date(2002, 3, 4)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002150 tz = time(18, 45, 3, 1234, tzinfo=met)
2151 dt = datetime.combine(d, tz)
2152 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
Tim Peters2a799bf2002-12-16 20:18:38 +00002153 tzinfo=met))
2154
2155 def test_extract(self):
2156 met = FixedOffset(60, "MET")
2157 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2158 self.assertEqual(dt.date(), date(2002, 3, 4))
2159 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
Tim Peters0bf60bd2003-01-08 20:40:01 +00002160 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
Tim Peters2a799bf2002-12-16 20:18:38 +00002161
2162 def test_tz_aware_arithmetic(self):
2163 import random
2164
2165 now = self.theclass.now()
2166 tz55 = FixedOffset(-330, "west 5:30")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002167 timeaware = now.time().replace(tzinfo=tz55)
Tim Peters2a799bf2002-12-16 20:18:38 +00002168 nowaware = self.theclass.combine(now.date(), timeaware)
2169 self.failUnless(nowaware.tzinfo is tz55)
2170 self.assertEqual(nowaware.timetz(), timeaware)
2171
2172 # Can't mix aware and non-aware.
2173 self.assertRaises(TypeError, lambda: now - nowaware)
2174 self.assertRaises(TypeError, lambda: nowaware - now)
2175
Tim Peters0bf60bd2003-01-08 20:40:01 +00002176 # And adding datetime's doesn't make sense, aware or not.
Tim Peters2a799bf2002-12-16 20:18:38 +00002177 self.assertRaises(TypeError, lambda: now + nowaware)
2178 self.assertRaises(TypeError, lambda: nowaware + now)
2179 self.assertRaises(TypeError, lambda: nowaware + nowaware)
2180
2181 # Subtracting should yield 0.
2182 self.assertEqual(now - now, timedelta(0))
2183 self.assertEqual(nowaware - nowaware, timedelta(0))
2184
2185 # Adding a delta should preserve tzinfo.
2186 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
2187 nowawareplus = nowaware + delta
2188 self.failUnless(nowaware.tzinfo is tz55)
2189 nowawareplus2 = delta + nowaware
2190 self.failUnless(nowawareplus2.tzinfo is tz55)
2191 self.assertEqual(nowawareplus, nowawareplus2)
2192
2193 # that - delta should be what we started with, and that - what we
2194 # started with should be delta.
2195 diff = nowawareplus - delta
2196 self.failUnless(diff.tzinfo is tz55)
2197 self.assertEqual(nowaware, diff)
2198 self.assertRaises(TypeError, lambda: delta - nowawareplus)
2199 self.assertEqual(nowawareplus - nowaware, delta)
2200
2201 # Make up a random timezone.
2202 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
Tim Peters4c0db782002-12-26 05:01:19 +00002203 # Attach it to nowawareplus.
2204 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Tim Peters2a799bf2002-12-16 20:18:38 +00002205 self.failUnless(nowawareplus.tzinfo is tzr)
2206 # Make sure the difference takes the timezone adjustments into account.
2207 got = nowaware - nowawareplus
2208 # Expected: (nowaware base - nowaware offset) -
2209 # (nowawareplus base - nowawareplus offset) =
2210 # (nowaware base - nowawareplus base) +
2211 # (nowawareplus offset - nowaware offset) =
2212 # -delta + nowawareplus offset - nowaware offset
Tim Peters855fe882002-12-22 03:43:39 +00002213 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
Tim Peters2a799bf2002-12-16 20:18:38 +00002214 self.assertEqual(got, expected)
2215
2216 # Try max possible difference.
2217 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
2218 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2219 tzinfo=FixedOffset(-1439, "max"))
2220 maxdiff = max - min
2221 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
2222 timedelta(minutes=2*1439))
2223
2224 def test_tzinfo_now(self):
2225 meth = self.theclass.now
2226 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2227 base = meth()
2228 # Try with and without naming the keyword.
2229 off42 = FixedOffset(42, "42")
2230 another = meth(off42)
Tim Peters10cadce2003-01-23 19:58:02 +00002231 again = meth(tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002232 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002233 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002234 # Bad argument with and w/o naming the keyword.
2235 self.assertRaises(TypeError, meth, 16)
2236 self.assertRaises(TypeError, meth, tzinfo=16)
2237 # Bad keyword name.
2238 self.assertRaises(TypeError, meth, tinfo=off42)
2239 # Too many args.
2240 self.assertRaises(TypeError, meth, off42, off42)
2241
Tim Peters10cadce2003-01-23 19:58:02 +00002242 # We don't know which time zone we're in, and don't have a tzinfo
2243 # class to represent it, so seeing whether a tz argument actually
2244 # does a conversion is tricky.
2245 weirdtz = FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0)
2246 utc = FixedOffset(0, "utc", 0)
2247 for dummy in range(3):
2248 now = datetime.now(weirdtz)
2249 self.failUnless(now.tzinfo is weirdtz)
2250 utcnow = datetime.utcnow().replace(tzinfo=utc)
2251 now2 = utcnow.astimezone(weirdtz)
2252 if abs(now - now2) < timedelta(seconds=30):
2253 break
2254 # Else the code is broken, or more than 30 seconds passed between
2255 # calls; assuming the latter, just try again.
2256 else:
2257 # Three strikes and we're out.
2258 self.fail("utcnow(), now(tz), or astimezone() may be broken")
2259
Tim Peters2a799bf2002-12-16 20:18:38 +00002260 def test_tzinfo_fromtimestamp(self):
2261 import time
2262 meth = self.theclass.fromtimestamp
2263 ts = time.time()
2264 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2265 base = meth(ts)
2266 # Try with and without naming the keyword.
2267 off42 = FixedOffset(42, "42")
2268 another = meth(ts, off42)
2269 again = meth(ts, tzinfo=off42)
2270 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002271 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002272 # Bad argument with and w/o naming the keyword.
2273 self.assertRaises(TypeError, meth, ts, 16)
2274 self.assertRaises(TypeError, meth, ts, tzinfo=16)
2275 # Bad keyword name.
2276 self.assertRaises(TypeError, meth, ts, tinfo=off42)
2277 # Too many args.
2278 self.assertRaises(TypeError, meth, ts, off42, off42)
2279 # Too few args.
2280 self.assertRaises(TypeError, meth)
2281
2282 def test_tzinfo_utcnow(self):
2283 meth = self.theclass.utcnow
2284 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2285 base = meth()
2286 # Try with and without naming the keyword; for whatever reason,
2287 # utcnow() doesn't accept a tzinfo argument.
2288 off42 = FixedOffset(42, "42")
2289 self.assertRaises(TypeError, meth, off42)
2290 self.assertRaises(TypeError, meth, tzinfo=off42)
2291
2292 def test_tzinfo_utcfromtimestamp(self):
2293 import time
2294 meth = self.theclass.utcfromtimestamp
2295 ts = time.time()
2296 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2297 base = meth(ts)
2298 # Try with and without naming the keyword; for whatever reason,
2299 # utcfromtimestamp() doesn't accept a tzinfo argument.
2300 off42 = FixedOffset(42, "42")
2301 self.assertRaises(TypeError, meth, ts, off42)
2302 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
2303
2304 def test_tzinfo_timetuple(self):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002305 # TestDateTime tested most of this. datetime adds a twist to the
Tim Peters2a799bf2002-12-16 20:18:38 +00002306 # DST flag.
2307 class DST(tzinfo):
2308 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002309 if isinstance(dstvalue, int):
2310 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002311 self.dstvalue = dstvalue
2312 def dst(self, dt):
2313 return self.dstvalue
2314
2315 cls = self.theclass
2316 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
2317 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
2318 t = d.timetuple()
2319 self.assertEqual(1, t.tm_year)
2320 self.assertEqual(1, t.tm_mon)
2321 self.assertEqual(1, t.tm_mday)
2322 self.assertEqual(10, t.tm_hour)
2323 self.assertEqual(20, t.tm_min)
2324 self.assertEqual(30, t.tm_sec)
2325 self.assertEqual(0, t.tm_wday)
2326 self.assertEqual(1, t.tm_yday)
2327 self.assertEqual(flag, t.tm_isdst)
2328
2329 # dst() returns wrong type.
2330 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
2331
2332 # dst() at the edge.
2333 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
2334 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
2335
2336 # dst() out of range.
2337 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
2338 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
2339
2340 def test_utctimetuple(self):
2341 class DST(tzinfo):
2342 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002343 if isinstance(dstvalue, int):
2344 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002345 self.dstvalue = dstvalue
2346 def dst(self, dt):
2347 return self.dstvalue
2348
2349 cls = self.theclass
2350 # This can't work: DST didn't implement utcoffset.
2351 self.assertRaises(NotImplementedError,
2352 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
2353
2354 class UOFS(DST):
2355 def __init__(self, uofs, dofs=None):
2356 DST.__init__(self, dofs)
Tim Peters397301e2003-01-02 21:28:08 +00002357 self.uofs = timedelta(minutes=uofs)
Tim Peters2a799bf2002-12-16 20:18:38 +00002358 def utcoffset(self, dt):
2359 return self.uofs
2360
2361 # Ensure tm_isdst is 0 regardless of what dst() says: DST is never
2362 # in effect for a UTC time.
2363 for dstvalue in -33, 33, 0, None:
2364 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
2365 t = d.utctimetuple()
2366 self.assertEqual(d.year, t.tm_year)
2367 self.assertEqual(d.month, t.tm_mon)
2368 self.assertEqual(d.day, t.tm_mday)
2369 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
2370 self.assertEqual(13, t.tm_min)
2371 self.assertEqual(d.second, t.tm_sec)
2372 self.assertEqual(d.weekday(), t.tm_wday)
2373 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
2374 t.tm_yday)
2375 self.assertEqual(0, t.tm_isdst)
2376
2377 # At the edges, UTC adjustment can normalize into years out-of-range
2378 # for a datetime object. Ensure that a correct timetuple is
2379 # created anyway.
2380 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
2381 # That goes back 1 minute less than a full day.
2382 t = tiny.utctimetuple()
2383 self.assertEqual(t.tm_year, MINYEAR-1)
2384 self.assertEqual(t.tm_mon, 12)
2385 self.assertEqual(t.tm_mday, 31)
2386 self.assertEqual(t.tm_hour, 0)
2387 self.assertEqual(t.tm_min, 1)
2388 self.assertEqual(t.tm_sec, 37)
2389 self.assertEqual(t.tm_yday, 366) # "year 0" is a leap year
2390 self.assertEqual(t.tm_isdst, 0)
2391
2392 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
2393 # That goes forward 1 minute less than a full day.
2394 t = huge.utctimetuple()
2395 self.assertEqual(t.tm_year, MAXYEAR+1)
2396 self.assertEqual(t.tm_mon, 1)
2397 self.assertEqual(t.tm_mday, 1)
2398 self.assertEqual(t.tm_hour, 23)
2399 self.assertEqual(t.tm_min, 58)
2400 self.assertEqual(t.tm_sec, 37)
2401 self.assertEqual(t.tm_yday, 1)
2402 self.assertEqual(t.tm_isdst, 0)
2403
2404 def test_tzinfo_isoformat(self):
2405 zero = FixedOffset(0, "+00:00")
2406 plus = FixedOffset(220, "+03:40")
2407 minus = FixedOffset(-231, "-03:51")
2408 unknown = FixedOffset(None, "")
2409
2410 cls = self.theclass
2411 datestr = '0001-02-03'
2412 for ofs in None, zero, plus, minus, unknown:
Tim Peters6578dc92002-12-24 18:31:27 +00002413 for us in 0, 987001:
Tim Peters2a799bf2002-12-16 20:18:38 +00002414 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
2415 timestr = '04:05:59' + (us and '.987001' or '')
2416 ofsstr = ofs is not None and d.tzname() or ''
2417 tailstr = timestr + ofsstr
2418 iso = d.isoformat()
2419 self.assertEqual(iso, datestr + 'T' + tailstr)
2420 self.assertEqual(iso, d.isoformat('T'))
2421 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
2422 self.assertEqual(str(d), datestr + ' ' + tailstr)
2423
Tim Peters12bf3392002-12-24 05:41:27 +00002424 def test_replace(self):
2425 cls = self.theclass
2426 z100 = FixedOffset(100, "+100")
2427 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2428 args = [1, 2, 3, 4, 5, 6, 7, z100]
2429 base = cls(*args)
2430 self.assertEqual(base, base.replace())
2431
2432 i = 0
2433 for name, newval in (("year", 2),
2434 ("month", 3),
2435 ("day", 4),
2436 ("hour", 5),
2437 ("minute", 6),
2438 ("second", 7),
2439 ("microsecond", 8),
2440 ("tzinfo", zm200)):
2441 newargs = args[:]
2442 newargs[i] = newval
2443 expected = cls(*newargs)
2444 got = base.replace(**{name: newval})
2445 self.assertEqual(expected, got)
2446 i += 1
2447
2448 # Ensure we can get rid of a tzinfo.
2449 self.assertEqual(base.tzname(), "+100")
2450 base2 = base.replace(tzinfo=None)
2451 self.failUnless(base2.tzinfo is None)
2452 self.failUnless(base2.tzname() is None)
2453
2454 # Ensure we can add one.
2455 base3 = base2.replace(tzinfo=z100)
2456 self.assertEqual(base, base3)
2457 self.failUnless(base.tzinfo is base3.tzinfo)
2458
2459 # Out of bounds.
2460 base = cls(2000, 2, 29)
2461 self.assertRaises(ValueError, base.replace, year=2001)
Tim Peters2a799bf2002-12-16 20:18:38 +00002462
Tim Peters80475bb2002-12-25 07:40:55 +00002463 def test_more_astimezone(self):
2464 # The inherited test_astimezone covered some trivial and error cases.
2465 fnone = FixedOffset(None, "None")
2466 f44m = FixedOffset(44, "44")
2467 fm5h = FixedOffset(-timedelta(hours=5), "m300")
2468
Tim Peters10cadce2003-01-23 19:58:02 +00002469 dt = self.theclass.now(tz=f44m)
Tim Peters80475bb2002-12-25 07:40:55 +00002470 self.failUnless(dt.tzinfo is f44m)
Tim Peters52dcce22003-01-23 16:36:11 +00002471 # Replacing with degenerate tzinfo raises an exception.
2472 self.assertRaises(ValueError, dt.astimezone, fnone)
2473 # Ditto with None tz.
2474 self.assertRaises(TypeError, dt.astimezone, None)
2475 # Replacing with same tzinfo makes no change.
Tim Peters80475bb2002-12-25 07:40:55 +00002476 x = dt.astimezone(dt.tzinfo)
2477 self.failUnless(x.tzinfo is f44m)
2478 self.assertEqual(x.date(), dt.date())
2479 self.assertEqual(x.time(), dt.time())
2480
2481 # Replacing with different tzinfo does adjust.
2482 got = dt.astimezone(fm5h)
2483 self.failUnless(got.tzinfo is fm5h)
2484 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
2485 expected = dt - dt.utcoffset() # in effect, convert to UTC
2486 expected += fm5h.utcoffset(dt) # and from there to local time
2487 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
2488 self.assertEqual(got.date(), expected.date())
2489 self.assertEqual(got.time(), expected.time())
2490 self.assertEqual(got.timetz(), expected.timetz())
2491 self.failUnless(got.tzinfo is expected.tzinfo)
2492 self.assertEqual(got, expected)
2493
Tim Peters4c0db782002-12-26 05:01:19 +00002494 def test_aware_subtract(self):
2495 cls = self.theclass
2496
Tim Peters60c76e42002-12-27 00:41:11 +00002497 # Ensure that utcoffset() is ignored when the operands have the
2498 # same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00002499 class OperandDependentOffset(tzinfo):
2500 def utcoffset(self, t):
2501 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00002502 # d0 and d1 equal after adjustment
2503 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00002504 else:
Tim Peters397301e2003-01-02 21:28:08 +00002505 # d2 off in the weeds
2506 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00002507
2508 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
2509 d0 = base.replace(minute=3)
2510 d1 = base.replace(minute=9)
2511 d2 = base.replace(minute=11)
2512 for x in d0, d1, d2:
2513 for y in d0, d1, d2:
2514 got = x - y
Tim Peters60c76e42002-12-27 00:41:11 +00002515 expected = timedelta(minutes=x.minute - y.minute)
2516 self.assertEqual(got, expected)
2517
2518 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
2519 # ignored.
2520 base = cls(8, 9, 10, 11, 12, 13, 14)
2521 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2522 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2523 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2524 for x in d0, d1, d2:
2525 for y in d0, d1, d2:
2526 got = x - y
Tim Peters4c0db782002-12-26 05:01:19 +00002527 if (x is d0 or x is d1) and (y is d0 or y is d1):
2528 expected = timedelta(0)
2529 elif x is y is d2:
2530 expected = timedelta(0)
2531 elif x is d2:
2532 expected = timedelta(minutes=(11-59)-0)
2533 else:
2534 assert y is d2
2535 expected = timedelta(minutes=0-(11-59))
2536 self.assertEqual(got, expected)
2537
Tim Peters60c76e42002-12-27 00:41:11 +00002538 def test_mixed_compare(self):
2539 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002540 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters60c76e42002-12-27 00:41:11 +00002541 self.assertEqual(t1, t2)
2542 t2 = t2.replace(tzinfo=None)
2543 self.assertEqual(t1, t2)
2544 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2545 self.assertEqual(t1, t2)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002546 if CMP_BUG_FIXED:
2547 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2548 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002549
Tim Peters0bf60bd2003-01-08 20:40:01 +00002550 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002551 class Varies(tzinfo):
2552 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002553 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002554 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002555 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002556 return self.offset
2557
2558 v = Varies()
2559 t1 = t2.replace(tzinfo=v)
2560 t2 = t2.replace(tzinfo=v)
2561 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2562 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2563 self.assertEqual(t1, t2)
2564
2565 # But if they're not identical, it isn't ignored.
2566 t2 = t2.replace(tzinfo=Varies())
2567 self.failUnless(t1 < t2) # t1's offset counter still going up
Tim Peters80475bb2002-12-25 07:40:55 +00002568
Tim Peters621818b2002-12-29 23:44:49 +00002569# Pain to set up DST-aware tzinfo classes.
2570
2571def first_sunday_on_or_after(dt):
2572 days_to_go = 6 - dt.weekday()
2573 if days_to_go:
2574 dt += timedelta(days_to_go)
2575 return dt
2576
2577ZERO = timedelta(0)
2578HOUR = timedelta(hours=1)
2579DAY = timedelta(days=1)
2580# In the US, DST starts at 2am (standard time) on the first Sunday in April.
2581DSTSTART = datetime(1, 4, 1, 2)
2582# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
Tim Peters327098a2003-01-20 22:54:38 +00002583# which is the first Sunday on or after Oct 25. Because we view 1:MM as
2584# being standard time on that day, there is no spelling in local time of
2585# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
2586DSTEND = datetime(1, 10, 25, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002587
2588class USTimeZone(tzinfo):
2589
2590 def __init__(self, hours, reprname, stdname, dstname):
2591 self.stdoffset = timedelta(hours=hours)
2592 self.reprname = reprname
2593 self.stdname = stdname
2594 self.dstname = dstname
2595
2596 def __repr__(self):
2597 return self.reprname
2598
2599 def tzname(self, dt):
2600 if self.dst(dt):
2601 return self.dstname
2602 else:
2603 return self.stdname
2604
2605 def utcoffset(self, dt):
2606 return self.stdoffset + self.dst(dt)
2607
2608 def dst(self, dt):
Tim Petersbad8ff02002-12-30 20:52:32 +00002609 if dt is None or dt.tzinfo is None:
Tim Peters621818b2002-12-29 23:44:49 +00002610 # An exception instead may be sensible here, in one or more of
2611 # the cases.
2612 return ZERO
Tim Peters521fc152002-12-31 17:36:56 +00002613 assert dt.tzinfo is self
Tim Peters621818b2002-12-29 23:44:49 +00002614
2615 # Find first Sunday in April.
2616 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
2617 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
2618
2619 # Find last Sunday in October.
2620 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
2621 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
2622
Tim Peters621818b2002-12-29 23:44:49 +00002623 # Can't compare naive to aware objects, so strip the timezone from
2624 # dt first.
Tim Peters52dcce22003-01-23 16:36:11 +00002625 if start <= dt.replace(tzinfo=None) < end:
Tim Peters621818b2002-12-29 23:44:49 +00002626 return HOUR
2627 else:
2628 return ZERO
2629
Tim Peters521fc152002-12-31 17:36:56 +00002630Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
2631Central = USTimeZone(-6, "Central", "CST", "CDT")
2632Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
2633Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
Tim Peters1024bf82002-12-30 17:09:40 +00002634utc_real = FixedOffset(0, "UTC", 0)
2635# For better test coverage, we want another flavor of UTC that's west of
2636# the Eastern and Pacific timezones.
Tim Petersadf64202003-01-04 06:03:15 +00002637utc_fake = FixedOffset(-12*60, "UTCfake", 0)
Tim Peters621818b2002-12-29 23:44:49 +00002638
2639class TestTimezoneConversions(unittest.TestCase):
Tim Peters327098a2003-01-20 22:54:38 +00002640 # The DST switch times for 2002, in std time.
Tim Peters0bf60bd2003-01-08 20:40:01 +00002641 dston = datetime(2002, 4, 7, 2)
Tim Peters327098a2003-01-20 22:54:38 +00002642 dstoff = datetime(2002, 10, 27, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002643
Tim Peters0bf60bd2003-01-08 20:40:01 +00002644 theclass = datetime
Tim Peters710fb152003-01-02 19:35:54 +00002645
Tim Peters521fc152002-12-31 17:36:56 +00002646 # Check a time that's inside DST.
2647 def checkinside(self, dt, tz, utc, dston, dstoff):
2648 self.assertEqual(dt.dst(), HOUR)
2649
2650 # Conversion to our own timezone is always an identity.
2651 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters521fc152002-12-31 17:36:56 +00002652
2653 asutc = dt.astimezone(utc)
2654 there_and_back = asutc.astimezone(tz)
2655
2656 # Conversion to UTC and back isn't always an identity here,
2657 # because there are redundant spellings (in local time) of
2658 # UTC time when DST begins: the clock jumps from 1:59:59
2659 # to 3:00:00, and a local time of 2:MM:SS doesn't really
2660 # make sense then. The classes above treat 2:MM:SS as
2661 # daylight time then (it's "after 2am"), really an alias
2662 # for 1:MM:SS standard time. The latter form is what
2663 # conversion back from UTC produces.
2664 if dt.date() == dston.date() and dt.hour == 2:
2665 # We're in the redundant hour, and coming back from
2666 # UTC gives the 1:MM:SS standard-time spelling.
2667 self.assertEqual(there_and_back + HOUR, dt)
2668 # Although during was considered to be in daylight
2669 # time, there_and_back is not.
2670 self.assertEqual(there_and_back.dst(), ZERO)
2671 # They're the same times in UTC.
2672 self.assertEqual(there_and_back.astimezone(utc),
2673 dt.astimezone(utc))
2674 else:
2675 # We're not in the redundant hour.
2676 self.assertEqual(dt, there_and_back)
2677
Tim Peters327098a2003-01-20 22:54:38 +00002678 # Because we have a redundant spelling when DST begins, there is
2679 # (unforunately) an hour when DST ends that can't be spelled at all in
2680 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
2681 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
2682 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
2683 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
2684 # expressed in local time. Nevertheless, we want conversion back
2685 # from UTC to mimic the local clock's "repeat an hour" behavior.
Tim Peters521fc152002-12-31 17:36:56 +00002686 nexthour_utc = asutc + HOUR
Tim Petersadf64202003-01-04 06:03:15 +00002687 nexthour_tz = nexthour_utc.astimezone(tz)
Tim Peters327098a2003-01-20 22:54:38 +00002688 if dt.date() == dstoff.date() and dt.hour == 0:
2689 # We're in the hour before the last DST hour. The last DST hour
Tim Petersadf64202003-01-04 06:03:15 +00002690 # is ineffable. We want the conversion back to repeat 1:MM.
Tim Peters327098a2003-01-20 22:54:38 +00002691 self.assertEqual(nexthour_tz, dt.replace(hour=1))
2692 nexthour_utc += HOUR
2693 nexthour_tz = nexthour_utc.astimezone(tz)
2694 self.assertEqual(nexthour_tz, dt.replace(hour=1))
Tim Peters521fc152002-12-31 17:36:56 +00002695 else:
Tim Peters327098a2003-01-20 22:54:38 +00002696 self.assertEqual(nexthour_tz - dt, HOUR)
Tim Peters521fc152002-12-31 17:36:56 +00002697
2698 # Check a time that's outside DST.
2699 def checkoutside(self, dt, tz, utc):
2700 self.assertEqual(dt.dst(), ZERO)
2701
2702 # Conversion to our own timezone is always an identity.
2703 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters52dcce22003-01-23 16:36:11 +00002704
2705 # Converting to UTC and back is an identity too.
2706 asutc = dt.astimezone(utc)
2707 there_and_back = asutc.astimezone(tz)
2708 self.assertEqual(dt, there_and_back)
Tim Peters521fc152002-12-31 17:36:56 +00002709
Tim Peters1024bf82002-12-30 17:09:40 +00002710 def convert_between_tz_and_utc(self, tz, utc):
2711 dston = self.dston.replace(tzinfo=tz)
Tim Peters327098a2003-01-20 22:54:38 +00002712 # Because 1:MM on the day DST ends is taken as being standard time,
2713 # there is no spelling in tz for the last hour of daylight time.
2714 # For purposes of the test, the last hour of DST is 0:MM, which is
2715 # taken as being daylight time (and 1:MM is taken as being standard
2716 # time).
Tim Peters1024bf82002-12-30 17:09:40 +00002717 dstoff = self.dstoff.replace(tzinfo=tz)
2718 for delta in (timedelta(weeks=13),
2719 DAY,
2720 HOUR,
2721 timedelta(minutes=1),
2722 timedelta(microseconds=1)):
2723
Tim Peters521fc152002-12-31 17:36:56 +00002724 self.checkinside(dston, tz, utc, dston, dstoff)
2725 for during in dston + delta, dstoff - delta:
2726 self.checkinside(during, tz, utc, dston, dstoff)
Tim Peters31cc3152002-12-30 17:37:30 +00002727
Tim Peters521fc152002-12-31 17:36:56 +00002728 self.checkoutside(dstoff, tz, utc)
2729 for outside in dston - delta, dstoff + delta:
2730 self.checkoutside(outside, tz, utc)
Tim Peters31cc3152002-12-30 17:37:30 +00002731
Tim Peters621818b2002-12-29 23:44:49 +00002732 def test_easy(self):
2733 # Despite the name of this test, the endcases are excruciating.
Tim Peters1024bf82002-12-30 17:09:40 +00002734 self.convert_between_tz_and_utc(Eastern, utc_real)
2735 self.convert_between_tz_and_utc(Pacific, utc_real)
2736 self.convert_between_tz_and_utc(Eastern, utc_fake)
2737 self.convert_between_tz_and_utc(Pacific, utc_fake)
2738 # The next is really dancing near the edge. It works because
2739 # Pacific and Eastern are far enough apart that their "problem
2740 # hours" don't overlap.
2741 self.convert_between_tz_and_utc(Eastern, Pacific)
2742 self.convert_between_tz_and_utc(Pacific, Eastern)
Tim Peters36087ed2003-01-01 04:18:51 +00002743 # OTOH, these fail! Don't enable them. The difficulty is that
2744 # the edge case tests assume that every hour is representable in
2745 # the "utc" class. This is always true for a fixed-offset tzinfo
2746 # class (lke utc_real and utc_fake), but not for Eastern or Central.
2747 # For these adjacent DST-aware time zones, the range of time offsets
2748 # tested ends up creating hours in the one that aren't representable
2749 # in the other. For the same reason, we would see failures in the
2750 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
2751 # offset deltas in convert_between_tz_and_utc().
2752 #
2753 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
2754 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
Tim Peters621818b2002-12-29 23:44:49 +00002755
Tim Petersf3615152003-01-01 21:51:37 +00002756 def test_tricky(self):
2757 # 22:00 on day before daylight starts.
2758 fourback = self.dston - timedelta(hours=4)
2759 ninewest = FixedOffset(-9*60, "-0900", 0)
Tim Peters52dcce22003-01-23 16:36:11 +00002760 fourback = fourback.replace(tzinfo=ninewest)
Tim Petersf3615152003-01-01 21:51:37 +00002761 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
2762 # 2", we should get the 3 spelling.
2763 # If we plug 22:00 the day before into Eastern, it "looks like std
2764 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
2765 # to 22:00 lands on 2:00, which makes no sense in local time (the
2766 # local clock jumps from 1 to 3). The point here is to make sure we
2767 # get the 3 spelling.
2768 expected = self.dston.replace(hour=3)
Tim Peters52dcce22003-01-23 16:36:11 +00002769 got = fourback.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00002770 self.assertEqual(expected, got)
2771
2772 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
2773 # case we want the 1:00 spelling.
Tim Peters52dcce22003-01-23 16:36:11 +00002774 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
Tim Petersf3615152003-01-01 21:51:37 +00002775 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
2776 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
2777 # spelling.
2778 expected = self.dston.replace(hour=1)
Tim Peters52dcce22003-01-23 16:36:11 +00002779 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00002780 self.assertEqual(expected, got)
Tim Peters621818b2002-12-29 23:44:49 +00002781
Tim Petersadf64202003-01-04 06:03:15 +00002782 # Now on the day DST ends, we want "repeat an hour" behavior.
2783 # UTC 4:MM 5:MM 6:MM 7:MM checking these
2784 # EST 23:MM 0:MM 1:MM 2:MM
2785 # EDT 0:MM 1:MM 2:MM 3:MM
2786 # wall 0:MM 1:MM 1:MM 2:MM against these
2787 for utc in utc_real, utc_fake:
2788 for tz in Eastern, Pacific:
Tim Peters327098a2003-01-20 22:54:38 +00002789 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
Tim Petersadf64202003-01-04 06:03:15 +00002790 # Convert that to UTC.
2791 first_std_hour -= tz.utcoffset(None)
2792 # Adjust for possibly fake UTC.
2793 asutc = first_std_hour + utc.utcoffset(None)
2794 # First UTC hour to convert; this is 4:00 when utc=utc_real &
2795 # tz=Eastern.
2796 asutcbase = asutc.replace(tzinfo=utc)
2797 for tzhour in (0, 1, 1, 2):
2798 expectedbase = self.dstoff.replace(hour=tzhour)
2799 for minute in 0, 30, 59:
2800 expected = expectedbase.replace(minute=minute)
2801 asutc = asutcbase.replace(minute=minute)
2802 astz = asutc.astimezone(tz)
2803 self.assertEqual(astz.replace(tzinfo=None), expected)
2804 asutcbase += HOUR
2805
2806
Tim Peters710fb152003-01-02 19:35:54 +00002807 def test_bogus_dst(self):
2808 class ok(tzinfo):
2809 def utcoffset(self, dt): return HOUR
2810 def dst(self, dt): return HOUR
2811
2812 now = self.theclass.now().replace(tzinfo=utc_real)
2813 # Doesn't blow up.
2814 now.astimezone(ok())
2815
2816 # Does blow up.
2817 class notok(ok):
2818 def dst(self, dt): return None
2819 self.assertRaises(ValueError, now.astimezone, notok())
2820
Tim Peters52dcce22003-01-23 16:36:11 +00002821 def test_fromutc(self):
2822 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
2823 now = datetime.utcnow().replace(tzinfo=utc_real)
2824 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
2825 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
2826 enow = Eastern.fromutc(now) # doesn't blow up
2827 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
2828 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
2829 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
2830
2831 # Always converts UTC to standard time.
2832 class FauxUSTimeZone(USTimeZone):
2833 def fromutc(self, dt):
2834 return dt + self.stdoffset
2835 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
2836
2837 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
2838 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
2839 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
2840
2841 # Check around DST start.
2842 start = self.dston.replace(hour=4, tzinfo=Eastern)
2843 fstart = start.replace(tzinfo=FEastern)
2844 for wall in 23, 0, 1, 3, 4, 5:
2845 expected = start.replace(hour=wall)
2846 if wall == 23:
2847 expected -= timedelta(days=1)
2848 got = Eastern.fromutc(start)
2849 self.assertEqual(expected, got)
2850
2851 expected = fstart + FEastern.stdoffset
2852 got = FEastern.fromutc(fstart)
2853 self.assertEqual(expected, got)
2854
2855 # Ensure astimezone() calls fromutc() too.
2856 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
2857 self.assertEqual(expected, got)
2858
2859 start += HOUR
2860 fstart += HOUR
2861
2862 # Check around DST end.
2863 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
2864 fstart = start.replace(tzinfo=FEastern)
2865 for wall in 0, 1, 1, 2, 3, 4:
2866 expected = start.replace(hour=wall)
2867 got = Eastern.fromutc(start)
2868 self.assertEqual(expected, got)
2869
2870 expected = fstart + FEastern.stdoffset
2871 got = FEastern.fromutc(fstart)
2872 self.assertEqual(expected, got)
2873
2874 # Ensure astimezone() calls fromutc() too.
2875 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
2876 self.assertEqual(expected, got)
2877
2878 start += HOUR
2879 fstart += HOUR
2880
Tim Peters710fb152003-01-02 19:35:54 +00002881
Tim Peterscfd4a8b2002-12-16 21:12:37 +00002882def test_suite():
Tim Peters2a799bf2002-12-16 20:18:38 +00002883 allsuites = [unittest.makeSuite(klass, 'test')
2884 for klass in (TestModule,
2885 TestTZInfo,
2886 TestTimeDelta,
2887 TestDateOnly,
2888 TestDate,
2889 TestDateTime,
2890 TestTime,
2891 TestTimeTZ,
2892 TestDateTimeTZ,
Tim Peters621818b2002-12-29 23:44:49 +00002893 TestTimezoneConversions,
Tim Peters2a799bf2002-12-16 20:18:38 +00002894 )
2895 ]
2896 return unittest.TestSuite(allsuites)
2897
2898def test_main():
2899 import gc
2900 import sys
2901
Tim Peterscfd4a8b2002-12-16 21:12:37 +00002902 thesuite = test_suite()
Tim Peters2a799bf2002-12-16 20:18:38 +00002903 lastrc = None
2904 while True:
2905 test_support.run_suite(thesuite)
2906 if 1: # change to 0, under a debug build, for some leak detection
2907 break
2908 gc.collect()
2909 if gc.garbage:
2910 raise SystemError("gc.garbage not empty after test run: %r" %
2911 gc.garbage)
2912 if hasattr(sys, 'gettotalrefcount'):
2913 thisrc = sys.gettotalrefcount()
2914 print >> sys.stderr, '*' * 10, 'total refs:', thisrc,
2915 if lastrc:
2916 print >> sys.stderr, 'delta:', thisrc - lastrc
2917 else:
2918 print >> sys.stderr
2919 lastrc = thisrc
2920
2921if __name__ == "__main__":
2922 test_main()