Exceptions raised during renaming in rotating file handlers are now passed to handleE...
[python.git] / Lib / test / test_datetime.py
blob27f42c67902e535d54c91e21440f15a6f54abb09
1 """Test date/time type.
3 See http://www.zope.org/Members/fdrake/DateTimeWiki/TestCases
4 """
6 import sys
7 import pickle
8 import cPickle
9 import unittest
11 from test import test_support
13 from datetime import MINYEAR, MAXYEAR
14 from datetime import timedelta
15 from datetime import tzinfo
16 from datetime import time
17 from datetime import date, datetime
19 pickle_choices = [(pickler, unpickler, proto)
20 for pickler in pickle, cPickle
21 for unpickler in pickle, cPickle
22 for proto in range(3)]
23 assert len(pickle_choices) == 2*2*3
25 # An arbitrary collection of objects of non-datetime types, for testing
26 # mixed-type comparisons.
27 OTHERSTUFF = (10, 10L, 34.5, "abc", {}, [], ())
30 #############################################################################
31 # module tests
33 class TestModule(unittest.TestCase):
35 def test_constants(self):
36 import datetime
37 self.assertEqual(datetime.MINYEAR, 1)
38 self.assertEqual(datetime.MAXYEAR, 9999)
40 #############################################################################
41 # tzinfo tests
43 class FixedOffset(tzinfo):
44 def __init__(self, offset, name, dstoffset=42):
45 if isinstance(offset, int):
46 offset = timedelta(minutes=offset)
47 if isinstance(dstoffset, int):
48 dstoffset = timedelta(minutes=dstoffset)
49 self.__offset = offset
50 self.__name = name
51 self.__dstoffset = dstoffset
52 def __repr__(self):
53 return self.__name.lower()
54 def utcoffset(self, dt):
55 return self.__offset
56 def tzname(self, dt):
57 return self.__name
58 def dst(self, dt):
59 return self.__dstoffset
61 class PicklableFixedOffset(FixedOffset):
62 def __init__(self, offset=None, name=None, dstoffset=None):
63 FixedOffset.__init__(self, offset, name, dstoffset)
65 class TestTZInfo(unittest.TestCase):
67 def test_non_abstractness(self):
68 # In order to allow subclasses to get pickled, the C implementation
69 # wasn't able to get away with having __init__ raise
70 # NotImplementedError.
71 useless = tzinfo()
72 dt = datetime.max
73 self.assertRaises(NotImplementedError, useless.tzname, dt)
74 self.assertRaises(NotImplementedError, useless.utcoffset, dt)
75 self.assertRaises(NotImplementedError, useless.dst, dt)
77 def test_subclass_must_override(self):
78 class NotEnough(tzinfo):
79 def __init__(self, offset, name):
80 self.__offset = offset
81 self.__name = name
82 self.failUnless(issubclass(NotEnough, tzinfo))
83 ne = NotEnough(3, "NotByALongShot")
84 self.failUnless(isinstance(ne, tzinfo))
86 dt = datetime.now()
87 self.assertRaises(NotImplementedError, ne.tzname, dt)
88 self.assertRaises(NotImplementedError, ne.utcoffset, dt)
89 self.assertRaises(NotImplementedError, ne.dst, dt)
91 def test_normal(self):
92 fo = FixedOffset(3, "Three")
93 self.failUnless(isinstance(fo, tzinfo))
94 for dt in datetime.now(), None:
95 self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3))
96 self.assertEqual(fo.tzname(dt), "Three")
97 self.assertEqual(fo.dst(dt), timedelta(minutes=42))
99 def test_pickling_base(self):
100 # There's no point to pickling tzinfo objects on their own (they
101 # carry no data), but they need to be picklable anyway else
102 # concrete subclasses can't be pickled.
103 orig = tzinfo.__new__(tzinfo)
104 self.failUnless(type(orig) is tzinfo)
105 for pickler, unpickler, proto in pickle_choices:
106 green = pickler.dumps(orig, proto)
107 derived = unpickler.loads(green)
108 self.failUnless(type(derived) is tzinfo)
110 def test_pickling_subclass(self):
111 # Make sure we can pickle/unpickle an instance of a subclass.
112 offset = timedelta(minutes=-300)
113 orig = PicklableFixedOffset(offset, 'cookie')
114 self.failUnless(isinstance(orig, tzinfo))
115 self.failUnless(type(orig) is PicklableFixedOffset)
116 self.assertEqual(orig.utcoffset(None), offset)
117 self.assertEqual(orig.tzname(None), 'cookie')
118 for pickler, unpickler, proto in pickle_choices:
119 green = pickler.dumps(orig, proto)
120 derived = unpickler.loads(green)
121 self.failUnless(isinstance(derived, tzinfo))
122 self.failUnless(type(derived) is PicklableFixedOffset)
123 self.assertEqual(derived.utcoffset(None), offset)
124 self.assertEqual(derived.tzname(None), 'cookie')
126 #############################################################################
127 # Base clase for testing a particular aspect of timedelta, time, date and
128 # datetime comparisons.
130 class HarmlessMixedComparison(unittest.TestCase):
131 # Test that __eq__ and __ne__ don't complain for mixed-type comparisons.
133 # Subclasses must define 'theclass', and theclass(1, 1, 1) must be a
134 # legit constructor.
136 def test_harmless_mixed_comparison(self):
137 me = self.theclass(1, 1, 1)
139 self.failIf(me == ())
140 self.failUnless(me != ())
141 self.failIf(() == me)
142 self.failUnless(() != me)
144 self.failUnless(me in [1, 20L, [], me])
145 self.failIf(me not in [1, 20L, [], me])
147 self.failUnless([] in [me, 1, 20L, []])
148 self.failIf([] not in [me, 1, 20L, []])
150 def test_harmful_mixed_comparison(self):
151 me = self.theclass(1, 1, 1)
153 self.assertRaises(TypeError, lambda: me < ())
154 self.assertRaises(TypeError, lambda: me <= ())
155 self.assertRaises(TypeError, lambda: me > ())
156 self.assertRaises(TypeError, lambda: me >= ())
158 self.assertRaises(TypeError, lambda: () < me)
159 self.assertRaises(TypeError, lambda: () <= me)
160 self.assertRaises(TypeError, lambda: () > me)
161 self.assertRaises(TypeError, lambda: () >= me)
163 self.assertRaises(TypeError, cmp, (), me)
164 self.assertRaises(TypeError, cmp, me, ())
166 #############################################################################
167 # timedelta tests
169 class TestTimeDelta(HarmlessMixedComparison):
171 theclass = timedelta
173 def test_constructor(self):
174 eq = self.assertEqual
175 td = timedelta
177 # Check keyword args to constructor
178 eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0,
179 milliseconds=0, microseconds=0))
180 eq(td(1), td(days=1))
181 eq(td(0, 1), td(seconds=1))
182 eq(td(0, 0, 1), td(microseconds=1))
183 eq(td(weeks=1), td(days=7))
184 eq(td(days=1), td(hours=24))
185 eq(td(hours=1), td(minutes=60))
186 eq(td(minutes=1), td(seconds=60))
187 eq(td(seconds=1), td(milliseconds=1000))
188 eq(td(milliseconds=1), td(microseconds=1000))
190 # Check float args to constructor
191 eq(td(weeks=1.0/7), td(days=1))
192 eq(td(days=1.0/24), td(hours=1))
193 eq(td(hours=1.0/60), td(minutes=1))
194 eq(td(minutes=1.0/60), td(seconds=1))
195 eq(td(seconds=0.001), td(milliseconds=1))
196 eq(td(milliseconds=0.001), td(microseconds=1))
198 def test_computations(self):
199 eq = self.assertEqual
200 td = timedelta
202 a = td(7) # One week
203 b = td(0, 60) # One minute
204 c = td(0, 0, 1000) # One millisecond
205 eq(a+b+c, td(7, 60, 1000))
206 eq(a-b, td(6, 24*3600 - 60))
207 eq(-a, td(-7))
208 eq(+a, td(7))
209 eq(-b, td(-1, 24*3600 - 60))
210 eq(-c, td(-1, 24*3600 - 1, 999000))
211 eq(abs(a), a)
212 eq(abs(-a), a)
213 eq(td(6, 24*3600), a)
214 eq(td(0, 0, 60*1000000), b)
215 eq(a*10, td(70))
216 eq(a*10, 10*a)
217 eq(a*10L, 10*a)
218 eq(b*10, td(0, 600))
219 eq(10*b, td(0, 600))
220 eq(b*10L, td(0, 600))
221 eq(c*10, td(0, 0, 10000))
222 eq(10*c, td(0, 0, 10000))
223 eq(c*10L, td(0, 0, 10000))
224 eq(a*-1, -a)
225 eq(b*-2, -b-b)
226 eq(c*-2, -c+-c)
227 eq(b*(60*24), (b*60)*24)
228 eq(b*(60*24), (60*b)*24)
229 eq(c*1000, td(0, 1))
230 eq(1000*c, td(0, 1))
231 eq(a//7, td(1))
232 eq(b//10, td(0, 6))
233 eq(c//1000, td(0, 0, 1))
234 eq(a//10, td(0, 7*24*360))
235 eq(a//3600000, td(0, 0, 7*24*1000))
237 def test_disallowed_computations(self):
238 a = timedelta(42)
240 # Add/sub ints, longs, floats should be illegal
241 for i in 1, 1L, 1.0:
242 self.assertRaises(TypeError, lambda: a+i)
243 self.assertRaises(TypeError, lambda: a-i)
244 self.assertRaises(TypeError, lambda: i+a)
245 self.assertRaises(TypeError, lambda: i-a)
247 # Mul/div by float isn't supported.
248 x = 2.3
249 self.assertRaises(TypeError, lambda: a*x)
250 self.assertRaises(TypeError, lambda: x*a)
251 self.assertRaises(TypeError, lambda: a/x)
252 self.assertRaises(TypeError, lambda: x/a)
253 self.assertRaises(TypeError, lambda: a // x)
254 self.assertRaises(TypeError, lambda: x // a)
256 # Divison of int by timedelta doesn't make sense.
257 # Division by zero doesn't make sense.
258 for zero in 0, 0L:
259 self.assertRaises(TypeError, lambda: zero // a)
260 self.assertRaises(ZeroDivisionError, lambda: a // zero)
262 def test_basic_attributes(self):
263 days, seconds, us = 1, 7, 31
264 td = timedelta(days, seconds, us)
265 self.assertEqual(td.days, days)
266 self.assertEqual(td.seconds, seconds)
267 self.assertEqual(td.microseconds, us)
269 def test_carries(self):
270 t1 = timedelta(days=100,
271 weeks=-7,
272 hours=-24*(100-49),
273 minutes=-3,
274 seconds=12,
275 microseconds=(3*60 - 12) * 1e6 + 1)
276 t2 = timedelta(microseconds=1)
277 self.assertEqual(t1, t2)
279 def test_hash_equality(self):
280 t1 = timedelta(days=100,
281 weeks=-7,
282 hours=-24*(100-49),
283 minutes=-3,
284 seconds=12,
285 microseconds=(3*60 - 12) * 1000000)
286 t2 = timedelta()
287 self.assertEqual(hash(t1), hash(t2))
289 t1 += timedelta(weeks=7)
290 t2 += timedelta(days=7*7)
291 self.assertEqual(t1, t2)
292 self.assertEqual(hash(t1), hash(t2))
294 d = {t1: 1}
295 d[t2] = 2
296 self.assertEqual(len(d), 1)
297 self.assertEqual(d[t1], 2)
299 def test_pickling(self):
300 args = 12, 34, 56
301 orig = timedelta(*args)
302 for pickler, unpickler, proto in pickle_choices:
303 green = pickler.dumps(orig, proto)
304 derived = unpickler.loads(green)
305 self.assertEqual(orig, derived)
307 def test_compare(self):
308 t1 = timedelta(2, 3, 4)
309 t2 = timedelta(2, 3, 4)
310 self.failUnless(t1 == t2)
311 self.failUnless(t1 <= t2)
312 self.failUnless(t1 >= t2)
313 self.failUnless(not t1 != t2)
314 self.failUnless(not t1 < t2)
315 self.failUnless(not t1 > t2)
316 self.assertEqual(cmp(t1, t2), 0)
317 self.assertEqual(cmp(t2, t1), 0)
319 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
320 t2 = timedelta(*args) # this is larger than t1
321 self.failUnless(t1 < t2)
322 self.failUnless(t2 > t1)
323 self.failUnless(t1 <= t2)
324 self.failUnless(t2 >= t1)
325 self.failUnless(t1 != t2)
326 self.failUnless(t2 != t1)
327 self.failUnless(not t1 == t2)
328 self.failUnless(not t2 == t1)
329 self.failUnless(not t1 > t2)
330 self.failUnless(not t2 < t1)
331 self.failUnless(not t1 >= t2)
332 self.failUnless(not t2 <= t1)
333 self.assertEqual(cmp(t1, t2), -1)
334 self.assertEqual(cmp(t2, t1), 1)
336 for badarg in OTHERSTUFF:
337 self.assertEqual(t1 == badarg, False)
338 self.assertEqual(t1 != badarg, True)
339 self.assertEqual(badarg == t1, False)
340 self.assertEqual(badarg != t1, True)
342 self.assertRaises(TypeError, lambda: t1 <= badarg)
343 self.assertRaises(TypeError, lambda: t1 < badarg)
344 self.assertRaises(TypeError, lambda: t1 > badarg)
345 self.assertRaises(TypeError, lambda: t1 >= badarg)
346 self.assertRaises(TypeError, lambda: badarg <= t1)
347 self.assertRaises(TypeError, lambda: badarg < t1)
348 self.assertRaises(TypeError, lambda: badarg > t1)
349 self.assertRaises(TypeError, lambda: badarg >= t1)
351 def test_str(self):
352 td = timedelta
353 eq = self.assertEqual
355 eq(str(td(1)), "1 day, 0:00:00")
356 eq(str(td(-1)), "-1 day, 0:00:00")
357 eq(str(td(2)), "2 days, 0:00:00")
358 eq(str(td(-2)), "-2 days, 0:00:00")
360 eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
361 eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
362 eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
363 "-210 days, 23:12:34")
365 eq(str(td(milliseconds=1)), "0:00:00.001000")
366 eq(str(td(microseconds=3)), "0:00:00.000003")
368 eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
369 microseconds=999999)),
370 "999999999 days, 23:59:59.999999")
372 def test_roundtrip(self):
373 for td in (timedelta(days=999999999, hours=23, minutes=59,
374 seconds=59, microseconds=999999),
375 timedelta(days=-999999999),
376 timedelta(days=1, seconds=2, microseconds=3)):
378 # Verify td -> string -> td identity.
379 s = repr(td)
380 self.failUnless(s.startswith('datetime.'))
381 s = s[9:]
382 td2 = eval(s)
383 self.assertEqual(td, td2)
385 # Verify identity via reconstructing from pieces.
386 td2 = timedelta(td.days, td.seconds, td.microseconds)
387 self.assertEqual(td, td2)
389 def test_resolution_info(self):
390 self.assert_(isinstance(timedelta.min, timedelta))
391 self.assert_(isinstance(timedelta.max, timedelta))
392 self.assert_(isinstance(timedelta.resolution, timedelta))
393 self.assert_(timedelta.max > timedelta.min)
394 self.assertEqual(timedelta.min, timedelta(-999999999))
395 self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
396 self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
398 def test_overflow(self):
399 tiny = timedelta.resolution
401 td = timedelta.min + tiny
402 td -= tiny # no problem
403 self.assertRaises(OverflowError, td.__sub__, tiny)
404 self.assertRaises(OverflowError, td.__add__, -tiny)
406 td = timedelta.max - tiny
407 td += tiny # no problem
408 self.assertRaises(OverflowError, td.__add__, tiny)
409 self.assertRaises(OverflowError, td.__sub__, -tiny)
411 self.assertRaises(OverflowError, lambda: -timedelta.max)
413 def test_microsecond_rounding(self):
414 td = timedelta
415 eq = self.assertEqual
417 # Single-field rounding.
418 eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
419 eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
420 eq(td(milliseconds=0.6/1000), td(microseconds=1))
421 eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
423 # Rounding due to contributions from more than one field.
424 us_per_hour = 3600e6
425 us_per_day = us_per_hour * 24
426 eq(td(days=.4/us_per_day), td(0))
427 eq(td(hours=.2/us_per_hour), td(0))
428 eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
430 eq(td(days=-.4/us_per_day), td(0))
431 eq(td(hours=-.2/us_per_hour), td(0))
432 eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
434 def test_massive_normalization(self):
435 td = timedelta(microseconds=-1)
436 self.assertEqual((td.days, td.seconds, td.microseconds),
437 (-1, 24*3600-1, 999999))
439 def test_bool(self):
440 self.failUnless(timedelta(1))
441 self.failUnless(timedelta(0, 1))
442 self.failUnless(timedelta(0, 0, 1))
443 self.failUnless(timedelta(microseconds=1))
444 self.failUnless(not timedelta(0))
446 def test_subclass_timedelta(self):
448 class T(timedelta):
449 @staticmethod
450 def from_td(td):
451 return T(td.days, td.seconds, td.microseconds)
453 def as_hours(self):
454 sum = (self.days * 24 +
455 self.seconds / 3600.0 +
456 self.microseconds / 3600e6)
457 return round(sum)
459 t1 = T(days=1)
460 self.assert_(type(t1) is T)
461 self.assertEqual(t1.as_hours(), 24)
463 t2 = T(days=-1, seconds=-3600)
464 self.assert_(type(t2) is T)
465 self.assertEqual(t2.as_hours(), -25)
467 t3 = t1 + t2
468 self.assert_(type(t3) is timedelta)
469 t4 = T.from_td(t3)
470 self.assert_(type(t4) is T)
471 self.assertEqual(t3.days, t4.days)
472 self.assertEqual(t3.seconds, t4.seconds)
473 self.assertEqual(t3.microseconds, t4.microseconds)
474 self.assertEqual(str(t3), str(t4))
475 self.assertEqual(t4.as_hours(), -1)
477 #############################################################################
478 # date tests
480 class TestDateOnly(unittest.TestCase):
481 # Tests here won't pass if also run on datetime objects, so don't
482 # subclass this to test datetimes too.
484 def test_delta_non_days_ignored(self):
485 dt = date(2000, 1, 2)
486 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
487 microseconds=5)
488 days = timedelta(delta.days)
489 self.assertEqual(days, timedelta(1))
491 dt2 = dt + delta
492 self.assertEqual(dt2, dt + days)
494 dt2 = delta + dt
495 self.assertEqual(dt2, dt + days)
497 dt2 = dt - delta
498 self.assertEqual(dt2, dt - days)
500 delta = -delta
501 days = timedelta(delta.days)
502 self.assertEqual(days, timedelta(-2))
504 dt2 = dt + delta
505 self.assertEqual(dt2, dt + days)
507 dt2 = delta + dt
508 self.assertEqual(dt2, dt + days)
510 dt2 = dt - delta
511 self.assertEqual(dt2, dt - days)
513 class SubclassDate(date):
514 sub_var = 1
516 class TestDate(HarmlessMixedComparison):
517 # Tests here should pass for both dates and datetimes, except for a
518 # few tests that TestDateTime overrides.
520 theclass = date
522 def test_basic_attributes(self):
523 dt = self.theclass(2002, 3, 1)
524 self.assertEqual(dt.year, 2002)
525 self.assertEqual(dt.month, 3)
526 self.assertEqual(dt.day, 1)
528 def test_roundtrip(self):
529 for dt in (self.theclass(1, 2, 3),
530 self.theclass.today()):
531 # Verify dt -> string -> date identity.
532 s = repr(dt)
533 self.failUnless(s.startswith('datetime.'))
534 s = s[9:]
535 dt2 = eval(s)
536 self.assertEqual(dt, dt2)
538 # Verify identity via reconstructing from pieces.
539 dt2 = self.theclass(dt.year, dt.month, dt.day)
540 self.assertEqual(dt, dt2)
542 def test_ordinal_conversions(self):
543 # Check some fixed values.
544 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
545 (1, 12, 31, 365),
546 (2, 1, 1, 366),
547 # first example from "Calendrical Calculations"
548 (1945, 11, 12, 710347)]:
549 d = self.theclass(y, m, d)
550 self.assertEqual(n, d.toordinal())
551 fromord = self.theclass.fromordinal(n)
552 self.assertEqual(d, fromord)
553 if hasattr(fromord, "hour"):
554 # if we're checking something fancier than a date, verify
555 # the extra fields have been zeroed out
556 self.assertEqual(fromord.hour, 0)
557 self.assertEqual(fromord.minute, 0)
558 self.assertEqual(fromord.second, 0)
559 self.assertEqual(fromord.microsecond, 0)
561 # Check first and last days of year spottily across the whole
562 # range of years supported.
563 for year in xrange(MINYEAR, MAXYEAR+1, 7):
564 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
565 d = self.theclass(year, 1, 1)
566 n = d.toordinal()
567 d2 = self.theclass.fromordinal(n)
568 self.assertEqual(d, d2)
569 # Verify that moving back a day gets to the end of year-1.
570 if year > 1:
571 d = self.theclass.fromordinal(n-1)
572 d2 = self.theclass(year-1, 12, 31)
573 self.assertEqual(d, d2)
574 self.assertEqual(d2.toordinal(), n-1)
576 # Test every day in a leap-year and a non-leap year.
577 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
578 for year, isleap in (2000, True), (2002, False):
579 n = self.theclass(year, 1, 1).toordinal()
580 for month, maxday in zip(range(1, 13), dim):
581 if month == 2 and isleap:
582 maxday += 1
583 for day in range(1, maxday+1):
584 d = self.theclass(year, month, day)
585 self.assertEqual(d.toordinal(), n)
586 self.assertEqual(d, self.theclass.fromordinal(n))
587 n += 1
589 def test_extreme_ordinals(self):
590 a = self.theclass.min
591 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
592 aord = a.toordinal()
593 b = a.fromordinal(aord)
594 self.assertEqual(a, b)
596 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
598 b = a + timedelta(days=1)
599 self.assertEqual(b.toordinal(), aord + 1)
600 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
602 a = self.theclass.max
603 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
604 aord = a.toordinal()
605 b = a.fromordinal(aord)
606 self.assertEqual(a, b)
608 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
610 b = a - timedelta(days=1)
611 self.assertEqual(b.toordinal(), aord - 1)
612 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
614 def test_bad_constructor_arguments(self):
615 # bad years
616 self.theclass(MINYEAR, 1, 1) # no exception
617 self.theclass(MAXYEAR, 1, 1) # no exception
618 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
619 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
620 # bad months
621 self.theclass(2000, 1, 1) # no exception
622 self.theclass(2000, 12, 1) # no exception
623 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
624 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
625 # bad days
626 self.theclass(2000, 2, 29) # no exception
627 self.theclass(2004, 2, 29) # no exception
628 self.theclass(2400, 2, 29) # no exception
629 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
630 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
631 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
632 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
633 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
634 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
636 def test_hash_equality(self):
637 d = self.theclass(2000, 12, 31)
638 # same thing
639 e = self.theclass(2000, 12, 31)
640 self.assertEqual(d, e)
641 self.assertEqual(hash(d), hash(e))
643 dic = {d: 1}
644 dic[e] = 2
645 self.assertEqual(len(dic), 1)
646 self.assertEqual(dic[d], 2)
647 self.assertEqual(dic[e], 2)
649 d = self.theclass(2001, 1, 1)
650 # same thing
651 e = self.theclass(2001, 1, 1)
652 self.assertEqual(d, e)
653 self.assertEqual(hash(d), hash(e))
655 dic = {d: 1}
656 dic[e] = 2
657 self.assertEqual(len(dic), 1)
658 self.assertEqual(dic[d], 2)
659 self.assertEqual(dic[e], 2)
661 def test_computations(self):
662 a = self.theclass(2002, 1, 31)
663 b = self.theclass(1956, 1, 31)
665 diff = a-b
666 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
667 self.assertEqual(diff.seconds, 0)
668 self.assertEqual(diff.microseconds, 0)
670 day = timedelta(1)
671 week = timedelta(7)
672 a = self.theclass(2002, 3, 2)
673 self.assertEqual(a + day, self.theclass(2002, 3, 3))
674 self.assertEqual(day + a, self.theclass(2002, 3, 3))
675 self.assertEqual(a - day, self.theclass(2002, 3, 1))
676 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
677 self.assertEqual(a + week, self.theclass(2002, 3, 9))
678 self.assertEqual(a - week, self.theclass(2002, 2, 23))
679 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
680 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
681 self.assertEqual((a + week) - a, week)
682 self.assertEqual((a + day) - a, day)
683 self.assertEqual((a - week) - a, -week)
684 self.assertEqual((a - day) - a, -day)
685 self.assertEqual(a - (a + week), -week)
686 self.assertEqual(a - (a + day), -day)
687 self.assertEqual(a - (a - week), week)
688 self.assertEqual(a - (a - day), day)
690 # Add/sub ints, longs, floats should be illegal
691 for i in 1, 1L, 1.0:
692 self.assertRaises(TypeError, lambda: a+i)
693 self.assertRaises(TypeError, lambda: a-i)
694 self.assertRaises(TypeError, lambda: i+a)
695 self.assertRaises(TypeError, lambda: i-a)
697 # delta - date is senseless.
698 self.assertRaises(TypeError, lambda: day - a)
699 # mixing date and (delta or date) via * or // is senseless
700 self.assertRaises(TypeError, lambda: day * a)
701 self.assertRaises(TypeError, lambda: a * day)
702 self.assertRaises(TypeError, lambda: day // a)
703 self.assertRaises(TypeError, lambda: a // day)
704 self.assertRaises(TypeError, lambda: a * a)
705 self.assertRaises(TypeError, lambda: a // a)
706 # date + date is senseless
707 self.assertRaises(TypeError, lambda: a + a)
709 def test_overflow(self):
710 tiny = self.theclass.resolution
712 dt = self.theclass.min + tiny
713 dt -= tiny # no problem
714 self.assertRaises(OverflowError, dt.__sub__, tiny)
715 self.assertRaises(OverflowError, dt.__add__, -tiny)
717 dt = self.theclass.max - tiny
718 dt += tiny # no problem
719 self.assertRaises(OverflowError, dt.__add__, tiny)
720 self.assertRaises(OverflowError, dt.__sub__, -tiny)
722 def test_fromtimestamp(self):
723 import time
725 # Try an arbitrary fixed value.
726 year, month, day = 1999, 9, 19
727 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
728 d = self.theclass.fromtimestamp(ts)
729 self.assertEqual(d.year, year)
730 self.assertEqual(d.month, month)
731 self.assertEqual(d.day, day)
733 def test_insane_fromtimestamp(self):
734 # It's possible that some platform maps time_t to double,
735 # and that this test will fail there. This test should
736 # exempt such platforms (provided they return reasonable
737 # results!).
738 for insane in -1e200, 1e200:
739 self.assertRaises(ValueError, self.theclass.fromtimestamp,
740 insane)
742 def test_today(self):
743 import time
745 # We claim that today() is like fromtimestamp(time.time()), so
746 # prove it.
747 for dummy in range(3):
748 today = self.theclass.today()
749 ts = time.time()
750 todayagain = self.theclass.fromtimestamp(ts)
751 if today == todayagain:
752 break
753 # There are several legit reasons that could fail:
754 # 1. It recently became midnight, between the today() and the
755 # time() calls.
756 # 2. The platform time() has such fine resolution that we'll
757 # never get the same value twice.
758 # 3. The platform time() has poor resolution, and we just
759 # happened to call today() right before a resolution quantum
760 # boundary.
761 # 4. The system clock got fiddled between calls.
762 # In any case, wait a little while and try again.
763 time.sleep(0.1)
765 # It worked or it didn't. If it didn't, assume it's reason #2, and
766 # let the test pass if they're within half a second of each other.
767 self.failUnless(today == todayagain or
768 abs(todayagain - today) < timedelta(seconds=0.5))
770 def test_weekday(self):
771 for i in range(7):
772 # March 4, 2002 is a Monday
773 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
774 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
775 # January 2, 1956 is a Monday
776 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
777 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
779 def test_isocalendar(self):
780 # Check examples from
781 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
782 for i in range(7):
783 d = self.theclass(2003, 12, 22+i)
784 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
785 d = self.theclass(2003, 12, 29) + timedelta(i)
786 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
787 d = self.theclass(2004, 1, 5+i)
788 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
789 d = self.theclass(2009, 12, 21+i)
790 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
791 d = self.theclass(2009, 12, 28) + timedelta(i)
792 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
793 d = self.theclass(2010, 1, 4+i)
794 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
796 def test_iso_long_years(self):
797 # Calculate long ISO years and compare to table from
798 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
799 ISO_LONG_YEARS_TABLE = """
800 4 32 60 88
801 9 37 65 93
802 15 43 71 99
803 20 48 76
804 26 54 82
806 105 133 161 189
807 111 139 167 195
808 116 144 172
809 122 150 178
810 128 156 184
812 201 229 257 285
813 207 235 263 291
814 212 240 268 296
815 218 246 274
816 224 252 280
818 303 331 359 387
819 308 336 364 392
820 314 342 370 398
821 320 348 376
822 325 353 381
824 iso_long_years = map(int, ISO_LONG_YEARS_TABLE.split())
825 iso_long_years.sort()
826 L = []
827 for i in range(400):
828 d = self.theclass(2000+i, 12, 31)
829 d1 = self.theclass(1600+i, 12, 31)
830 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
831 if d.isocalendar()[1] == 53:
832 L.append(i)
833 self.assertEqual(L, iso_long_years)
835 def test_isoformat(self):
836 t = self.theclass(2, 3, 2)
837 self.assertEqual(t.isoformat(), "0002-03-02")
839 def test_ctime(self):
840 t = self.theclass(2002, 3, 2)
841 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
843 def test_strftime(self):
844 t = self.theclass(2005, 3, 2)
845 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
846 self.assertEqual(t.strftime(""), "") # SF bug #761337
848 self.assertRaises(TypeError, t.strftime) # needs an arg
849 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
850 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
852 # A naive object replaces %z and %Z w/ empty strings.
853 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
855 def test_resolution_info(self):
856 self.assert_(isinstance(self.theclass.min, self.theclass))
857 self.assert_(isinstance(self.theclass.max, self.theclass))
858 self.assert_(isinstance(self.theclass.resolution, timedelta))
859 self.assert_(self.theclass.max > self.theclass.min)
861 def test_extreme_timedelta(self):
862 big = self.theclass.max - self.theclass.min
863 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
864 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
865 # n == 315537897599999999 ~= 2**58.13
866 justasbig = timedelta(0, 0, n)
867 self.assertEqual(big, justasbig)
868 self.assertEqual(self.theclass.min + big, self.theclass.max)
869 self.assertEqual(self.theclass.max - big, self.theclass.min)
871 def test_timetuple(self):
872 for i in range(7):
873 # January 2, 1956 is a Monday (0)
874 d = self.theclass(1956, 1, 2+i)
875 t = d.timetuple()
876 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
877 # February 1, 1956 is a Wednesday (2)
878 d = self.theclass(1956, 2, 1+i)
879 t = d.timetuple()
880 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
881 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
882 # of the year.
883 d = self.theclass(1956, 3, 1+i)
884 t = d.timetuple()
885 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
886 self.assertEqual(t.tm_year, 1956)
887 self.assertEqual(t.tm_mon, 3)
888 self.assertEqual(t.tm_mday, 1+i)
889 self.assertEqual(t.tm_hour, 0)
890 self.assertEqual(t.tm_min, 0)
891 self.assertEqual(t.tm_sec, 0)
892 self.assertEqual(t.tm_wday, (3+i)%7)
893 self.assertEqual(t.tm_yday, 61+i)
894 self.assertEqual(t.tm_isdst, -1)
896 def test_pickling(self):
897 args = 6, 7, 23
898 orig = self.theclass(*args)
899 for pickler, unpickler, proto in pickle_choices:
900 green = pickler.dumps(orig, proto)
901 derived = unpickler.loads(green)
902 self.assertEqual(orig, derived)
904 def test_compare(self):
905 t1 = self.theclass(2, 3, 4)
906 t2 = self.theclass(2, 3, 4)
907 self.failUnless(t1 == t2)
908 self.failUnless(t1 <= t2)
909 self.failUnless(t1 >= t2)
910 self.failUnless(not t1 != t2)
911 self.failUnless(not t1 < t2)
912 self.failUnless(not t1 > t2)
913 self.assertEqual(cmp(t1, t2), 0)
914 self.assertEqual(cmp(t2, t1), 0)
916 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
917 t2 = self.theclass(*args) # this is larger than t1
918 self.failUnless(t1 < t2)
919 self.failUnless(t2 > t1)
920 self.failUnless(t1 <= t2)
921 self.failUnless(t2 >= t1)
922 self.failUnless(t1 != t2)
923 self.failUnless(t2 != t1)
924 self.failUnless(not t1 == t2)
925 self.failUnless(not t2 == t1)
926 self.failUnless(not t1 > t2)
927 self.failUnless(not t2 < t1)
928 self.failUnless(not t1 >= t2)
929 self.failUnless(not t2 <= t1)
930 self.assertEqual(cmp(t1, t2), -1)
931 self.assertEqual(cmp(t2, t1), 1)
933 for badarg in OTHERSTUFF:
934 self.assertEqual(t1 == badarg, False)
935 self.assertEqual(t1 != badarg, True)
936 self.assertEqual(badarg == t1, False)
937 self.assertEqual(badarg != t1, True)
939 self.assertRaises(TypeError, lambda: t1 < badarg)
940 self.assertRaises(TypeError, lambda: t1 > badarg)
941 self.assertRaises(TypeError, lambda: t1 >= badarg)
942 self.assertRaises(TypeError, lambda: badarg <= t1)
943 self.assertRaises(TypeError, lambda: badarg < t1)
944 self.assertRaises(TypeError, lambda: badarg > t1)
945 self.assertRaises(TypeError, lambda: badarg >= t1)
947 def test_mixed_compare(self):
948 our = self.theclass(2000, 4, 5)
949 self.assertRaises(TypeError, cmp, our, 1)
950 self.assertRaises(TypeError, cmp, 1, our)
952 class AnotherDateTimeClass(object):
953 def __cmp__(self, other):
954 # Return "equal" so calling this can't be confused with
955 # compare-by-address (which never says "equal" for distinct
956 # objects).
957 return 0
959 # This still errors, because date and datetime comparison raise
960 # TypeError instead of NotImplemented when they don't know what to
961 # do, in order to stop comparison from falling back to the default
962 # compare-by-address.
963 their = AnotherDateTimeClass()
964 self.assertRaises(TypeError, cmp, our, their)
965 # Oops: The next stab raises TypeError in the C implementation,
966 # but not in the Python implementation of datetime. The difference
967 # is due to that the Python implementation defines __cmp__ but
968 # the C implementation defines tp_richcompare. This is more pain
969 # to fix than it's worth, so commenting out the test.
970 # self.assertEqual(cmp(their, our), 0)
972 # But date and datetime comparison return NotImplemented instead if the
973 # other object has a timetuple attr. This gives the other object a
974 # chance to do the comparison.
975 class Comparable(AnotherDateTimeClass):
976 def timetuple(self):
977 return ()
979 their = Comparable()
980 self.assertEqual(cmp(our, their), 0)
981 self.assertEqual(cmp(their, our), 0)
982 self.failUnless(our == their)
983 self.failUnless(their == our)
985 def test_bool(self):
986 # All dates are considered true.
987 self.failUnless(self.theclass.min)
988 self.failUnless(self.theclass.max)
990 def test_srftime_out_of_range(self):
991 # For nasty technical reasons, we can't handle years before 1900.
992 cls = self.theclass
993 self.assertEqual(cls(1900, 1, 1).strftime("%Y"), "1900")
994 for y in 1, 49, 51, 99, 100, 1000, 1899:
995 self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y")
997 def test_replace(self):
998 cls = self.theclass
999 args = [1, 2, 3]
1000 base = cls(*args)
1001 self.assertEqual(base, base.replace())
1003 i = 0
1004 for name, newval in (("year", 2),
1005 ("month", 3),
1006 ("day", 4)):
1007 newargs = args[:]
1008 newargs[i] = newval
1009 expected = cls(*newargs)
1010 got = base.replace(**{name: newval})
1011 self.assertEqual(expected, got)
1012 i += 1
1014 # Out of bounds.
1015 base = cls(2000, 2, 29)
1016 self.assertRaises(ValueError, base.replace, year=2001)
1018 def test_subclass_date(self):
1020 class C(self.theclass):
1021 theAnswer = 42
1023 def __new__(cls, *args, **kws):
1024 temp = kws.copy()
1025 extra = temp.pop('extra')
1026 result = self.theclass.__new__(cls, *args, **temp)
1027 result.extra = extra
1028 return result
1030 def newmeth(self, start):
1031 return start + self.year + self.month
1033 args = 2003, 4, 14
1035 dt1 = self.theclass(*args)
1036 dt2 = C(*args, **{'extra': 7})
1038 self.assertEqual(dt2.__class__, C)
1039 self.assertEqual(dt2.theAnswer, 42)
1040 self.assertEqual(dt2.extra, 7)
1041 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1042 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1044 def test_pickling_subclass_date(self):
1046 args = 6, 7, 23
1047 orig = SubclassDate(*args)
1048 for pickler, unpickler, proto in pickle_choices:
1049 green = pickler.dumps(orig, proto)
1050 derived = unpickler.loads(green)
1051 self.assertEqual(orig, derived)
1053 def test_backdoor_resistance(self):
1054 # For fast unpickling, the constructor accepts a pickle string.
1055 # This is a low-overhead backdoor. A user can (by intent or
1056 # mistake) pass a string directly, which (if it's the right length)
1057 # will get treated like a pickle, and bypass the normal sanity
1058 # checks in the constructor. This can create insane objects.
1059 # The constructor doesn't want to burn the time to validate all
1060 # fields, but does check the month field. This stops, e.g.,
1061 # datetime.datetime('1995-03-25') from yielding an insane object.
1062 base = '1995-03-25'
1063 if not issubclass(self.theclass, datetime):
1064 base = base[:4]
1065 for month_byte in '9', chr(0), chr(13), '\xff':
1066 self.assertRaises(TypeError, self.theclass,
1067 base[:2] + month_byte + base[3:])
1068 for ord_byte in range(1, 13):
1069 # This shouldn't blow up because of the month byte alone. If
1070 # the implementation changes to do more-careful checking, it may
1071 # blow up because other fields are insane.
1072 self.theclass(base[:2] + chr(ord_byte) + base[3:])
1074 #############################################################################
1075 # datetime tests
1077 class SubclassDatetime(datetime):
1078 sub_var = 1
1080 class TestDateTime(TestDate):
1082 theclass = datetime
1084 def test_basic_attributes(self):
1085 dt = self.theclass(2002, 3, 1, 12, 0)
1086 self.assertEqual(dt.year, 2002)
1087 self.assertEqual(dt.month, 3)
1088 self.assertEqual(dt.day, 1)
1089 self.assertEqual(dt.hour, 12)
1090 self.assertEqual(dt.minute, 0)
1091 self.assertEqual(dt.second, 0)
1092 self.assertEqual(dt.microsecond, 0)
1094 def test_basic_attributes_nonzero(self):
1095 # Make sure all attributes are non-zero so bugs in
1096 # bit-shifting access show up.
1097 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1098 self.assertEqual(dt.year, 2002)
1099 self.assertEqual(dt.month, 3)
1100 self.assertEqual(dt.day, 1)
1101 self.assertEqual(dt.hour, 12)
1102 self.assertEqual(dt.minute, 59)
1103 self.assertEqual(dt.second, 59)
1104 self.assertEqual(dt.microsecond, 8000)
1106 def test_roundtrip(self):
1107 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1108 self.theclass.now()):
1109 # Verify dt -> string -> datetime identity.
1110 s = repr(dt)
1111 self.failUnless(s.startswith('datetime.'))
1112 s = s[9:]
1113 dt2 = eval(s)
1114 self.assertEqual(dt, dt2)
1116 # Verify identity via reconstructing from pieces.
1117 dt2 = self.theclass(dt.year, dt.month, dt.day,
1118 dt.hour, dt.minute, dt.second,
1119 dt.microsecond)
1120 self.assertEqual(dt, dt2)
1122 def test_isoformat(self):
1123 t = self.theclass(2, 3, 2, 4, 5, 1, 123)
1124 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123")
1125 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123")
1126 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123")
1127 # str is ISO format with the separator forced to a blank.
1128 self.assertEqual(str(t), "0002-03-02 04:05:01.000123")
1130 t = self.theclass(2, 3, 2)
1131 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1132 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1133 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1134 # str is ISO format with the separator forced to a blank.
1135 self.assertEqual(str(t), "0002-03-02 00:00:00")
1137 def test_more_ctime(self):
1138 # Test fields that TestDate doesn't touch.
1139 import time
1141 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1142 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1143 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1144 # out. The difference is that t.ctime() produces " 2" for the day,
1145 # but platform ctime() produces "02" for the day. According to
1146 # C99, t.ctime() is correct here.
1147 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1149 # So test a case where that difference doesn't matter.
1150 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1151 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1153 def test_tz_independent_comparing(self):
1154 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1155 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1156 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1157 self.assertEqual(dt1, dt3)
1158 self.assert_(dt2 > dt3)
1160 # Make sure comparison doesn't forget microseconds, and isn't done
1161 # via comparing a float timestamp (an IEEE double doesn't have enough
1162 # precision to span microsecond resolution across years 1 thru 9999,
1163 # so comparing via timestamp necessarily calls some distinct values
1164 # equal).
1165 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1166 us = timedelta(microseconds=1)
1167 dt2 = dt1 + us
1168 self.assertEqual(dt2 - dt1, us)
1169 self.assert_(dt1 < dt2)
1171 def test_bad_constructor_arguments(self):
1172 # bad years
1173 self.theclass(MINYEAR, 1, 1) # no exception
1174 self.theclass(MAXYEAR, 1, 1) # no exception
1175 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1176 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1177 # bad months
1178 self.theclass(2000, 1, 1) # no exception
1179 self.theclass(2000, 12, 1) # no exception
1180 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1181 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1182 # bad days
1183 self.theclass(2000, 2, 29) # no exception
1184 self.theclass(2004, 2, 29) # no exception
1185 self.theclass(2400, 2, 29) # no exception
1186 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1187 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1188 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1189 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1190 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1191 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1192 # bad hours
1193 self.theclass(2000, 1, 31, 0) # no exception
1194 self.theclass(2000, 1, 31, 23) # no exception
1195 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1196 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1197 # bad minutes
1198 self.theclass(2000, 1, 31, 23, 0) # no exception
1199 self.theclass(2000, 1, 31, 23, 59) # no exception
1200 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1201 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1202 # bad seconds
1203 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1204 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1205 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1206 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1207 # bad microseconds
1208 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1209 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1210 self.assertRaises(ValueError, self.theclass,
1211 2000, 1, 31, 23, 59, 59, -1)
1212 self.assertRaises(ValueError, self.theclass,
1213 2000, 1, 31, 23, 59, 59,
1214 1000000)
1216 def test_hash_equality(self):
1217 d = self.theclass(2000, 12, 31, 23, 30, 17)
1218 e = self.theclass(2000, 12, 31, 23, 30, 17)
1219 self.assertEqual(d, e)
1220 self.assertEqual(hash(d), hash(e))
1222 dic = {d: 1}
1223 dic[e] = 2
1224 self.assertEqual(len(dic), 1)
1225 self.assertEqual(dic[d], 2)
1226 self.assertEqual(dic[e], 2)
1228 d = self.theclass(2001, 1, 1, 0, 5, 17)
1229 e = self.theclass(2001, 1, 1, 0, 5, 17)
1230 self.assertEqual(d, e)
1231 self.assertEqual(hash(d), hash(e))
1233 dic = {d: 1}
1234 dic[e] = 2
1235 self.assertEqual(len(dic), 1)
1236 self.assertEqual(dic[d], 2)
1237 self.assertEqual(dic[e], 2)
1239 def test_computations(self):
1240 a = self.theclass(2002, 1, 31)
1241 b = self.theclass(1956, 1, 31)
1242 diff = a-b
1243 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1244 self.assertEqual(diff.seconds, 0)
1245 self.assertEqual(diff.microseconds, 0)
1246 a = self.theclass(2002, 3, 2, 17, 6)
1247 millisec = timedelta(0, 0, 1000)
1248 hour = timedelta(0, 3600)
1249 day = timedelta(1)
1250 week = timedelta(7)
1251 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1252 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1253 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1254 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1255 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1256 self.assertEqual(a - hour, a + -hour)
1257 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1258 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1259 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1260 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1261 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1262 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1263 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1264 self.assertEqual((a + week) - a, week)
1265 self.assertEqual((a + day) - a, day)
1266 self.assertEqual((a + hour) - a, hour)
1267 self.assertEqual((a + millisec) - a, millisec)
1268 self.assertEqual((a - week) - a, -week)
1269 self.assertEqual((a - day) - a, -day)
1270 self.assertEqual((a - hour) - a, -hour)
1271 self.assertEqual((a - millisec) - a, -millisec)
1272 self.assertEqual(a - (a + week), -week)
1273 self.assertEqual(a - (a + day), -day)
1274 self.assertEqual(a - (a + hour), -hour)
1275 self.assertEqual(a - (a + millisec), -millisec)
1276 self.assertEqual(a - (a - week), week)
1277 self.assertEqual(a - (a - day), day)
1278 self.assertEqual(a - (a - hour), hour)
1279 self.assertEqual(a - (a - millisec), millisec)
1280 self.assertEqual(a + (week + day + hour + millisec),
1281 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1282 self.assertEqual(a + (week + day + hour + millisec),
1283 (((a + week) + day) + hour) + millisec)
1284 self.assertEqual(a - (week + day + hour + millisec),
1285 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1286 self.assertEqual(a - (week + day + hour + millisec),
1287 (((a - week) - day) - hour) - millisec)
1288 # Add/sub ints, longs, floats should be illegal
1289 for i in 1, 1L, 1.0:
1290 self.assertRaises(TypeError, lambda: a+i)
1291 self.assertRaises(TypeError, lambda: a-i)
1292 self.assertRaises(TypeError, lambda: i+a)
1293 self.assertRaises(TypeError, lambda: i-a)
1295 # delta - datetime is senseless.
1296 self.assertRaises(TypeError, lambda: day - a)
1297 # mixing datetime and (delta or datetime) via * or // is senseless
1298 self.assertRaises(TypeError, lambda: day * a)
1299 self.assertRaises(TypeError, lambda: a * day)
1300 self.assertRaises(TypeError, lambda: day // a)
1301 self.assertRaises(TypeError, lambda: a // day)
1302 self.assertRaises(TypeError, lambda: a * a)
1303 self.assertRaises(TypeError, lambda: a // a)
1304 # datetime + datetime is senseless
1305 self.assertRaises(TypeError, lambda: a + a)
1307 def test_pickling(self):
1308 args = 6, 7, 23, 20, 59, 1, 64**2
1309 orig = self.theclass(*args)
1310 for pickler, unpickler, proto in pickle_choices:
1311 green = pickler.dumps(orig, proto)
1312 derived = unpickler.loads(green)
1313 self.assertEqual(orig, derived)
1315 def test_more_pickling(self):
1316 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
1317 s = pickle.dumps(a)
1318 b = pickle.loads(s)
1319 self.assertEqual(b.year, 2003)
1320 self.assertEqual(b.month, 2)
1321 self.assertEqual(b.day, 7)
1323 def test_pickling_subclass_datetime(self):
1324 args = 6, 7, 23, 20, 59, 1, 64**2
1325 orig = SubclassDatetime(*args)
1326 for pickler, unpickler, proto in pickle_choices:
1327 green = pickler.dumps(orig, proto)
1328 derived = unpickler.loads(green)
1329 self.assertEqual(orig, derived)
1331 def test_more_compare(self):
1332 # The test_compare() inherited from TestDate covers the error cases.
1333 # We just want to test lexicographic ordering on the members datetime
1334 # has that date lacks.
1335 args = [2000, 11, 29, 20, 58, 16, 999998]
1336 t1 = self.theclass(*args)
1337 t2 = self.theclass(*args)
1338 self.failUnless(t1 == t2)
1339 self.failUnless(t1 <= t2)
1340 self.failUnless(t1 >= t2)
1341 self.failUnless(not t1 != t2)
1342 self.failUnless(not t1 < t2)
1343 self.failUnless(not t1 > t2)
1344 self.assertEqual(cmp(t1, t2), 0)
1345 self.assertEqual(cmp(t2, t1), 0)
1347 for i in range(len(args)):
1348 newargs = args[:]
1349 newargs[i] = args[i] + 1
1350 t2 = self.theclass(*newargs) # this is larger than t1
1351 self.failUnless(t1 < t2)
1352 self.failUnless(t2 > t1)
1353 self.failUnless(t1 <= t2)
1354 self.failUnless(t2 >= t1)
1355 self.failUnless(t1 != t2)
1356 self.failUnless(t2 != t1)
1357 self.failUnless(not t1 == t2)
1358 self.failUnless(not t2 == t1)
1359 self.failUnless(not t1 > t2)
1360 self.failUnless(not t2 < t1)
1361 self.failUnless(not t1 >= t2)
1362 self.failUnless(not t2 <= t1)
1363 self.assertEqual(cmp(t1, t2), -1)
1364 self.assertEqual(cmp(t2, t1), 1)
1367 # A helper for timestamp constructor tests.
1368 def verify_field_equality(self, expected, got):
1369 self.assertEqual(expected.tm_year, got.year)
1370 self.assertEqual(expected.tm_mon, got.month)
1371 self.assertEqual(expected.tm_mday, got.day)
1372 self.assertEqual(expected.tm_hour, got.hour)
1373 self.assertEqual(expected.tm_min, got.minute)
1374 self.assertEqual(expected.tm_sec, got.second)
1376 def test_fromtimestamp(self):
1377 import time
1379 ts = time.time()
1380 expected = time.localtime(ts)
1381 got = self.theclass.fromtimestamp(ts)
1382 self.verify_field_equality(expected, got)
1384 def test_utcfromtimestamp(self):
1385 import time
1387 ts = time.time()
1388 expected = time.gmtime(ts)
1389 got = self.theclass.utcfromtimestamp(ts)
1390 self.verify_field_equality(expected, got)
1392 def test_insane_fromtimestamp(self):
1393 # It's possible that some platform maps time_t to double,
1394 # and that this test will fail there. This test should
1395 # exempt such platforms (provided they return reasonable
1396 # results!).
1397 for insane in -1e200, 1e200:
1398 self.assertRaises(ValueError, self.theclass.fromtimestamp,
1399 insane)
1401 def test_insane_utcfromtimestamp(self):
1402 # It's possible that some platform maps time_t to double,
1403 # and that this test will fail there. This test should
1404 # exempt such platforms (provided they return reasonable
1405 # results!).
1406 for insane in -1e200, 1e200:
1407 self.assertRaises(ValueError, self.theclass.utcfromtimestamp,
1408 insane)
1410 def test_utcnow(self):
1411 import time
1413 # Call it a success if utcnow() and utcfromtimestamp() are within
1414 # a second of each other.
1415 tolerance = timedelta(seconds=1)
1416 for dummy in range(3):
1417 from_now = self.theclass.utcnow()
1418 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1419 if abs(from_timestamp - from_now) <= tolerance:
1420 break
1421 # Else try again a few times.
1422 self.failUnless(abs(from_timestamp - from_now) <= tolerance)
1424 def test_strptime(self):
1425 import time
1427 string = '2004-12-01 13:02:47'
1428 format = '%Y-%m-%d %H:%M:%S'
1429 expected = self.theclass(*(time.strptime(string, format)[0:6]))
1430 got = self.theclass.strptime(string, format)
1431 self.assertEqual(expected, got)
1433 def test_more_timetuple(self):
1434 # This tests fields beyond those tested by the TestDate.test_timetuple.
1435 t = self.theclass(2004, 12, 31, 6, 22, 33)
1436 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
1437 self.assertEqual(t.timetuple(),
1438 (t.year, t.month, t.day,
1439 t.hour, t.minute, t.second,
1440 t.weekday(),
1441 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
1442 -1))
1443 tt = t.timetuple()
1444 self.assertEqual(tt.tm_year, t.year)
1445 self.assertEqual(tt.tm_mon, t.month)
1446 self.assertEqual(tt.tm_mday, t.day)
1447 self.assertEqual(tt.tm_hour, t.hour)
1448 self.assertEqual(tt.tm_min, t.minute)
1449 self.assertEqual(tt.tm_sec, t.second)
1450 self.assertEqual(tt.tm_wday, t.weekday())
1451 self.assertEqual(tt.tm_yday, t.toordinal() -
1452 date(t.year, 1, 1).toordinal() + 1)
1453 self.assertEqual(tt.tm_isdst, -1)
1455 def test_more_strftime(self):
1456 # This tests fields beyond those tested by the TestDate.test_strftime.
1457 t = self.theclass(2004, 12, 31, 6, 22, 33)
1458 self.assertEqual(t.strftime("%m %d %y %S %M %H %j"),
1459 "12 31 04 33 22 06 366")
1461 def test_extract(self):
1462 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1463 self.assertEqual(dt.date(), date(2002, 3, 4))
1464 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
1466 def test_combine(self):
1467 d = date(2002, 3, 4)
1468 t = time(18, 45, 3, 1234)
1469 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1470 combine = self.theclass.combine
1471 dt = combine(d, t)
1472 self.assertEqual(dt, expected)
1474 dt = combine(time=t, date=d)
1475 self.assertEqual(dt, expected)
1477 self.assertEqual(d, dt.date())
1478 self.assertEqual(t, dt.time())
1479 self.assertEqual(dt, combine(dt.date(), dt.time()))
1481 self.assertRaises(TypeError, combine) # need an arg
1482 self.assertRaises(TypeError, combine, d) # need two args
1483 self.assertRaises(TypeError, combine, t, d) # args reversed
1484 self.assertRaises(TypeError, combine, d, t, 1) # too many args
1485 self.assertRaises(TypeError, combine, "date", "time") # wrong types
1487 def test_replace(self):
1488 cls = self.theclass
1489 args = [1, 2, 3, 4, 5, 6, 7]
1490 base = cls(*args)
1491 self.assertEqual(base, base.replace())
1493 i = 0
1494 for name, newval in (("year", 2),
1495 ("month", 3),
1496 ("day", 4),
1497 ("hour", 5),
1498 ("minute", 6),
1499 ("second", 7),
1500 ("microsecond", 8)):
1501 newargs = args[:]
1502 newargs[i] = newval
1503 expected = cls(*newargs)
1504 got = base.replace(**{name: newval})
1505 self.assertEqual(expected, got)
1506 i += 1
1508 # Out of bounds.
1509 base = cls(2000, 2, 29)
1510 self.assertRaises(ValueError, base.replace, year=2001)
1512 def test_astimezone(self):
1513 # Pretty boring! The TZ test is more interesting here. astimezone()
1514 # simply can't be applied to a naive object.
1515 dt = self.theclass.now()
1516 f = FixedOffset(44, "")
1517 self.assertRaises(TypeError, dt.astimezone) # not enough args
1518 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
1519 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
1520 self.assertRaises(ValueError, dt.astimezone, f) # naive
1521 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
1523 class Bogus(tzinfo):
1524 def utcoffset(self, dt): return None
1525 def dst(self, dt): return timedelta(0)
1526 bog = Bogus()
1527 self.assertRaises(ValueError, dt.astimezone, bog) # naive
1529 class AlsoBogus(tzinfo):
1530 def utcoffset(self, dt): return timedelta(0)
1531 def dst(self, dt): return None
1532 alsobog = AlsoBogus()
1533 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
1535 def test_subclass_datetime(self):
1537 class C(self.theclass):
1538 theAnswer = 42
1540 def __new__(cls, *args, **kws):
1541 temp = kws.copy()
1542 extra = temp.pop('extra')
1543 result = self.theclass.__new__(cls, *args, **temp)
1544 result.extra = extra
1545 return result
1547 def newmeth(self, start):
1548 return start + self.year + self.month + self.second
1550 args = 2003, 4, 14, 12, 13, 41
1552 dt1 = self.theclass(*args)
1553 dt2 = C(*args, **{'extra': 7})
1555 self.assertEqual(dt2.__class__, C)
1556 self.assertEqual(dt2.theAnswer, 42)
1557 self.assertEqual(dt2.extra, 7)
1558 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1559 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
1560 dt1.second - 7)
1562 class SubclassTime(time):
1563 sub_var = 1
1565 class TestTime(HarmlessMixedComparison):
1567 theclass = time
1569 def test_basic_attributes(self):
1570 t = self.theclass(12, 0)
1571 self.assertEqual(t.hour, 12)
1572 self.assertEqual(t.minute, 0)
1573 self.assertEqual(t.second, 0)
1574 self.assertEqual(t.microsecond, 0)
1576 def test_basic_attributes_nonzero(self):
1577 # Make sure all attributes are non-zero so bugs in
1578 # bit-shifting access show up.
1579 t = self.theclass(12, 59, 59, 8000)
1580 self.assertEqual(t.hour, 12)
1581 self.assertEqual(t.minute, 59)
1582 self.assertEqual(t.second, 59)
1583 self.assertEqual(t.microsecond, 8000)
1585 def test_roundtrip(self):
1586 t = self.theclass(1, 2, 3, 4)
1588 # Verify t -> string -> time identity.
1589 s = repr(t)
1590 self.failUnless(s.startswith('datetime.'))
1591 s = s[9:]
1592 t2 = eval(s)
1593 self.assertEqual(t, t2)
1595 # Verify identity via reconstructing from pieces.
1596 t2 = self.theclass(t.hour, t.minute, t.second,
1597 t.microsecond)
1598 self.assertEqual(t, t2)
1600 def test_comparing(self):
1601 args = [1, 2, 3, 4]
1602 t1 = self.theclass(*args)
1603 t2 = self.theclass(*args)
1604 self.failUnless(t1 == t2)
1605 self.failUnless(t1 <= t2)
1606 self.failUnless(t1 >= t2)
1607 self.failUnless(not t1 != t2)
1608 self.failUnless(not t1 < t2)
1609 self.failUnless(not t1 > t2)
1610 self.assertEqual(cmp(t1, t2), 0)
1611 self.assertEqual(cmp(t2, t1), 0)
1613 for i in range(len(args)):
1614 newargs = args[:]
1615 newargs[i] = args[i] + 1
1616 t2 = self.theclass(*newargs) # this is larger than t1
1617 self.failUnless(t1 < t2)
1618 self.failUnless(t2 > t1)
1619 self.failUnless(t1 <= t2)
1620 self.failUnless(t2 >= t1)
1621 self.failUnless(t1 != t2)
1622 self.failUnless(t2 != t1)
1623 self.failUnless(not t1 == t2)
1624 self.failUnless(not t2 == t1)
1625 self.failUnless(not t1 > t2)
1626 self.failUnless(not t2 < t1)
1627 self.failUnless(not t1 >= t2)
1628 self.failUnless(not t2 <= t1)
1629 self.assertEqual(cmp(t1, t2), -1)
1630 self.assertEqual(cmp(t2, t1), 1)
1632 for badarg in OTHERSTUFF:
1633 self.assertEqual(t1 == badarg, False)
1634 self.assertEqual(t1 != badarg, True)
1635 self.assertEqual(badarg == t1, False)
1636 self.assertEqual(badarg != t1, True)
1638 self.assertRaises(TypeError, lambda: t1 <= badarg)
1639 self.assertRaises(TypeError, lambda: t1 < badarg)
1640 self.assertRaises(TypeError, lambda: t1 > badarg)
1641 self.assertRaises(TypeError, lambda: t1 >= badarg)
1642 self.assertRaises(TypeError, lambda: badarg <= t1)
1643 self.assertRaises(TypeError, lambda: badarg < t1)
1644 self.assertRaises(TypeError, lambda: badarg > t1)
1645 self.assertRaises(TypeError, lambda: badarg >= t1)
1647 def test_bad_constructor_arguments(self):
1648 # bad hours
1649 self.theclass(0, 0) # no exception
1650 self.theclass(23, 0) # no exception
1651 self.assertRaises(ValueError, self.theclass, -1, 0)
1652 self.assertRaises(ValueError, self.theclass, 24, 0)
1653 # bad minutes
1654 self.theclass(23, 0) # no exception
1655 self.theclass(23, 59) # no exception
1656 self.assertRaises(ValueError, self.theclass, 23, -1)
1657 self.assertRaises(ValueError, self.theclass, 23, 60)
1658 # bad seconds
1659 self.theclass(23, 59, 0) # no exception
1660 self.theclass(23, 59, 59) # no exception
1661 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
1662 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
1663 # bad microseconds
1664 self.theclass(23, 59, 59, 0) # no exception
1665 self.theclass(23, 59, 59, 999999) # no exception
1666 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
1667 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
1669 def test_hash_equality(self):
1670 d = self.theclass(23, 30, 17)
1671 e = self.theclass(23, 30, 17)
1672 self.assertEqual(d, e)
1673 self.assertEqual(hash(d), hash(e))
1675 dic = {d: 1}
1676 dic[e] = 2
1677 self.assertEqual(len(dic), 1)
1678 self.assertEqual(dic[d], 2)
1679 self.assertEqual(dic[e], 2)
1681 d = self.theclass(0, 5, 17)
1682 e = self.theclass(0, 5, 17)
1683 self.assertEqual(d, e)
1684 self.assertEqual(hash(d), hash(e))
1686 dic = {d: 1}
1687 dic[e] = 2
1688 self.assertEqual(len(dic), 1)
1689 self.assertEqual(dic[d], 2)
1690 self.assertEqual(dic[e], 2)
1692 def test_isoformat(self):
1693 t = self.theclass(4, 5, 1, 123)
1694 self.assertEqual(t.isoformat(), "04:05:01.000123")
1695 self.assertEqual(t.isoformat(), str(t))
1697 t = self.theclass()
1698 self.assertEqual(t.isoformat(), "00:00:00")
1699 self.assertEqual(t.isoformat(), str(t))
1701 t = self.theclass(microsecond=1)
1702 self.assertEqual(t.isoformat(), "00:00:00.000001")
1703 self.assertEqual(t.isoformat(), str(t))
1705 t = self.theclass(microsecond=10)
1706 self.assertEqual(t.isoformat(), "00:00:00.000010")
1707 self.assertEqual(t.isoformat(), str(t))
1709 t = self.theclass(microsecond=100)
1710 self.assertEqual(t.isoformat(), "00:00:00.000100")
1711 self.assertEqual(t.isoformat(), str(t))
1713 t = self.theclass(microsecond=1000)
1714 self.assertEqual(t.isoformat(), "00:00:00.001000")
1715 self.assertEqual(t.isoformat(), str(t))
1717 t = self.theclass(microsecond=10000)
1718 self.assertEqual(t.isoformat(), "00:00:00.010000")
1719 self.assertEqual(t.isoformat(), str(t))
1721 t = self.theclass(microsecond=100000)
1722 self.assertEqual(t.isoformat(), "00:00:00.100000")
1723 self.assertEqual(t.isoformat(), str(t))
1725 def test_strftime(self):
1726 t = self.theclass(1, 2, 3, 4)
1727 self.assertEqual(t.strftime('%H %M %S'), "01 02 03")
1728 # A naive object replaces %z and %Z with empty strings.
1729 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1731 def test_str(self):
1732 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
1733 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
1734 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
1735 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
1736 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
1738 def test_repr(self):
1739 name = 'datetime.' + self.theclass.__name__
1740 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
1741 "%s(1, 2, 3, 4)" % name)
1742 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
1743 "%s(10, 2, 3, 4000)" % name)
1744 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
1745 "%s(0, 2, 3, 400000)" % name)
1746 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
1747 "%s(12, 2, 3)" % name)
1748 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
1749 "%s(23, 15)" % name)
1751 def test_resolution_info(self):
1752 self.assert_(isinstance(self.theclass.min, self.theclass))
1753 self.assert_(isinstance(self.theclass.max, self.theclass))
1754 self.assert_(isinstance(self.theclass.resolution, timedelta))
1755 self.assert_(self.theclass.max > self.theclass.min)
1757 def test_pickling(self):
1758 args = 20, 59, 16, 64**2
1759 orig = self.theclass(*args)
1760 for pickler, unpickler, proto in pickle_choices:
1761 green = pickler.dumps(orig, proto)
1762 derived = unpickler.loads(green)
1763 self.assertEqual(orig, derived)
1765 def test_pickling_subclass_time(self):
1766 args = 20, 59, 16, 64**2
1767 orig = SubclassTime(*args)
1768 for pickler, unpickler, proto in pickle_choices:
1769 green = pickler.dumps(orig, proto)
1770 derived = unpickler.loads(green)
1771 self.assertEqual(orig, derived)
1773 def test_bool(self):
1774 cls = self.theclass
1775 self.failUnless(cls(1))
1776 self.failUnless(cls(0, 1))
1777 self.failUnless(cls(0, 0, 1))
1778 self.failUnless(cls(0, 0, 0, 1))
1779 self.failUnless(not cls(0))
1780 self.failUnless(not cls())
1782 def test_replace(self):
1783 cls = self.theclass
1784 args = [1, 2, 3, 4]
1785 base = cls(*args)
1786 self.assertEqual(base, base.replace())
1788 i = 0
1789 for name, newval in (("hour", 5),
1790 ("minute", 6),
1791 ("second", 7),
1792 ("microsecond", 8)):
1793 newargs = args[:]
1794 newargs[i] = newval
1795 expected = cls(*newargs)
1796 got = base.replace(**{name: newval})
1797 self.assertEqual(expected, got)
1798 i += 1
1800 # Out of bounds.
1801 base = cls(1)
1802 self.assertRaises(ValueError, base.replace, hour=24)
1803 self.assertRaises(ValueError, base.replace, minute=-1)
1804 self.assertRaises(ValueError, base.replace, second=100)
1805 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1807 def test_subclass_time(self):
1809 class C(self.theclass):
1810 theAnswer = 42
1812 def __new__(cls, *args, **kws):
1813 temp = kws.copy()
1814 extra = temp.pop('extra')
1815 result = self.theclass.__new__(cls, *args, **temp)
1816 result.extra = extra
1817 return result
1819 def newmeth(self, start):
1820 return start + self.hour + self.second
1822 args = 4, 5, 6
1824 dt1 = self.theclass(*args)
1825 dt2 = C(*args, **{'extra': 7})
1827 self.assertEqual(dt2.__class__, C)
1828 self.assertEqual(dt2.theAnswer, 42)
1829 self.assertEqual(dt2.extra, 7)
1830 self.assertEqual(dt1.isoformat(), dt2.isoformat())
1831 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
1833 def test_backdoor_resistance(self):
1834 # see TestDate.test_backdoor_resistance().
1835 base = '2:59.0'
1836 for hour_byte in ' ', '9', chr(24), '\xff':
1837 self.assertRaises(TypeError, self.theclass,
1838 hour_byte + base[1:])
1840 # A mixin for classes with a tzinfo= argument. Subclasses must define
1841 # theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
1842 # must be legit (which is true for time and datetime).
1843 class TZInfoBase(unittest.TestCase):
1845 def test_argument_passing(self):
1846 cls = self.theclass
1847 # A datetime passes itself on, a time passes None.
1848 class introspective(tzinfo):
1849 def tzname(self, dt): return dt and "real" or "none"
1850 def utcoffset(self, dt):
1851 return timedelta(minutes = dt and 42 or -42)
1852 dst = utcoffset
1854 obj = cls(1, 2, 3, tzinfo=introspective())
1856 expected = cls is time and "none" or "real"
1857 self.assertEqual(obj.tzname(), expected)
1859 expected = timedelta(minutes=(cls is time and -42 or 42))
1860 self.assertEqual(obj.utcoffset(), expected)
1861 self.assertEqual(obj.dst(), expected)
1863 def test_bad_tzinfo_classes(self):
1864 cls = self.theclass
1865 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
1867 class NiceTry(object):
1868 def __init__(self): pass
1869 def utcoffset(self, dt): pass
1870 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
1872 class BetterTry(tzinfo):
1873 def __init__(self): pass
1874 def utcoffset(self, dt): pass
1875 b = BetterTry()
1876 t = cls(1, 1, 1, tzinfo=b)
1877 self.failUnless(t.tzinfo is b)
1879 def test_utc_offset_out_of_bounds(self):
1880 class Edgy(tzinfo):
1881 def __init__(self, offset):
1882 self.offset = timedelta(minutes=offset)
1883 def utcoffset(self, dt):
1884 return self.offset
1886 cls = self.theclass
1887 for offset, legit in ((-1440, False),
1888 (-1439, True),
1889 (1439, True),
1890 (1440, False)):
1891 if cls is time:
1892 t = cls(1, 2, 3, tzinfo=Edgy(offset))
1893 elif cls is datetime:
1894 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
1895 else:
1896 assert 0, "impossible"
1897 if legit:
1898 aofs = abs(offset)
1899 h, m = divmod(aofs, 60)
1900 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
1901 if isinstance(t, datetime):
1902 t = t.timetz()
1903 self.assertEqual(str(t), "01:02:03" + tag)
1904 else:
1905 self.assertRaises(ValueError, str, t)
1907 def test_tzinfo_classes(self):
1908 cls = self.theclass
1909 class C1(tzinfo):
1910 def utcoffset(self, dt): return None
1911 def dst(self, dt): return None
1912 def tzname(self, dt): return None
1913 for t in (cls(1, 1, 1),
1914 cls(1, 1, 1, tzinfo=None),
1915 cls(1, 1, 1, tzinfo=C1())):
1916 self.failUnless(t.utcoffset() is None)
1917 self.failUnless(t.dst() is None)
1918 self.failUnless(t.tzname() is None)
1920 class C3(tzinfo):
1921 def utcoffset(self, dt): return timedelta(minutes=-1439)
1922 def dst(self, dt): return timedelta(minutes=1439)
1923 def tzname(self, dt): return "aname"
1924 t = cls(1, 1, 1, tzinfo=C3())
1925 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
1926 self.assertEqual(t.dst(), timedelta(minutes=1439))
1927 self.assertEqual(t.tzname(), "aname")
1929 # Wrong types.
1930 class C4(tzinfo):
1931 def utcoffset(self, dt): return "aname"
1932 def dst(self, dt): return 7
1933 def tzname(self, dt): return 0
1934 t = cls(1, 1, 1, tzinfo=C4())
1935 self.assertRaises(TypeError, t.utcoffset)
1936 self.assertRaises(TypeError, t.dst)
1937 self.assertRaises(TypeError, t.tzname)
1939 # Offset out of range.
1940 class C6(tzinfo):
1941 def utcoffset(self, dt): return timedelta(hours=-24)
1942 def dst(self, dt): return timedelta(hours=24)
1943 t = cls(1, 1, 1, tzinfo=C6())
1944 self.assertRaises(ValueError, t.utcoffset)
1945 self.assertRaises(ValueError, t.dst)
1947 # Not a whole number of minutes.
1948 class C7(tzinfo):
1949 def utcoffset(self, dt): return timedelta(seconds=61)
1950 def dst(self, dt): return timedelta(microseconds=-81)
1951 t = cls(1, 1, 1, tzinfo=C7())
1952 self.assertRaises(ValueError, t.utcoffset)
1953 self.assertRaises(ValueError, t.dst)
1955 def test_aware_compare(self):
1956 cls = self.theclass
1958 # Ensure that utcoffset() gets ignored if the comparands have
1959 # the same tzinfo member.
1960 class OperandDependentOffset(tzinfo):
1961 def utcoffset(self, t):
1962 if t.minute < 10:
1963 # d0 and d1 equal after adjustment
1964 return timedelta(minutes=t.minute)
1965 else:
1966 # d2 off in the weeds
1967 return timedelta(minutes=59)
1969 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
1970 d0 = base.replace(minute=3)
1971 d1 = base.replace(minute=9)
1972 d2 = base.replace(minute=11)
1973 for x in d0, d1, d2:
1974 for y in d0, d1, d2:
1975 got = cmp(x, y)
1976 expected = cmp(x.minute, y.minute)
1977 self.assertEqual(got, expected)
1979 # However, if they're different members, uctoffset is not ignored.
1980 # Note that a time can't actually have an operand-depedent offset,
1981 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
1982 # so skip this test for time.
1983 if cls is not time:
1984 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
1985 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
1986 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
1987 for x in d0, d1, d2:
1988 for y in d0, d1, d2:
1989 got = cmp(x, y)
1990 if (x is d0 or x is d1) and (y is d0 or y is d1):
1991 expected = 0
1992 elif x is y is d2:
1993 expected = 0
1994 elif x is d2:
1995 expected = -1
1996 else:
1997 assert y is d2
1998 expected = 1
1999 self.assertEqual(got, expected)
2002 # Testing time objects with a non-None tzinfo.
2003 class TestTimeTZ(TestTime, TZInfoBase):
2004 theclass = time
2006 def test_empty(self):
2007 t = self.theclass()
2008 self.assertEqual(t.hour, 0)
2009 self.assertEqual(t.minute, 0)
2010 self.assertEqual(t.second, 0)
2011 self.assertEqual(t.microsecond, 0)
2012 self.failUnless(t.tzinfo is None)
2014 def test_zones(self):
2015 est = FixedOffset(-300, "EST", 1)
2016 utc = FixedOffset(0, "UTC", -2)
2017 met = FixedOffset(60, "MET", 3)
2018 t1 = time( 7, 47, tzinfo=est)
2019 t2 = time(12, 47, tzinfo=utc)
2020 t3 = time(13, 47, tzinfo=met)
2021 t4 = time(microsecond=40)
2022 t5 = time(microsecond=40, tzinfo=utc)
2024 self.assertEqual(t1.tzinfo, est)
2025 self.assertEqual(t2.tzinfo, utc)
2026 self.assertEqual(t3.tzinfo, met)
2027 self.failUnless(t4.tzinfo is None)
2028 self.assertEqual(t5.tzinfo, utc)
2030 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2031 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2032 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
2033 self.failUnless(t4.utcoffset() is None)
2034 self.assertRaises(TypeError, t1.utcoffset, "no args")
2036 self.assertEqual(t1.tzname(), "EST")
2037 self.assertEqual(t2.tzname(), "UTC")
2038 self.assertEqual(t3.tzname(), "MET")
2039 self.failUnless(t4.tzname() is None)
2040 self.assertRaises(TypeError, t1.tzname, "no args")
2042 self.assertEqual(t1.dst(), timedelta(minutes=1))
2043 self.assertEqual(t2.dst(), timedelta(minutes=-2))
2044 self.assertEqual(t3.dst(), timedelta(minutes=3))
2045 self.failUnless(t4.dst() is None)
2046 self.assertRaises(TypeError, t1.dst, "no args")
2048 self.assertEqual(hash(t1), hash(t2))
2049 self.assertEqual(hash(t1), hash(t3))
2050 self.assertEqual(hash(t2), hash(t3))
2052 self.assertEqual(t1, t2)
2053 self.assertEqual(t1, t3)
2054 self.assertEqual(t2, t3)
2055 self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
2056 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
2057 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
2059 self.assertEqual(str(t1), "07:47:00-05:00")
2060 self.assertEqual(str(t2), "12:47:00+00:00")
2061 self.assertEqual(str(t3), "13:47:00+01:00")
2062 self.assertEqual(str(t4), "00:00:00.000040")
2063 self.assertEqual(str(t5), "00:00:00.000040+00:00")
2065 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
2066 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
2067 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
2068 self.assertEqual(t4.isoformat(), "00:00:00.000040")
2069 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
2071 d = 'datetime.time'
2072 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
2073 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
2074 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
2075 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
2076 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
2078 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
2079 "07:47:00 %Z=EST %z=-0500")
2080 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
2081 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
2083 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
2084 t1 = time(23, 59, tzinfo=yuck)
2085 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
2086 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
2088 # Check that an invalid tzname result raises an exception.
2089 class Badtzname(tzinfo):
2090 def tzname(self, dt): return 42
2091 t = time(2, 3, 4, tzinfo=Badtzname())
2092 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
2093 self.assertRaises(TypeError, t.strftime, "%Z")
2095 def test_hash_edge_cases(self):
2096 # Offsets that overflow a basic time.
2097 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2098 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2099 self.assertEqual(hash(t1), hash(t2))
2101 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2102 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2103 self.assertEqual(hash(t1), hash(t2))
2105 def test_pickling(self):
2106 # Try one without a tzinfo.
2107 args = 20, 59, 16, 64**2
2108 orig = self.theclass(*args)
2109 for pickler, unpickler, proto in pickle_choices:
2110 green = pickler.dumps(orig, proto)
2111 derived = unpickler.loads(green)
2112 self.assertEqual(orig, derived)
2114 # Try one with a tzinfo.
2115 tinfo = PicklableFixedOffset(-300, 'cookie')
2116 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
2117 for pickler, unpickler, proto in pickle_choices:
2118 green = pickler.dumps(orig, proto)
2119 derived = unpickler.loads(green)
2120 self.assertEqual(orig, derived)
2121 self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
2122 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2123 self.assertEqual(derived.tzname(), 'cookie')
2125 def test_more_bool(self):
2126 # Test cases with non-None tzinfo.
2127 cls = self.theclass
2129 t = cls(0, tzinfo=FixedOffset(-300, ""))
2130 self.failUnless(t)
2132 t = cls(5, tzinfo=FixedOffset(-300, ""))
2133 self.failUnless(t)
2135 t = cls(5, tzinfo=FixedOffset(300, ""))
2136 self.failUnless(not t)
2138 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2139 self.failUnless(not t)
2141 # Mostly ensuring this doesn't overflow internally.
2142 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
2143 self.failUnless(t)
2145 # But this should yield a value error -- the utcoffset is bogus.
2146 t = cls(0, tzinfo=FixedOffset(24*60, ""))
2147 self.assertRaises(ValueError, lambda: bool(t))
2149 # Likewise.
2150 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
2151 self.assertRaises(ValueError, lambda: bool(t))
2153 def test_replace(self):
2154 cls = self.theclass
2155 z100 = FixedOffset(100, "+100")
2156 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2157 args = [1, 2, 3, 4, z100]
2158 base = cls(*args)
2159 self.assertEqual(base, base.replace())
2161 i = 0
2162 for name, newval in (("hour", 5),
2163 ("minute", 6),
2164 ("second", 7),
2165 ("microsecond", 8),
2166 ("tzinfo", zm200)):
2167 newargs = args[:]
2168 newargs[i] = newval
2169 expected = cls(*newargs)
2170 got = base.replace(**{name: newval})
2171 self.assertEqual(expected, got)
2172 i += 1
2174 # Ensure we can get rid of a tzinfo.
2175 self.assertEqual(base.tzname(), "+100")
2176 base2 = base.replace(tzinfo=None)
2177 self.failUnless(base2.tzinfo is None)
2178 self.failUnless(base2.tzname() is None)
2180 # Ensure we can add one.
2181 base3 = base2.replace(tzinfo=z100)
2182 self.assertEqual(base, base3)
2183 self.failUnless(base.tzinfo is base3.tzinfo)
2185 # Out of bounds.
2186 base = cls(1)
2187 self.assertRaises(ValueError, base.replace, hour=24)
2188 self.assertRaises(ValueError, base.replace, minute=-1)
2189 self.assertRaises(ValueError, base.replace, second=100)
2190 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2192 def test_mixed_compare(self):
2193 t1 = time(1, 2, 3)
2194 t2 = time(1, 2, 3)
2195 self.assertEqual(t1, t2)
2196 t2 = t2.replace(tzinfo=None)
2197 self.assertEqual(t1, t2)
2198 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2199 self.assertEqual(t1, t2)
2200 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2201 self.assertRaises(TypeError, lambda: t1 == t2)
2203 # In time w/ identical tzinfo objects, utcoffset is ignored.
2204 class Varies(tzinfo):
2205 def __init__(self):
2206 self.offset = timedelta(minutes=22)
2207 def utcoffset(self, t):
2208 self.offset += timedelta(minutes=1)
2209 return self.offset
2211 v = Varies()
2212 t1 = t2.replace(tzinfo=v)
2213 t2 = t2.replace(tzinfo=v)
2214 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2215 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2216 self.assertEqual(t1, t2)
2218 # But if they're not identical, it isn't ignored.
2219 t2 = t2.replace(tzinfo=Varies())
2220 self.failUnless(t1 < t2) # t1's offset counter still going up
2222 def test_subclass_timetz(self):
2224 class C(self.theclass):
2225 theAnswer = 42
2227 def __new__(cls, *args, **kws):
2228 temp = kws.copy()
2229 extra = temp.pop('extra')
2230 result = self.theclass.__new__(cls, *args, **temp)
2231 result.extra = extra
2232 return result
2234 def newmeth(self, start):
2235 return start + self.hour + self.second
2237 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2239 dt1 = self.theclass(*args)
2240 dt2 = C(*args, **{'extra': 7})
2242 self.assertEqual(dt2.__class__, C)
2243 self.assertEqual(dt2.theAnswer, 42)
2244 self.assertEqual(dt2.extra, 7)
2245 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2246 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2249 # Testing datetime objects with a non-None tzinfo.
2251 class TestDateTimeTZ(TestDateTime, TZInfoBase):
2252 theclass = datetime
2254 def test_trivial(self):
2255 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2256 self.assertEqual(dt.year, 1)
2257 self.assertEqual(dt.month, 2)
2258 self.assertEqual(dt.day, 3)
2259 self.assertEqual(dt.hour, 4)
2260 self.assertEqual(dt.minute, 5)
2261 self.assertEqual(dt.second, 6)
2262 self.assertEqual(dt.microsecond, 7)
2263 self.assertEqual(dt.tzinfo, None)
2265 def test_even_more_compare(self):
2266 # The test_compare() and test_more_compare() inherited from TestDate
2267 # and TestDateTime covered non-tzinfo cases.
2269 # Smallest possible after UTC adjustment.
2270 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2271 # Largest possible after UTC adjustment.
2272 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2273 tzinfo=FixedOffset(-1439, ""))
2275 # Make sure those compare correctly, and w/o overflow.
2276 self.failUnless(t1 < t2)
2277 self.failUnless(t1 != t2)
2278 self.failUnless(t2 > t1)
2280 self.failUnless(t1 == t1)
2281 self.failUnless(t2 == t2)
2283 # Equal afer adjustment.
2284 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2285 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2286 self.assertEqual(t1, t2)
2288 # Change t1 not to subtract a minute, and t1 should be larger.
2289 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2290 self.failUnless(t1 > t2)
2292 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2293 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2294 self.failUnless(t1 < t2)
2296 # Back to the original t1, but make seconds resolve it.
2297 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2298 second=1)
2299 self.failUnless(t1 > t2)
2301 # Likewise, but make microseconds resolve it.
2302 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2303 microsecond=1)
2304 self.failUnless(t1 > t2)
2306 # Make t2 naive and it should fail.
2307 t2 = self.theclass.min
2308 self.assertRaises(TypeError, lambda: t1 == t2)
2309 self.assertEqual(t2, t2)
2311 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2312 class Naive(tzinfo):
2313 def utcoffset(self, dt): return None
2314 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
2315 self.assertRaises(TypeError, lambda: t1 == t2)
2316 self.assertEqual(t2, t2)
2318 # OTOH, it's OK to compare two of these mixing the two ways of being
2319 # naive.
2320 t1 = self.theclass(5, 6, 7)
2321 self.assertEqual(t1, t2)
2323 # Try a bogus uctoffset.
2324 class Bogus(tzinfo):
2325 def utcoffset(self, dt):
2326 return timedelta(minutes=1440) # out of bounds
2327 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2328 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
2329 self.assertRaises(ValueError, lambda: t1 == t2)
2331 def test_pickling(self):
2332 # Try one without a tzinfo.
2333 args = 6, 7, 23, 20, 59, 1, 64**2
2334 orig = self.theclass(*args)
2335 for pickler, unpickler, proto in pickle_choices:
2336 green = pickler.dumps(orig, proto)
2337 derived = unpickler.loads(green)
2338 self.assertEqual(orig, derived)
2340 # Try one with a tzinfo.
2341 tinfo = PicklableFixedOffset(-300, 'cookie')
2342 orig = self.theclass(*args, **{'tzinfo': tinfo})
2343 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
2344 for pickler, unpickler, proto in pickle_choices:
2345 green = pickler.dumps(orig, proto)
2346 derived = unpickler.loads(green)
2347 self.assertEqual(orig, derived)
2348 self.failUnless(isinstance(derived.tzinfo,
2349 PicklableFixedOffset))
2350 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2351 self.assertEqual(derived.tzname(), 'cookie')
2353 def test_extreme_hashes(self):
2354 # If an attempt is made to hash these via subtracting the offset
2355 # then hashing a datetime object, OverflowError results. The
2356 # Python implementation used to blow up here.
2357 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2358 hash(t)
2359 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2360 tzinfo=FixedOffset(-1439, ""))
2361 hash(t)
2363 # OTOH, an OOB offset should blow up.
2364 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2365 self.assertRaises(ValueError, hash, t)
2367 def test_zones(self):
2368 est = FixedOffset(-300, "EST")
2369 utc = FixedOffset(0, "UTC")
2370 met = FixedOffset(60, "MET")
2371 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2372 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2373 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
2374 self.assertEqual(t1.tzinfo, est)
2375 self.assertEqual(t2.tzinfo, utc)
2376 self.assertEqual(t3.tzinfo, met)
2377 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2378 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2379 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
2380 self.assertEqual(t1.tzname(), "EST")
2381 self.assertEqual(t2.tzname(), "UTC")
2382 self.assertEqual(t3.tzname(), "MET")
2383 self.assertEqual(hash(t1), hash(t2))
2384 self.assertEqual(hash(t1), hash(t3))
2385 self.assertEqual(hash(t2), hash(t3))
2386 self.assertEqual(t1, t2)
2387 self.assertEqual(t1, t3)
2388 self.assertEqual(t2, t3)
2389 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2390 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2391 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
2392 d = 'datetime.datetime(2002, 3, 19, '
2393 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2394 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2395 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2397 def test_combine(self):
2398 met = FixedOffset(60, "MET")
2399 d = date(2002, 3, 4)
2400 tz = time(18, 45, 3, 1234, tzinfo=met)
2401 dt = datetime.combine(d, tz)
2402 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
2403 tzinfo=met))
2405 def test_extract(self):
2406 met = FixedOffset(60, "MET")
2407 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2408 self.assertEqual(dt.date(), date(2002, 3, 4))
2409 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2410 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
2412 def test_tz_aware_arithmetic(self):
2413 import random
2415 now = self.theclass.now()
2416 tz55 = FixedOffset(-330, "west 5:30")
2417 timeaware = now.time().replace(tzinfo=tz55)
2418 nowaware = self.theclass.combine(now.date(), timeaware)
2419 self.failUnless(nowaware.tzinfo is tz55)
2420 self.assertEqual(nowaware.timetz(), timeaware)
2422 # Can't mix aware and non-aware.
2423 self.assertRaises(TypeError, lambda: now - nowaware)
2424 self.assertRaises(TypeError, lambda: nowaware - now)
2426 # And adding datetime's doesn't make sense, aware or not.
2427 self.assertRaises(TypeError, lambda: now + nowaware)
2428 self.assertRaises(TypeError, lambda: nowaware + now)
2429 self.assertRaises(TypeError, lambda: nowaware + nowaware)
2431 # Subtracting should yield 0.
2432 self.assertEqual(now - now, timedelta(0))
2433 self.assertEqual(nowaware - nowaware, timedelta(0))
2435 # Adding a delta should preserve tzinfo.
2436 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
2437 nowawareplus = nowaware + delta
2438 self.failUnless(nowaware.tzinfo is tz55)
2439 nowawareplus2 = delta + nowaware
2440 self.failUnless(nowawareplus2.tzinfo is tz55)
2441 self.assertEqual(nowawareplus, nowawareplus2)
2443 # that - delta should be what we started with, and that - what we
2444 # started with should be delta.
2445 diff = nowawareplus - delta
2446 self.failUnless(diff.tzinfo is tz55)
2447 self.assertEqual(nowaware, diff)
2448 self.assertRaises(TypeError, lambda: delta - nowawareplus)
2449 self.assertEqual(nowawareplus - nowaware, delta)
2451 # Make up a random timezone.
2452 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
2453 # Attach it to nowawareplus.
2454 nowawareplus = nowawareplus.replace(tzinfo=tzr)
2455 self.failUnless(nowawareplus.tzinfo is tzr)
2456 # Make sure the difference takes the timezone adjustments into account.
2457 got = nowaware - nowawareplus
2458 # Expected: (nowaware base - nowaware offset) -
2459 # (nowawareplus base - nowawareplus offset) =
2460 # (nowaware base - nowawareplus base) +
2461 # (nowawareplus offset - nowaware offset) =
2462 # -delta + nowawareplus offset - nowaware offset
2463 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
2464 self.assertEqual(got, expected)
2466 # Try max possible difference.
2467 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
2468 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2469 tzinfo=FixedOffset(-1439, "max"))
2470 maxdiff = max - min
2471 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
2472 timedelta(minutes=2*1439))
2474 def test_tzinfo_now(self):
2475 meth = self.theclass.now
2476 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2477 base = meth()
2478 # Try with and without naming the keyword.
2479 off42 = FixedOffset(42, "42")
2480 another = meth(off42)
2481 again = meth(tz=off42)
2482 self.failUnless(another.tzinfo is again.tzinfo)
2483 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
2484 # Bad argument with and w/o naming the keyword.
2485 self.assertRaises(TypeError, meth, 16)
2486 self.assertRaises(TypeError, meth, tzinfo=16)
2487 # Bad keyword name.
2488 self.assertRaises(TypeError, meth, tinfo=off42)
2489 # Too many args.
2490 self.assertRaises(TypeError, meth, off42, off42)
2492 # We don't know which time zone we're in, and don't have a tzinfo
2493 # class to represent it, so seeing whether a tz argument actually
2494 # does a conversion is tricky.
2495 weirdtz = FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0)
2496 utc = FixedOffset(0, "utc", 0)
2497 for dummy in range(3):
2498 now = datetime.now(weirdtz)
2499 self.failUnless(now.tzinfo is weirdtz)
2500 utcnow = datetime.utcnow().replace(tzinfo=utc)
2501 now2 = utcnow.astimezone(weirdtz)
2502 if abs(now - now2) < timedelta(seconds=30):
2503 break
2504 # Else the code is broken, or more than 30 seconds passed between
2505 # calls; assuming the latter, just try again.
2506 else:
2507 # Three strikes and we're out.
2508 self.fail("utcnow(), now(tz), or astimezone() may be broken")
2510 def test_tzinfo_fromtimestamp(self):
2511 import time
2512 meth = self.theclass.fromtimestamp
2513 ts = time.time()
2514 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2515 base = meth(ts)
2516 # Try with and without naming the keyword.
2517 off42 = FixedOffset(42, "42")
2518 another = meth(ts, off42)
2519 again = meth(ts, tz=off42)
2520 self.failUnless(another.tzinfo is again.tzinfo)
2521 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
2522 # Bad argument with and w/o naming the keyword.
2523 self.assertRaises(TypeError, meth, ts, 16)
2524 self.assertRaises(TypeError, meth, ts, tzinfo=16)
2525 # Bad keyword name.
2526 self.assertRaises(TypeError, meth, ts, tinfo=off42)
2527 # Too many args.
2528 self.assertRaises(TypeError, meth, ts, off42, off42)
2529 # Too few args.
2530 self.assertRaises(TypeError, meth)
2532 # Try to make sure tz= actually does some conversion.
2533 timestamp = 1000000000
2534 utcdatetime = datetime.utcfromtimestamp(timestamp)
2535 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
2536 # But on some flavor of Mac, it's nowhere near that. So we can't have
2537 # any idea here what time that actually is, we can only test that
2538 # relative changes match.
2539 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
2540 tz = FixedOffset(utcoffset, "tz", 0)
2541 expected = utcdatetime + utcoffset
2542 got = datetime.fromtimestamp(timestamp, tz)
2543 self.assertEqual(expected, got.replace(tzinfo=None))
2545 def test_tzinfo_utcnow(self):
2546 meth = self.theclass.utcnow
2547 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2548 base = meth()
2549 # Try with and without naming the keyword; for whatever reason,
2550 # utcnow() doesn't accept a tzinfo argument.
2551 off42 = FixedOffset(42, "42")
2552 self.assertRaises(TypeError, meth, off42)
2553 self.assertRaises(TypeError, meth, tzinfo=off42)
2555 def test_tzinfo_utcfromtimestamp(self):
2556 import time
2557 meth = self.theclass.utcfromtimestamp
2558 ts = time.time()
2559 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2560 base = meth(ts)
2561 # Try with and without naming the keyword; for whatever reason,
2562 # utcfromtimestamp() doesn't accept a tzinfo argument.
2563 off42 = FixedOffset(42, "42")
2564 self.assertRaises(TypeError, meth, ts, off42)
2565 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
2567 def test_tzinfo_timetuple(self):
2568 # TestDateTime tested most of this. datetime adds a twist to the
2569 # DST flag.
2570 class DST(tzinfo):
2571 def __init__(self, dstvalue):
2572 if isinstance(dstvalue, int):
2573 dstvalue = timedelta(minutes=dstvalue)
2574 self.dstvalue = dstvalue
2575 def dst(self, dt):
2576 return self.dstvalue
2578 cls = self.theclass
2579 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
2580 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
2581 t = d.timetuple()
2582 self.assertEqual(1, t.tm_year)
2583 self.assertEqual(1, t.tm_mon)
2584 self.assertEqual(1, t.tm_mday)
2585 self.assertEqual(10, t.tm_hour)
2586 self.assertEqual(20, t.tm_min)
2587 self.assertEqual(30, t.tm_sec)
2588 self.assertEqual(0, t.tm_wday)
2589 self.assertEqual(1, t.tm_yday)
2590 self.assertEqual(flag, t.tm_isdst)
2592 # dst() returns wrong type.
2593 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
2595 # dst() at the edge.
2596 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
2597 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
2599 # dst() out of range.
2600 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
2601 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
2603 def test_utctimetuple(self):
2604 class DST(tzinfo):
2605 def __init__(self, dstvalue):
2606 if isinstance(dstvalue, int):
2607 dstvalue = timedelta(minutes=dstvalue)
2608 self.dstvalue = dstvalue
2609 def dst(self, dt):
2610 return self.dstvalue
2612 cls = self.theclass
2613 # This can't work: DST didn't implement utcoffset.
2614 self.assertRaises(NotImplementedError,
2615 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
2617 class UOFS(DST):
2618 def __init__(self, uofs, dofs=None):
2619 DST.__init__(self, dofs)
2620 self.uofs = timedelta(minutes=uofs)
2621 def utcoffset(self, dt):
2622 return self.uofs
2624 # Ensure tm_isdst is 0 regardless of what dst() says: DST is never
2625 # in effect for a UTC time.
2626 for dstvalue in -33, 33, 0, None:
2627 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
2628 t = d.utctimetuple()
2629 self.assertEqual(d.year, t.tm_year)
2630 self.assertEqual(d.month, t.tm_mon)
2631 self.assertEqual(d.day, t.tm_mday)
2632 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
2633 self.assertEqual(13, t.tm_min)
2634 self.assertEqual(d.second, t.tm_sec)
2635 self.assertEqual(d.weekday(), t.tm_wday)
2636 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
2637 t.tm_yday)
2638 self.assertEqual(0, t.tm_isdst)
2640 # At the edges, UTC adjustment can normalize into years out-of-range
2641 # for a datetime object. Ensure that a correct timetuple is
2642 # created anyway.
2643 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
2644 # That goes back 1 minute less than a full day.
2645 t = tiny.utctimetuple()
2646 self.assertEqual(t.tm_year, MINYEAR-1)
2647 self.assertEqual(t.tm_mon, 12)
2648 self.assertEqual(t.tm_mday, 31)
2649 self.assertEqual(t.tm_hour, 0)
2650 self.assertEqual(t.tm_min, 1)
2651 self.assertEqual(t.tm_sec, 37)
2652 self.assertEqual(t.tm_yday, 366) # "year 0" is a leap year
2653 self.assertEqual(t.tm_isdst, 0)
2655 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
2656 # That goes forward 1 minute less than a full day.
2657 t = huge.utctimetuple()
2658 self.assertEqual(t.tm_year, MAXYEAR+1)
2659 self.assertEqual(t.tm_mon, 1)
2660 self.assertEqual(t.tm_mday, 1)
2661 self.assertEqual(t.tm_hour, 23)
2662 self.assertEqual(t.tm_min, 58)
2663 self.assertEqual(t.tm_sec, 37)
2664 self.assertEqual(t.tm_yday, 1)
2665 self.assertEqual(t.tm_isdst, 0)
2667 def test_tzinfo_isoformat(self):
2668 zero = FixedOffset(0, "+00:00")
2669 plus = FixedOffset(220, "+03:40")
2670 minus = FixedOffset(-231, "-03:51")
2671 unknown = FixedOffset(None, "")
2673 cls = self.theclass
2674 datestr = '0001-02-03'
2675 for ofs in None, zero, plus, minus, unknown:
2676 for us in 0, 987001:
2677 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
2678 timestr = '04:05:59' + (us and '.987001' or '')
2679 ofsstr = ofs is not None and d.tzname() or ''
2680 tailstr = timestr + ofsstr
2681 iso = d.isoformat()
2682 self.assertEqual(iso, datestr + 'T' + tailstr)
2683 self.assertEqual(iso, d.isoformat('T'))
2684 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
2685 self.assertEqual(str(d), datestr + ' ' + tailstr)
2687 def test_replace(self):
2688 cls = self.theclass
2689 z100 = FixedOffset(100, "+100")
2690 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2691 args = [1, 2, 3, 4, 5, 6, 7, z100]
2692 base = cls(*args)
2693 self.assertEqual(base, base.replace())
2695 i = 0
2696 for name, newval in (("year", 2),
2697 ("month", 3),
2698 ("day", 4),
2699 ("hour", 5),
2700 ("minute", 6),
2701 ("second", 7),
2702 ("microsecond", 8),
2703 ("tzinfo", zm200)):
2704 newargs = args[:]
2705 newargs[i] = newval
2706 expected = cls(*newargs)
2707 got = base.replace(**{name: newval})
2708 self.assertEqual(expected, got)
2709 i += 1
2711 # Ensure we can get rid of a tzinfo.
2712 self.assertEqual(base.tzname(), "+100")
2713 base2 = base.replace(tzinfo=None)
2714 self.failUnless(base2.tzinfo is None)
2715 self.failUnless(base2.tzname() is None)
2717 # Ensure we can add one.
2718 base3 = base2.replace(tzinfo=z100)
2719 self.assertEqual(base, base3)
2720 self.failUnless(base.tzinfo is base3.tzinfo)
2722 # Out of bounds.
2723 base = cls(2000, 2, 29)
2724 self.assertRaises(ValueError, base.replace, year=2001)
2726 def test_more_astimezone(self):
2727 # The inherited test_astimezone covered some trivial and error cases.
2728 fnone = FixedOffset(None, "None")
2729 f44m = FixedOffset(44, "44")
2730 fm5h = FixedOffset(-timedelta(hours=5), "m300")
2732 dt = self.theclass.now(tz=f44m)
2733 self.failUnless(dt.tzinfo is f44m)
2734 # Replacing with degenerate tzinfo raises an exception.
2735 self.assertRaises(ValueError, dt.astimezone, fnone)
2736 # Ditto with None tz.
2737 self.assertRaises(TypeError, dt.astimezone, None)
2738 # Replacing with same tzinfo makes no change.
2739 x = dt.astimezone(dt.tzinfo)
2740 self.failUnless(x.tzinfo is f44m)
2741 self.assertEqual(x.date(), dt.date())
2742 self.assertEqual(x.time(), dt.time())
2744 # Replacing with different tzinfo does adjust.
2745 got = dt.astimezone(fm5h)
2746 self.failUnless(got.tzinfo is fm5h)
2747 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
2748 expected = dt - dt.utcoffset() # in effect, convert to UTC
2749 expected += fm5h.utcoffset(dt) # and from there to local time
2750 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
2751 self.assertEqual(got.date(), expected.date())
2752 self.assertEqual(got.time(), expected.time())
2753 self.assertEqual(got.timetz(), expected.timetz())
2754 self.failUnless(got.tzinfo is expected.tzinfo)
2755 self.assertEqual(got, expected)
2757 def test_aware_subtract(self):
2758 cls = self.theclass
2760 # Ensure that utcoffset() is ignored when the operands have the
2761 # same tzinfo member.
2762 class OperandDependentOffset(tzinfo):
2763 def utcoffset(self, t):
2764 if t.minute < 10:
2765 # d0 and d1 equal after adjustment
2766 return timedelta(minutes=t.minute)
2767 else:
2768 # d2 off in the weeds
2769 return timedelta(minutes=59)
2771 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
2772 d0 = base.replace(minute=3)
2773 d1 = base.replace(minute=9)
2774 d2 = base.replace(minute=11)
2775 for x in d0, d1, d2:
2776 for y in d0, d1, d2:
2777 got = x - y
2778 expected = timedelta(minutes=x.minute - y.minute)
2779 self.assertEqual(got, expected)
2781 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
2782 # ignored.
2783 base = cls(8, 9, 10, 11, 12, 13, 14)
2784 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2785 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2786 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2787 for x in d0, d1, d2:
2788 for y in d0, d1, d2:
2789 got = x - y
2790 if (x is d0 or x is d1) and (y is d0 or y is d1):
2791 expected = timedelta(0)
2792 elif x is y is d2:
2793 expected = timedelta(0)
2794 elif x is d2:
2795 expected = timedelta(minutes=(11-59)-0)
2796 else:
2797 assert y is d2
2798 expected = timedelta(minutes=0-(11-59))
2799 self.assertEqual(got, expected)
2801 def test_mixed_compare(self):
2802 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
2803 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
2804 self.assertEqual(t1, t2)
2805 t2 = t2.replace(tzinfo=None)
2806 self.assertEqual(t1, t2)
2807 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2808 self.assertEqual(t1, t2)
2809 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2810 self.assertRaises(TypeError, lambda: t1 == t2)
2812 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
2813 class Varies(tzinfo):
2814 def __init__(self):
2815 self.offset = timedelta(minutes=22)
2816 def utcoffset(self, t):
2817 self.offset += timedelta(minutes=1)
2818 return self.offset
2820 v = Varies()
2821 t1 = t2.replace(tzinfo=v)
2822 t2 = t2.replace(tzinfo=v)
2823 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2824 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2825 self.assertEqual(t1, t2)
2827 # But if they're not identical, it isn't ignored.
2828 t2 = t2.replace(tzinfo=Varies())
2829 self.failUnless(t1 < t2) # t1's offset counter still going up
2831 def test_subclass_datetimetz(self):
2833 class C(self.theclass):
2834 theAnswer = 42
2836 def __new__(cls, *args, **kws):
2837 temp = kws.copy()
2838 extra = temp.pop('extra')
2839 result = self.theclass.__new__(cls, *args, **temp)
2840 result.extra = extra
2841 return result
2843 def newmeth(self, start):
2844 return start + self.hour + self.year
2846 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2848 dt1 = self.theclass(*args)
2849 dt2 = C(*args, **{'extra': 7})
2851 self.assertEqual(dt2.__class__, C)
2852 self.assertEqual(dt2.theAnswer, 42)
2853 self.assertEqual(dt2.extra, 7)
2854 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2855 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
2857 # Pain to set up DST-aware tzinfo classes.
2859 def first_sunday_on_or_after(dt):
2860 days_to_go = 6 - dt.weekday()
2861 if days_to_go:
2862 dt += timedelta(days_to_go)
2863 return dt
2865 ZERO = timedelta(0)
2866 HOUR = timedelta(hours=1)
2867 DAY = timedelta(days=1)
2868 # In the US, DST starts at 2am (standard time) on the first Sunday in April.
2869 DSTSTART = datetime(1, 4, 1, 2)
2870 # and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
2871 # which is the first Sunday on or after Oct 25. Because we view 1:MM as
2872 # being standard time on that day, there is no spelling in local time of
2873 # the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
2874 DSTEND = datetime(1, 10, 25, 1)
2876 class USTimeZone(tzinfo):
2878 def __init__(self, hours, reprname, stdname, dstname):
2879 self.stdoffset = timedelta(hours=hours)
2880 self.reprname = reprname
2881 self.stdname = stdname
2882 self.dstname = dstname
2884 def __repr__(self):
2885 return self.reprname
2887 def tzname(self, dt):
2888 if self.dst(dt):
2889 return self.dstname
2890 else:
2891 return self.stdname
2893 def utcoffset(self, dt):
2894 return self.stdoffset + self.dst(dt)
2896 def dst(self, dt):
2897 if dt is None or dt.tzinfo is None:
2898 # An exception instead may be sensible here, in one or more of
2899 # the cases.
2900 return ZERO
2901 assert dt.tzinfo is self
2903 # Find first Sunday in April.
2904 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
2905 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
2907 # Find last Sunday in October.
2908 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
2909 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
2911 # Can't compare naive to aware objects, so strip the timezone from
2912 # dt first.
2913 if start <= dt.replace(tzinfo=None) < end:
2914 return HOUR
2915 else:
2916 return ZERO
2918 Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
2919 Central = USTimeZone(-6, "Central", "CST", "CDT")
2920 Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
2921 Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
2922 utc_real = FixedOffset(0, "UTC", 0)
2923 # For better test coverage, we want another flavor of UTC that's west of
2924 # the Eastern and Pacific timezones.
2925 utc_fake = FixedOffset(-12*60, "UTCfake", 0)
2927 class TestTimezoneConversions(unittest.TestCase):
2928 # The DST switch times for 2002, in std time.
2929 dston = datetime(2002, 4, 7, 2)
2930 dstoff = datetime(2002, 10, 27, 1)
2932 theclass = datetime
2934 # Check a time that's inside DST.
2935 def checkinside(self, dt, tz, utc, dston, dstoff):
2936 self.assertEqual(dt.dst(), HOUR)
2938 # Conversion to our own timezone is always an identity.
2939 self.assertEqual(dt.astimezone(tz), dt)
2941 asutc = dt.astimezone(utc)
2942 there_and_back = asutc.astimezone(tz)
2944 # Conversion to UTC and back isn't always an identity here,
2945 # because there are redundant spellings (in local time) of
2946 # UTC time when DST begins: the clock jumps from 1:59:59
2947 # to 3:00:00, and a local time of 2:MM:SS doesn't really
2948 # make sense then. The classes above treat 2:MM:SS as
2949 # daylight time then (it's "after 2am"), really an alias
2950 # for 1:MM:SS standard time. The latter form is what
2951 # conversion back from UTC produces.
2952 if dt.date() == dston.date() and dt.hour == 2:
2953 # We're in the redundant hour, and coming back from
2954 # UTC gives the 1:MM:SS standard-time spelling.
2955 self.assertEqual(there_and_back + HOUR, dt)
2956 # Although during was considered to be in daylight
2957 # time, there_and_back is not.
2958 self.assertEqual(there_and_back.dst(), ZERO)
2959 # They're the same times in UTC.
2960 self.assertEqual(there_and_back.astimezone(utc),
2961 dt.astimezone(utc))
2962 else:
2963 # We're not in the redundant hour.
2964 self.assertEqual(dt, there_and_back)
2966 # Because we have a redundant spelling when DST begins, there is
2967 # (unforunately) an hour when DST ends that can't be spelled at all in
2968 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
2969 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
2970 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
2971 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
2972 # expressed in local time. Nevertheless, we want conversion back
2973 # from UTC to mimic the local clock's "repeat an hour" behavior.
2974 nexthour_utc = asutc + HOUR
2975 nexthour_tz = nexthour_utc.astimezone(tz)
2976 if dt.date() == dstoff.date() and dt.hour == 0:
2977 # We're in the hour before the last DST hour. The last DST hour
2978 # is ineffable. We want the conversion back to repeat 1:MM.
2979 self.assertEqual(nexthour_tz, dt.replace(hour=1))
2980 nexthour_utc += HOUR
2981 nexthour_tz = nexthour_utc.astimezone(tz)
2982 self.assertEqual(nexthour_tz, dt.replace(hour=1))
2983 else:
2984 self.assertEqual(nexthour_tz - dt, HOUR)
2986 # Check a time that's outside DST.
2987 def checkoutside(self, dt, tz, utc):
2988 self.assertEqual(dt.dst(), ZERO)
2990 # Conversion to our own timezone is always an identity.
2991 self.assertEqual(dt.astimezone(tz), dt)
2993 # Converting to UTC and back is an identity too.
2994 asutc = dt.astimezone(utc)
2995 there_and_back = asutc.astimezone(tz)
2996 self.assertEqual(dt, there_and_back)
2998 def convert_between_tz_and_utc(self, tz, utc):
2999 dston = self.dston.replace(tzinfo=tz)
3000 # Because 1:MM on the day DST ends is taken as being standard time,
3001 # there is no spelling in tz for the last hour of daylight time.
3002 # For purposes of the test, the last hour of DST is 0:MM, which is
3003 # taken as being daylight time (and 1:MM is taken as being standard
3004 # time).
3005 dstoff = self.dstoff.replace(tzinfo=tz)
3006 for delta in (timedelta(weeks=13),
3007 DAY,
3008 HOUR,
3009 timedelta(minutes=1),
3010 timedelta(microseconds=1)):
3012 self.checkinside(dston, tz, utc, dston, dstoff)
3013 for during in dston + delta, dstoff - delta:
3014 self.checkinside(during, tz, utc, dston, dstoff)
3016 self.checkoutside(dstoff, tz, utc)
3017 for outside in dston - delta, dstoff + delta:
3018 self.checkoutside(outside, tz, utc)
3020 def test_easy(self):
3021 # Despite the name of this test, the endcases are excruciating.
3022 self.convert_between_tz_and_utc(Eastern, utc_real)
3023 self.convert_between_tz_and_utc(Pacific, utc_real)
3024 self.convert_between_tz_and_utc(Eastern, utc_fake)
3025 self.convert_between_tz_and_utc(Pacific, utc_fake)
3026 # The next is really dancing near the edge. It works because
3027 # Pacific and Eastern are far enough apart that their "problem
3028 # hours" don't overlap.
3029 self.convert_between_tz_and_utc(Eastern, Pacific)
3030 self.convert_between_tz_and_utc(Pacific, Eastern)
3031 # OTOH, these fail! Don't enable them. The difficulty is that
3032 # the edge case tests assume that every hour is representable in
3033 # the "utc" class. This is always true for a fixed-offset tzinfo
3034 # class (lke utc_real and utc_fake), but not for Eastern or Central.
3035 # For these adjacent DST-aware time zones, the range of time offsets
3036 # tested ends up creating hours in the one that aren't representable
3037 # in the other. For the same reason, we would see failures in the
3038 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
3039 # offset deltas in convert_between_tz_and_utc().
3041 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
3042 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
3044 def test_tricky(self):
3045 # 22:00 on day before daylight starts.
3046 fourback = self.dston - timedelta(hours=4)
3047 ninewest = FixedOffset(-9*60, "-0900", 0)
3048 fourback = fourback.replace(tzinfo=ninewest)
3049 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
3050 # 2", we should get the 3 spelling.
3051 # If we plug 22:00 the day before into Eastern, it "looks like std
3052 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
3053 # to 22:00 lands on 2:00, which makes no sense in local time (the
3054 # local clock jumps from 1 to 3). The point here is to make sure we
3055 # get the 3 spelling.
3056 expected = self.dston.replace(hour=3)
3057 got = fourback.astimezone(Eastern).replace(tzinfo=None)
3058 self.assertEqual(expected, got)
3060 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
3061 # case we want the 1:00 spelling.
3062 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
3063 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
3064 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
3065 # spelling.
3066 expected = self.dston.replace(hour=1)
3067 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
3068 self.assertEqual(expected, got)
3070 # Now on the day DST ends, we want "repeat an hour" behavior.
3071 # UTC 4:MM 5:MM 6:MM 7:MM checking these
3072 # EST 23:MM 0:MM 1:MM 2:MM
3073 # EDT 0:MM 1:MM 2:MM 3:MM
3074 # wall 0:MM 1:MM 1:MM 2:MM against these
3075 for utc in utc_real, utc_fake:
3076 for tz in Eastern, Pacific:
3077 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
3078 # Convert that to UTC.
3079 first_std_hour -= tz.utcoffset(None)
3080 # Adjust for possibly fake UTC.
3081 asutc = first_std_hour + utc.utcoffset(None)
3082 # First UTC hour to convert; this is 4:00 when utc=utc_real &
3083 # tz=Eastern.
3084 asutcbase = asutc.replace(tzinfo=utc)
3085 for tzhour in (0, 1, 1, 2):
3086 expectedbase = self.dstoff.replace(hour=tzhour)
3087 for minute in 0, 30, 59:
3088 expected = expectedbase.replace(minute=minute)
3089 asutc = asutcbase.replace(minute=minute)
3090 astz = asutc.astimezone(tz)
3091 self.assertEqual(astz.replace(tzinfo=None), expected)
3092 asutcbase += HOUR
3095 def test_bogus_dst(self):
3096 class ok(tzinfo):
3097 def utcoffset(self, dt): return HOUR
3098 def dst(self, dt): return HOUR
3100 now = self.theclass.now().replace(tzinfo=utc_real)
3101 # Doesn't blow up.
3102 now.astimezone(ok())
3104 # Does blow up.
3105 class notok(ok):
3106 def dst(self, dt): return None
3107 self.assertRaises(ValueError, now.astimezone, notok())
3109 def test_fromutc(self):
3110 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
3111 now = datetime.utcnow().replace(tzinfo=utc_real)
3112 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3113 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
3114 enow = Eastern.fromutc(now) # doesn't blow up
3115 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
3116 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
3117 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
3119 # Always converts UTC to standard time.
3120 class FauxUSTimeZone(USTimeZone):
3121 def fromutc(self, dt):
3122 return dt + self.stdoffset
3123 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
3125 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
3126 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
3127 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
3129 # Check around DST start.
3130 start = self.dston.replace(hour=4, tzinfo=Eastern)
3131 fstart = start.replace(tzinfo=FEastern)
3132 for wall in 23, 0, 1, 3, 4, 5:
3133 expected = start.replace(hour=wall)
3134 if wall == 23:
3135 expected -= timedelta(days=1)
3136 got = Eastern.fromutc(start)
3137 self.assertEqual(expected, got)
3139 expected = fstart + FEastern.stdoffset
3140 got = FEastern.fromutc(fstart)
3141 self.assertEqual(expected, got)
3143 # Ensure astimezone() calls fromutc() too.
3144 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3145 self.assertEqual(expected, got)
3147 start += HOUR
3148 fstart += HOUR
3150 # Check around DST end.
3151 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
3152 fstart = start.replace(tzinfo=FEastern)
3153 for wall in 0, 1, 1, 2, 3, 4:
3154 expected = start.replace(hour=wall)
3155 got = Eastern.fromutc(start)
3156 self.assertEqual(expected, got)
3158 expected = fstart + FEastern.stdoffset
3159 got = FEastern.fromutc(fstart)
3160 self.assertEqual(expected, got)
3162 # Ensure astimezone() calls fromutc() too.
3163 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3164 self.assertEqual(expected, got)
3166 start += HOUR
3167 fstart += HOUR
3170 #############################################################################
3171 # oddballs
3173 class Oddballs(unittest.TestCase):
3175 def test_bug_1028306(self):
3176 # Trying to compare a date to a datetime should act like a mixed-
3177 # type comparison, despite that datetime is a subclass of date.
3178 as_date = date.today()
3179 as_datetime = datetime.combine(as_date, time())
3180 self.assert_(as_date != as_datetime)
3181 self.assert_(as_datetime != as_date)
3182 self.assert_(not as_date == as_datetime)
3183 self.assert_(not as_datetime == as_date)
3184 self.assertRaises(TypeError, lambda: as_date < as_datetime)
3185 self.assertRaises(TypeError, lambda: as_datetime < as_date)
3186 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
3187 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
3188 self.assertRaises(TypeError, lambda: as_date > as_datetime)
3189 self.assertRaises(TypeError, lambda: as_datetime > as_date)
3190 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
3191 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
3193 # Neverthelss, comparison should work with the base-class (date)
3194 # projection if use of a date method is forced.
3195 self.assert_(as_date.__eq__(as_datetime))
3196 different_day = (as_date.day + 1) % 20 + 1
3197 self.assert_(not as_date.__eq__(as_datetime.replace(day=
3198 different_day)))
3200 # And date should compare with other subclasses of date. If a
3201 # subclass wants to stop this, it's up to the subclass to do so.
3202 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
3203 self.assertEqual(as_date, date_sc)
3204 self.assertEqual(date_sc, as_date)
3206 # Ditto for datetimes.
3207 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
3208 as_date.day, 0, 0, 0)
3209 self.assertEqual(as_datetime, datetime_sc)
3210 self.assertEqual(datetime_sc, as_datetime)
3212 def test_suite():
3213 allsuites = [unittest.makeSuite(klass, 'test')
3214 for klass in (TestModule,
3215 TestTZInfo,
3216 TestTimeDelta,
3217 TestDateOnly,
3218 TestDate,
3219 TestDateTime,
3220 TestTime,
3221 TestTimeTZ,
3222 TestDateTimeTZ,
3223 TestTimezoneConversions,
3224 Oddballs,
3227 return unittest.TestSuite(allsuites)
3229 def test_main():
3230 import gc
3231 import sys
3233 thesuite = test_suite()
3234 lastrc = None
3235 while True:
3236 test_support.run_suite(thesuite)
3237 if 1: # change to 0, under a debug build, for some leak detection
3238 break
3239 gc.collect()
3240 if gc.garbage:
3241 raise SystemError("gc.garbage not empty after test run: %r" %
3242 gc.garbage)
3243 if hasattr(sys, 'gettotalrefcount'):
3244 thisrc = sys.gettotalrefcount()
3245 print >> sys.stderr, '*' * 10, 'total refs:', thisrc,
3246 if lastrc:
3247 print >> sys.stderr, 'delta:', thisrc - lastrc
3248 else:
3249 print >> sys.stderr
3250 lastrc = thisrc
3252 if __name__ == "__main__":
3253 test_main()