fix to work on python <= 2.1
[python/dscho.git] / Lib / test / test_datetime.py
blobc6dbb4895624f3451002b9dfb2bd7210a08122be
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 def from_td(td):
450 return T(td.days, td.seconds, td.microseconds)
451 from_td = staticmethod(from_td)
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 TestDate(HarmlessMixedComparison):
514 # Tests here should pass for both dates and datetimes, except for a
515 # few tests that TestDateTime overrides.
517 theclass = date
519 def test_basic_attributes(self):
520 dt = self.theclass(2002, 3, 1)
521 self.assertEqual(dt.year, 2002)
522 self.assertEqual(dt.month, 3)
523 self.assertEqual(dt.day, 1)
525 def test_roundtrip(self):
526 for dt in (self.theclass(1, 2, 3),
527 self.theclass.today()):
528 # Verify dt -> string -> date identity.
529 s = repr(dt)
530 self.failUnless(s.startswith('datetime.'))
531 s = s[9:]
532 dt2 = eval(s)
533 self.assertEqual(dt, dt2)
535 # Verify identity via reconstructing from pieces.
536 dt2 = self.theclass(dt.year, dt.month, dt.day)
537 self.assertEqual(dt, dt2)
539 def test_ordinal_conversions(self):
540 # Check some fixed values.
541 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
542 (1, 12, 31, 365),
543 (2, 1, 1, 366),
544 # first example from "Calendrical Calculations"
545 (1945, 11, 12, 710347)]:
546 d = self.theclass(y, m, d)
547 self.assertEqual(n, d.toordinal())
548 fromord = self.theclass.fromordinal(n)
549 self.assertEqual(d, fromord)
550 if hasattr(fromord, "hour"):
551 # if we're checking something fancier than a date, verify
552 # the extra fields have been zeroed out
553 self.assertEqual(fromord.hour, 0)
554 self.assertEqual(fromord.minute, 0)
555 self.assertEqual(fromord.second, 0)
556 self.assertEqual(fromord.microsecond, 0)
558 # Check first and last days of year spottily across the whole
559 # range of years supported.
560 for year in xrange(MINYEAR, MAXYEAR+1, 7):
561 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
562 d = self.theclass(year, 1, 1)
563 n = d.toordinal()
564 d2 = self.theclass.fromordinal(n)
565 self.assertEqual(d, d2)
566 # Verify that moving back a day gets to the end of year-1.
567 if year > 1:
568 d = self.theclass.fromordinal(n-1)
569 d2 = self.theclass(year-1, 12, 31)
570 self.assertEqual(d, d2)
571 self.assertEqual(d2.toordinal(), n-1)
573 # Test every day in a leap-year and a non-leap year.
574 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
575 for year, isleap in (2000, True), (2002, False):
576 n = self.theclass(year, 1, 1).toordinal()
577 for month, maxday in zip(range(1, 13), dim):
578 if month == 2 and isleap:
579 maxday += 1
580 for day in range(1, maxday+1):
581 d = self.theclass(year, month, day)
582 self.assertEqual(d.toordinal(), n)
583 self.assertEqual(d, self.theclass.fromordinal(n))
584 n += 1
586 def test_extreme_ordinals(self):
587 a = self.theclass.min
588 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
589 aord = a.toordinal()
590 b = a.fromordinal(aord)
591 self.assertEqual(a, b)
593 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
595 b = a + timedelta(days=1)
596 self.assertEqual(b.toordinal(), aord + 1)
597 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
599 a = self.theclass.max
600 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
601 aord = a.toordinal()
602 b = a.fromordinal(aord)
603 self.assertEqual(a, b)
605 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
607 b = a - timedelta(days=1)
608 self.assertEqual(b.toordinal(), aord - 1)
609 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
611 def test_bad_constructor_arguments(self):
612 # bad years
613 self.theclass(MINYEAR, 1, 1) # no exception
614 self.theclass(MAXYEAR, 1, 1) # no exception
615 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
616 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
617 # bad months
618 self.theclass(2000, 1, 1) # no exception
619 self.theclass(2000, 12, 1) # no exception
620 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
621 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
622 # bad days
623 self.theclass(2000, 2, 29) # no exception
624 self.theclass(2004, 2, 29) # no exception
625 self.theclass(2400, 2, 29) # no exception
626 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
627 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
628 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
629 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
630 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
631 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
633 def test_hash_equality(self):
634 d = self.theclass(2000, 12, 31)
635 # same thing
636 e = self.theclass(2000, 12, 31)
637 self.assertEqual(d, e)
638 self.assertEqual(hash(d), hash(e))
640 dic = {d: 1}
641 dic[e] = 2
642 self.assertEqual(len(dic), 1)
643 self.assertEqual(dic[d], 2)
644 self.assertEqual(dic[e], 2)
646 d = self.theclass(2001, 1, 1)
647 # same thing
648 e = self.theclass(2001, 1, 1)
649 self.assertEqual(d, e)
650 self.assertEqual(hash(d), hash(e))
652 dic = {d: 1}
653 dic[e] = 2
654 self.assertEqual(len(dic), 1)
655 self.assertEqual(dic[d], 2)
656 self.assertEqual(dic[e], 2)
658 def test_computations(self):
659 a = self.theclass(2002, 1, 31)
660 b = self.theclass(1956, 1, 31)
662 diff = a-b
663 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
664 self.assertEqual(diff.seconds, 0)
665 self.assertEqual(diff.microseconds, 0)
667 day = timedelta(1)
668 week = timedelta(7)
669 a = self.theclass(2002, 3, 2)
670 self.assertEqual(a + day, self.theclass(2002, 3, 3))
671 self.assertEqual(day + a, self.theclass(2002, 3, 3))
672 self.assertEqual(a - day, self.theclass(2002, 3, 1))
673 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
674 self.assertEqual(a + week, self.theclass(2002, 3, 9))
675 self.assertEqual(a - week, self.theclass(2002, 2, 23))
676 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
677 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
678 self.assertEqual((a + week) - a, week)
679 self.assertEqual((a + day) - a, day)
680 self.assertEqual((a - week) - a, -week)
681 self.assertEqual((a - day) - a, -day)
682 self.assertEqual(a - (a + week), -week)
683 self.assertEqual(a - (a + day), -day)
684 self.assertEqual(a - (a - week), week)
685 self.assertEqual(a - (a - day), day)
687 # Add/sub ints, longs, floats should be illegal
688 for i in 1, 1L, 1.0:
689 self.assertRaises(TypeError, lambda: a+i)
690 self.assertRaises(TypeError, lambda: a-i)
691 self.assertRaises(TypeError, lambda: i+a)
692 self.assertRaises(TypeError, lambda: i-a)
694 # delta - date is senseless.
695 self.assertRaises(TypeError, lambda: day - a)
696 # mixing date and (delta or date) via * or // is senseless
697 self.assertRaises(TypeError, lambda: day * a)
698 self.assertRaises(TypeError, lambda: a * day)
699 self.assertRaises(TypeError, lambda: day // a)
700 self.assertRaises(TypeError, lambda: a // day)
701 self.assertRaises(TypeError, lambda: a * a)
702 self.assertRaises(TypeError, lambda: a // a)
703 # date + date is senseless
704 self.assertRaises(TypeError, lambda: a + a)
706 def test_overflow(self):
707 tiny = self.theclass.resolution
709 dt = self.theclass.min + tiny
710 dt -= tiny # no problem
711 self.assertRaises(OverflowError, dt.__sub__, tiny)
712 self.assertRaises(OverflowError, dt.__add__, -tiny)
714 dt = self.theclass.max - tiny
715 dt += tiny # no problem
716 self.assertRaises(OverflowError, dt.__add__, tiny)
717 self.assertRaises(OverflowError, dt.__sub__, -tiny)
719 def test_fromtimestamp(self):
720 import time
722 # Try an arbitrary fixed value.
723 year, month, day = 1999, 9, 19
724 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
725 d = self.theclass.fromtimestamp(ts)
726 self.assertEqual(d.year, year)
727 self.assertEqual(d.month, month)
728 self.assertEqual(d.day, day)
730 def test_today(self):
731 import time
733 # We claim that today() is like fromtimestamp(time.time()), so
734 # prove it.
735 for dummy in range(3):
736 today = self.theclass.today()
737 ts = time.time()
738 todayagain = self.theclass.fromtimestamp(ts)
739 if today == todayagain:
740 break
741 # There are several legit reasons that could fail:
742 # 1. It recently became midnight, between the today() and the
743 # time() calls.
744 # 2. The platform time() has such fine resolution that we'll
745 # never get the same value twice.
746 # 3. The platform time() has poor resolution, and we just
747 # happened to call today() right before a resolution quantum
748 # boundary.
749 # 4. The system clock got fiddled between calls.
750 # In any case, wait a little while and try again.
751 time.sleep(0.1)
753 # It worked or it didn't. If it didn't, assume it's reason #2, and
754 # let the test pass if they're within half a second of each other.
755 self.failUnless(today == todayagain or
756 abs(todayagain - today) < timedelta(seconds=0.5))
758 def test_weekday(self):
759 for i in range(7):
760 # March 4, 2002 is a Monday
761 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
762 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
763 # January 2, 1956 is a Monday
764 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
765 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
767 def test_isocalendar(self):
768 # Check examples from
769 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
770 for i in range(7):
771 d = self.theclass(2003, 12, 22+i)
772 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
773 d = self.theclass(2003, 12, 29) + timedelta(i)
774 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
775 d = self.theclass(2004, 1, 5+i)
776 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
777 d = self.theclass(2009, 12, 21+i)
778 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
779 d = self.theclass(2009, 12, 28) + timedelta(i)
780 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
781 d = self.theclass(2010, 1, 4+i)
782 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
784 def test_iso_long_years(self):
785 # Calculate long ISO years and compare to table from
786 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
787 ISO_LONG_YEARS_TABLE = """
788 4 32 60 88
789 9 37 65 93
790 15 43 71 99
791 20 48 76
792 26 54 82
794 105 133 161 189
795 111 139 167 195
796 116 144 172
797 122 150 178
798 128 156 184
800 201 229 257 285
801 207 235 263 291
802 212 240 268 296
803 218 246 274
804 224 252 280
806 303 331 359 387
807 308 336 364 392
808 314 342 370 398
809 320 348 376
810 325 353 381
812 iso_long_years = map(int, ISO_LONG_YEARS_TABLE.split())
813 iso_long_years.sort()
814 L = []
815 for i in range(400):
816 d = self.theclass(2000+i, 12, 31)
817 d1 = self.theclass(1600+i, 12, 31)
818 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
819 if d.isocalendar()[1] == 53:
820 L.append(i)
821 self.assertEqual(L, iso_long_years)
823 def test_isoformat(self):
824 t = self.theclass(2, 3, 2)
825 self.assertEqual(t.isoformat(), "0002-03-02")
827 def test_ctime(self):
828 t = self.theclass(2002, 3, 2)
829 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
831 def test_strftime(self):
832 t = self.theclass(2005, 3, 2)
833 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
834 self.assertEqual(t.strftime(""), "") # SF bug #761337
836 self.assertRaises(TypeError, t.strftime) # needs an arg
837 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
838 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
840 # A naive object replaces %z and %Z w/ empty strings.
841 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
843 def test_resolution_info(self):
844 self.assert_(isinstance(self.theclass.min, self.theclass))
845 self.assert_(isinstance(self.theclass.max, self.theclass))
846 self.assert_(isinstance(self.theclass.resolution, timedelta))
847 self.assert_(self.theclass.max > self.theclass.min)
849 def test_extreme_timedelta(self):
850 big = self.theclass.max - self.theclass.min
851 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
852 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
853 # n == 315537897599999999 ~= 2**58.13
854 justasbig = timedelta(0, 0, n)
855 self.assertEqual(big, justasbig)
856 self.assertEqual(self.theclass.min + big, self.theclass.max)
857 self.assertEqual(self.theclass.max - big, self.theclass.min)
859 def test_timetuple(self):
860 for i in range(7):
861 # January 2, 1956 is a Monday (0)
862 d = self.theclass(1956, 1, 2+i)
863 t = d.timetuple()
864 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
865 # February 1, 1956 is a Wednesday (2)
866 d = self.theclass(1956, 2, 1+i)
867 t = d.timetuple()
868 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
869 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
870 # of the year.
871 d = self.theclass(1956, 3, 1+i)
872 t = d.timetuple()
873 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
874 self.assertEqual(t.tm_year, 1956)
875 self.assertEqual(t.tm_mon, 3)
876 self.assertEqual(t.tm_mday, 1+i)
877 self.assertEqual(t.tm_hour, 0)
878 self.assertEqual(t.tm_min, 0)
879 self.assertEqual(t.tm_sec, 0)
880 self.assertEqual(t.tm_wday, (3+i)%7)
881 self.assertEqual(t.tm_yday, 61+i)
882 self.assertEqual(t.tm_isdst, -1)
884 def test_pickling(self):
885 args = 6, 7, 23
886 orig = self.theclass(*args)
887 for pickler, unpickler, proto in pickle_choices:
888 green = pickler.dumps(orig, proto)
889 derived = unpickler.loads(green)
890 self.assertEqual(orig, derived)
892 def test_compare(self):
893 t1 = self.theclass(2, 3, 4)
894 t2 = self.theclass(2, 3, 4)
895 self.failUnless(t1 == t2)
896 self.failUnless(t1 <= t2)
897 self.failUnless(t1 >= t2)
898 self.failUnless(not t1 != t2)
899 self.failUnless(not t1 < t2)
900 self.failUnless(not t1 > t2)
901 self.assertEqual(cmp(t1, t2), 0)
902 self.assertEqual(cmp(t2, t1), 0)
904 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
905 t2 = self.theclass(*args) # this is larger than t1
906 self.failUnless(t1 < t2)
907 self.failUnless(t2 > t1)
908 self.failUnless(t1 <= t2)
909 self.failUnless(t2 >= t1)
910 self.failUnless(t1 != t2)
911 self.failUnless(t2 != t1)
912 self.failUnless(not t1 == t2)
913 self.failUnless(not t2 == t1)
914 self.failUnless(not t1 > t2)
915 self.failUnless(not t2 < t1)
916 self.failUnless(not t1 >= t2)
917 self.failUnless(not t2 <= t1)
918 self.assertEqual(cmp(t1, t2), -1)
919 self.assertEqual(cmp(t2, t1), 1)
921 for badarg in OTHERSTUFF:
922 self.assertEqual(t1 == badarg, False)
923 self.assertEqual(t1 != badarg, True)
924 self.assertEqual(badarg == t1, False)
925 self.assertEqual(badarg != t1, True)
927 self.assertRaises(TypeError, lambda: t1 < badarg)
928 self.assertRaises(TypeError, lambda: t1 > badarg)
929 self.assertRaises(TypeError, lambda: t1 >= badarg)
930 self.assertRaises(TypeError, lambda: badarg <= t1)
931 self.assertRaises(TypeError, lambda: badarg < t1)
932 self.assertRaises(TypeError, lambda: badarg > t1)
933 self.assertRaises(TypeError, lambda: badarg >= t1)
935 def test_mixed_compare(self):
936 our = self.theclass(2000, 4, 5)
937 self.assertRaises(TypeError, cmp, our, 1)
938 self.assertRaises(TypeError, cmp, 1, our)
940 class AnotherDateTimeClass(object):
941 def __cmp__(self, other):
942 # Return "equal" so calling this can't be confused with
943 # compare-by-address (which never says "equal" for distinct
944 # objects).
945 return 0
947 # This still errors, because date and datetime comparison raise
948 # TypeError instead of NotImplemented when they don't know what to
949 # do, in order to stop comparison from falling back to the default
950 # compare-by-address.
951 their = AnotherDateTimeClass()
952 self.assertRaises(TypeError, cmp, our, their)
953 # Oops: The next stab raises TypeError in the C implementation,
954 # but not in the Python implementation of datetime. The difference
955 # is due to that the Python implementation defines __cmp__ but
956 # the C implementation defines tp_richcompare. This is more pain
957 # to fix than it's worth, so commenting out the test.
958 # self.assertEqual(cmp(their, our), 0)
960 # But date and datetime comparison return NotImplemented instead if the
961 # other object has a timetuple attr. This gives the other object a
962 # chance to do the comparison.
963 class Comparable(AnotherDateTimeClass):
964 def timetuple(self):
965 return ()
967 their = Comparable()
968 self.assertEqual(cmp(our, their), 0)
969 self.assertEqual(cmp(their, our), 0)
970 self.failUnless(our == their)
971 self.failUnless(their == our)
973 def test_bool(self):
974 # All dates are considered true.
975 self.failUnless(self.theclass.min)
976 self.failUnless(self.theclass.max)
978 def test_srftime_out_of_range(self):
979 # For nasty technical reasons, we can't handle years before 1900.
980 cls = self.theclass
981 self.assertEqual(cls(1900, 1, 1).strftime("%Y"), "1900")
982 for y in 1, 49, 51, 99, 100, 1000, 1899:
983 self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y")
985 def test_replace(self):
986 cls = self.theclass
987 args = [1, 2, 3]
988 base = cls(*args)
989 self.assertEqual(base, base.replace())
991 i = 0
992 for name, newval in (("year", 2),
993 ("month", 3),
994 ("day", 4)):
995 newargs = args[:]
996 newargs[i] = newval
997 expected = cls(*newargs)
998 got = base.replace(**{name: newval})
999 self.assertEqual(expected, got)
1000 i += 1
1002 # Out of bounds.
1003 base = cls(2000, 2, 29)
1004 self.assertRaises(ValueError, base.replace, year=2001)
1006 def test_subclass_date(self):
1008 class C(self.theclass):
1009 theAnswer = 42
1011 def __new__(cls, *args, **kws):
1012 temp = kws.copy()
1013 extra = temp.pop('extra')
1014 result = self.theclass.__new__(cls, *args, **temp)
1015 result.extra = extra
1016 return result
1018 def newmeth(self, start):
1019 return start + self.year + self.month
1021 args = 2003, 4, 14
1023 dt1 = self.theclass(*args)
1024 dt2 = C(*args, **{'extra': 7})
1026 self.assertEqual(dt2.__class__, C)
1027 self.assertEqual(dt2.theAnswer, 42)
1028 self.assertEqual(dt2.extra, 7)
1029 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1030 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1033 #############################################################################
1034 # datetime tests
1036 class TestDateTime(TestDate):
1038 theclass = datetime
1040 def test_basic_attributes(self):
1041 dt = self.theclass(2002, 3, 1, 12, 0)
1042 self.assertEqual(dt.year, 2002)
1043 self.assertEqual(dt.month, 3)
1044 self.assertEqual(dt.day, 1)
1045 self.assertEqual(dt.hour, 12)
1046 self.assertEqual(dt.minute, 0)
1047 self.assertEqual(dt.second, 0)
1048 self.assertEqual(dt.microsecond, 0)
1050 def test_basic_attributes_nonzero(self):
1051 # Make sure all attributes are non-zero so bugs in
1052 # bit-shifting access show up.
1053 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1054 self.assertEqual(dt.year, 2002)
1055 self.assertEqual(dt.month, 3)
1056 self.assertEqual(dt.day, 1)
1057 self.assertEqual(dt.hour, 12)
1058 self.assertEqual(dt.minute, 59)
1059 self.assertEqual(dt.second, 59)
1060 self.assertEqual(dt.microsecond, 8000)
1062 def test_roundtrip(self):
1063 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1064 self.theclass.now()):
1065 # Verify dt -> string -> datetime identity.
1066 s = repr(dt)
1067 self.failUnless(s.startswith('datetime.'))
1068 s = s[9:]
1069 dt2 = eval(s)
1070 self.assertEqual(dt, dt2)
1072 # Verify identity via reconstructing from pieces.
1073 dt2 = self.theclass(dt.year, dt.month, dt.day,
1074 dt.hour, dt.minute, dt.second,
1075 dt.microsecond)
1076 self.assertEqual(dt, dt2)
1078 def test_isoformat(self):
1079 t = self.theclass(2, 3, 2, 4, 5, 1, 123)
1080 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123")
1081 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123")
1082 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123")
1083 # str is ISO format with the separator forced to a blank.
1084 self.assertEqual(str(t), "0002-03-02 04:05:01.000123")
1086 t = self.theclass(2, 3, 2)
1087 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1088 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1089 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1090 # str is ISO format with the separator forced to a blank.
1091 self.assertEqual(str(t), "0002-03-02 00:00:00")
1093 def test_more_ctime(self):
1094 # Test fields that TestDate doesn't touch.
1095 import time
1097 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1098 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1099 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1100 # out. The difference is that t.ctime() produces " 2" for the day,
1101 # but platform ctime() produces "02" for the day. According to
1102 # C99, t.ctime() is correct here.
1103 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1105 # So test a case where that difference doesn't matter.
1106 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1107 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1109 def test_tz_independent_comparing(self):
1110 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1111 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1112 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1113 self.assertEqual(dt1, dt3)
1114 self.assert_(dt2 > dt3)
1116 # Make sure comparison doesn't forget microseconds, and isn't done
1117 # via comparing a float timestamp (an IEEE double doesn't have enough
1118 # precision to span microsecond resolution across years 1 thru 9999,
1119 # so comparing via timestamp necessarily calls some distinct values
1120 # equal).
1121 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1122 us = timedelta(microseconds=1)
1123 dt2 = dt1 + us
1124 self.assertEqual(dt2 - dt1, us)
1125 self.assert_(dt1 < dt2)
1127 def test_bad_constructor_arguments(self):
1128 # bad years
1129 self.theclass(MINYEAR, 1, 1) # no exception
1130 self.theclass(MAXYEAR, 1, 1) # no exception
1131 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1132 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1133 # bad months
1134 self.theclass(2000, 1, 1) # no exception
1135 self.theclass(2000, 12, 1) # no exception
1136 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1137 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1138 # bad days
1139 self.theclass(2000, 2, 29) # no exception
1140 self.theclass(2004, 2, 29) # no exception
1141 self.theclass(2400, 2, 29) # no exception
1142 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1143 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1144 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1145 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1146 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1147 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1148 # bad hours
1149 self.theclass(2000, 1, 31, 0) # no exception
1150 self.theclass(2000, 1, 31, 23) # no exception
1151 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1152 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1153 # bad minutes
1154 self.theclass(2000, 1, 31, 23, 0) # no exception
1155 self.theclass(2000, 1, 31, 23, 59) # no exception
1156 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1157 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1158 # bad seconds
1159 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1160 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1161 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1162 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1163 # bad microseconds
1164 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1165 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1166 self.assertRaises(ValueError, self.theclass,
1167 2000, 1, 31, 23, 59, 59, -1)
1168 self.assertRaises(ValueError, self.theclass,
1169 2000, 1, 31, 23, 59, 59,
1170 1000000)
1172 def test_hash_equality(self):
1173 d = self.theclass(2000, 12, 31, 23, 30, 17)
1174 e = self.theclass(2000, 12, 31, 23, 30, 17)
1175 self.assertEqual(d, e)
1176 self.assertEqual(hash(d), hash(e))
1178 dic = {d: 1}
1179 dic[e] = 2
1180 self.assertEqual(len(dic), 1)
1181 self.assertEqual(dic[d], 2)
1182 self.assertEqual(dic[e], 2)
1184 d = self.theclass(2001, 1, 1, 0, 5, 17)
1185 e = self.theclass(2001, 1, 1, 0, 5, 17)
1186 self.assertEqual(d, e)
1187 self.assertEqual(hash(d), hash(e))
1189 dic = {d: 1}
1190 dic[e] = 2
1191 self.assertEqual(len(dic), 1)
1192 self.assertEqual(dic[d], 2)
1193 self.assertEqual(dic[e], 2)
1195 def test_computations(self):
1196 a = self.theclass(2002, 1, 31)
1197 b = self.theclass(1956, 1, 31)
1198 diff = a-b
1199 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1200 self.assertEqual(diff.seconds, 0)
1201 self.assertEqual(diff.microseconds, 0)
1202 a = self.theclass(2002, 3, 2, 17, 6)
1203 millisec = timedelta(0, 0, 1000)
1204 hour = timedelta(0, 3600)
1205 day = timedelta(1)
1206 week = timedelta(7)
1207 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1208 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1209 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1210 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1211 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1212 self.assertEqual(a - hour, a + -hour)
1213 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1214 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1215 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1216 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1217 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1218 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1219 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1220 self.assertEqual((a + week) - a, week)
1221 self.assertEqual((a + day) - a, day)
1222 self.assertEqual((a + hour) - a, hour)
1223 self.assertEqual((a + millisec) - a, millisec)
1224 self.assertEqual((a - week) - a, -week)
1225 self.assertEqual((a - day) - a, -day)
1226 self.assertEqual((a - hour) - a, -hour)
1227 self.assertEqual((a - millisec) - a, -millisec)
1228 self.assertEqual(a - (a + week), -week)
1229 self.assertEqual(a - (a + day), -day)
1230 self.assertEqual(a - (a + hour), -hour)
1231 self.assertEqual(a - (a + millisec), -millisec)
1232 self.assertEqual(a - (a - week), week)
1233 self.assertEqual(a - (a - day), day)
1234 self.assertEqual(a - (a - hour), hour)
1235 self.assertEqual(a - (a - millisec), millisec)
1236 self.assertEqual(a + (week + day + hour + millisec),
1237 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1238 self.assertEqual(a + (week + day + hour + millisec),
1239 (((a + week) + day) + hour) + millisec)
1240 self.assertEqual(a - (week + day + hour + millisec),
1241 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1242 self.assertEqual(a - (week + day + hour + millisec),
1243 (((a - week) - day) - hour) - millisec)
1244 # Add/sub ints, longs, floats should be illegal
1245 for i in 1, 1L, 1.0:
1246 self.assertRaises(TypeError, lambda: a+i)
1247 self.assertRaises(TypeError, lambda: a-i)
1248 self.assertRaises(TypeError, lambda: i+a)
1249 self.assertRaises(TypeError, lambda: i-a)
1251 # delta - datetime is senseless.
1252 self.assertRaises(TypeError, lambda: day - a)
1253 # mixing datetime and (delta or datetime) via * or // is senseless
1254 self.assertRaises(TypeError, lambda: day * a)
1255 self.assertRaises(TypeError, lambda: a * day)
1256 self.assertRaises(TypeError, lambda: day // a)
1257 self.assertRaises(TypeError, lambda: a // day)
1258 self.assertRaises(TypeError, lambda: a * a)
1259 self.assertRaises(TypeError, lambda: a // a)
1260 # datetime + datetime is senseless
1261 self.assertRaises(TypeError, lambda: a + a)
1263 def test_pickling(self):
1264 args = 6, 7, 23, 20, 59, 1, 64**2
1265 orig = self.theclass(*args)
1266 for pickler, unpickler, proto in pickle_choices:
1267 green = pickler.dumps(orig, proto)
1268 derived = unpickler.loads(green)
1269 self.assertEqual(orig, derived)
1271 def test_more_pickling(self):
1272 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
1273 s = pickle.dumps(a)
1274 b = pickle.loads(s)
1275 self.assertEqual(b.year, 2003)
1276 self.assertEqual(b.month, 2)
1277 self.assertEqual(b.day, 7)
1279 def test_more_compare(self):
1280 # The test_compare() inherited from TestDate covers the error cases.
1281 # We just want to test lexicographic ordering on the members datetime
1282 # has that date lacks.
1283 args = [2000, 11, 29, 20, 58, 16, 999998]
1284 t1 = self.theclass(*args)
1285 t2 = self.theclass(*args)
1286 self.failUnless(t1 == t2)
1287 self.failUnless(t1 <= t2)
1288 self.failUnless(t1 >= t2)
1289 self.failUnless(not t1 != t2)
1290 self.failUnless(not t1 < t2)
1291 self.failUnless(not t1 > t2)
1292 self.assertEqual(cmp(t1, t2), 0)
1293 self.assertEqual(cmp(t2, t1), 0)
1295 for i in range(len(args)):
1296 newargs = args[:]
1297 newargs[i] = args[i] + 1
1298 t2 = self.theclass(*newargs) # this is larger than t1
1299 self.failUnless(t1 < t2)
1300 self.failUnless(t2 > t1)
1301 self.failUnless(t1 <= t2)
1302 self.failUnless(t2 >= t1)
1303 self.failUnless(t1 != t2)
1304 self.failUnless(t2 != t1)
1305 self.failUnless(not t1 == t2)
1306 self.failUnless(not t2 == t1)
1307 self.failUnless(not t1 > t2)
1308 self.failUnless(not t2 < t1)
1309 self.failUnless(not t1 >= t2)
1310 self.failUnless(not t2 <= t1)
1311 self.assertEqual(cmp(t1, t2), -1)
1312 self.assertEqual(cmp(t2, t1), 1)
1315 # A helper for timestamp constructor tests.
1316 def verify_field_equality(self, expected, got):
1317 self.assertEqual(expected.tm_year, got.year)
1318 self.assertEqual(expected.tm_mon, got.month)
1319 self.assertEqual(expected.tm_mday, got.day)
1320 self.assertEqual(expected.tm_hour, got.hour)
1321 self.assertEqual(expected.tm_min, got.minute)
1322 self.assertEqual(expected.tm_sec, got.second)
1324 def test_fromtimestamp(self):
1325 import time
1327 ts = time.time()
1328 expected = time.localtime(ts)
1329 got = self.theclass.fromtimestamp(ts)
1330 self.verify_field_equality(expected, got)
1332 def test_utcfromtimestamp(self):
1333 import time
1335 ts = time.time()
1336 expected = time.gmtime(ts)
1337 got = self.theclass.utcfromtimestamp(ts)
1338 self.verify_field_equality(expected, got)
1340 def test_utcnow(self):
1341 import time
1343 # Call it a success if utcnow() and utcfromtimestamp() are within
1344 # a second of each other.
1345 tolerance = timedelta(seconds=1)
1346 for dummy in range(3):
1347 from_now = self.theclass.utcnow()
1348 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1349 if abs(from_timestamp - from_now) <= tolerance:
1350 break
1351 # Else try again a few times.
1352 self.failUnless(abs(from_timestamp - from_now) <= tolerance)
1354 def test_more_timetuple(self):
1355 # This tests fields beyond those tested by the TestDate.test_timetuple.
1356 t = self.theclass(2004, 12, 31, 6, 22, 33)
1357 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
1358 self.assertEqual(t.timetuple(),
1359 (t.year, t.month, t.day,
1360 t.hour, t.minute, t.second,
1361 t.weekday(),
1362 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
1363 -1))
1364 tt = t.timetuple()
1365 self.assertEqual(tt.tm_year, t.year)
1366 self.assertEqual(tt.tm_mon, t.month)
1367 self.assertEqual(tt.tm_mday, t.day)
1368 self.assertEqual(tt.tm_hour, t.hour)
1369 self.assertEqual(tt.tm_min, t.minute)
1370 self.assertEqual(tt.tm_sec, t.second)
1371 self.assertEqual(tt.tm_wday, t.weekday())
1372 self.assertEqual(tt.tm_yday, t.toordinal() -
1373 date(t.year, 1, 1).toordinal() + 1)
1374 self.assertEqual(tt.tm_isdst, -1)
1376 def test_more_strftime(self):
1377 # This tests fields beyond those tested by the TestDate.test_strftime.
1378 t = self.theclass(2004, 12, 31, 6, 22, 33)
1379 self.assertEqual(t.strftime("%m %d %y %S %M %H %j"),
1380 "12 31 04 33 22 06 366")
1382 def test_extract(self):
1383 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1384 self.assertEqual(dt.date(), date(2002, 3, 4))
1385 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
1387 def test_combine(self):
1388 d = date(2002, 3, 4)
1389 t = time(18, 45, 3, 1234)
1390 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1391 combine = self.theclass.combine
1392 dt = combine(d, t)
1393 self.assertEqual(dt, expected)
1395 dt = combine(time=t, date=d)
1396 self.assertEqual(dt, expected)
1398 self.assertEqual(d, dt.date())
1399 self.assertEqual(t, dt.time())
1400 self.assertEqual(dt, combine(dt.date(), dt.time()))
1402 self.assertRaises(TypeError, combine) # need an arg
1403 self.assertRaises(TypeError, combine, d) # need two args
1404 self.assertRaises(TypeError, combine, t, d) # args reversed
1405 self.assertRaises(TypeError, combine, d, t, 1) # too many args
1406 self.assertRaises(TypeError, combine, "date", "time") # wrong types
1408 def test_replace(self):
1409 cls = self.theclass
1410 args = [1, 2, 3, 4, 5, 6, 7]
1411 base = cls(*args)
1412 self.assertEqual(base, base.replace())
1414 i = 0
1415 for name, newval in (("year", 2),
1416 ("month", 3),
1417 ("day", 4),
1418 ("hour", 5),
1419 ("minute", 6),
1420 ("second", 7),
1421 ("microsecond", 8)):
1422 newargs = args[:]
1423 newargs[i] = newval
1424 expected = cls(*newargs)
1425 got = base.replace(**{name: newval})
1426 self.assertEqual(expected, got)
1427 i += 1
1429 # Out of bounds.
1430 base = cls(2000, 2, 29)
1431 self.assertRaises(ValueError, base.replace, year=2001)
1433 def test_astimezone(self):
1434 # Pretty boring! The TZ test is more interesting here. astimezone()
1435 # simply can't be applied to a naive object.
1436 dt = self.theclass.now()
1437 f = FixedOffset(44, "")
1438 self.assertRaises(TypeError, dt.astimezone) # not enough args
1439 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
1440 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
1441 self.assertRaises(ValueError, dt.astimezone, f) # naive
1442 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
1444 class Bogus(tzinfo):
1445 def utcoffset(self, dt): return None
1446 def dst(self, dt): return timedelta(0)
1447 bog = Bogus()
1448 self.assertRaises(ValueError, dt.astimezone, bog) # naive
1450 class AlsoBogus(tzinfo):
1451 def utcoffset(self, dt): return timedelta(0)
1452 def dst(self, dt): return None
1453 alsobog = AlsoBogus()
1454 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
1456 def test_subclass_datetime(self):
1458 class C(self.theclass):
1459 theAnswer = 42
1461 def __new__(cls, *args, **kws):
1462 temp = kws.copy()
1463 extra = temp.pop('extra')
1464 result = self.theclass.__new__(cls, *args, **temp)
1465 result.extra = extra
1466 return result
1468 def newmeth(self, start):
1469 return start + self.year + self.month + self.second
1471 args = 2003, 4, 14, 12, 13, 41
1473 dt1 = self.theclass(*args)
1474 dt2 = C(*args, **{'extra': 7})
1476 self.assertEqual(dt2.__class__, C)
1477 self.assertEqual(dt2.theAnswer, 42)
1478 self.assertEqual(dt2.extra, 7)
1479 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1480 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
1481 dt1.second - 7)
1483 class TestTime(HarmlessMixedComparison):
1485 theclass = time
1487 def test_basic_attributes(self):
1488 t = self.theclass(12, 0)
1489 self.assertEqual(t.hour, 12)
1490 self.assertEqual(t.minute, 0)
1491 self.assertEqual(t.second, 0)
1492 self.assertEqual(t.microsecond, 0)
1494 def test_basic_attributes_nonzero(self):
1495 # Make sure all attributes are non-zero so bugs in
1496 # bit-shifting access show up.
1497 t = self.theclass(12, 59, 59, 8000)
1498 self.assertEqual(t.hour, 12)
1499 self.assertEqual(t.minute, 59)
1500 self.assertEqual(t.second, 59)
1501 self.assertEqual(t.microsecond, 8000)
1503 def test_roundtrip(self):
1504 t = self.theclass(1, 2, 3, 4)
1506 # Verify t -> string -> time identity.
1507 s = repr(t)
1508 self.failUnless(s.startswith('datetime.'))
1509 s = s[9:]
1510 t2 = eval(s)
1511 self.assertEqual(t, t2)
1513 # Verify identity via reconstructing from pieces.
1514 t2 = self.theclass(t.hour, t.minute, t.second,
1515 t.microsecond)
1516 self.assertEqual(t, t2)
1518 def test_comparing(self):
1519 args = [1, 2, 3, 4]
1520 t1 = self.theclass(*args)
1521 t2 = self.theclass(*args)
1522 self.failUnless(t1 == t2)
1523 self.failUnless(t1 <= t2)
1524 self.failUnless(t1 >= t2)
1525 self.failUnless(not t1 != t2)
1526 self.failUnless(not t1 < t2)
1527 self.failUnless(not t1 > t2)
1528 self.assertEqual(cmp(t1, t2), 0)
1529 self.assertEqual(cmp(t2, t1), 0)
1531 for i in range(len(args)):
1532 newargs = args[:]
1533 newargs[i] = args[i] + 1
1534 t2 = self.theclass(*newargs) # this is larger than t1
1535 self.failUnless(t1 < t2)
1536 self.failUnless(t2 > t1)
1537 self.failUnless(t1 <= t2)
1538 self.failUnless(t2 >= t1)
1539 self.failUnless(t1 != t2)
1540 self.failUnless(t2 != t1)
1541 self.failUnless(not t1 == t2)
1542 self.failUnless(not t2 == t1)
1543 self.failUnless(not t1 > t2)
1544 self.failUnless(not t2 < t1)
1545 self.failUnless(not t1 >= t2)
1546 self.failUnless(not t2 <= t1)
1547 self.assertEqual(cmp(t1, t2), -1)
1548 self.assertEqual(cmp(t2, t1), 1)
1550 for badarg in OTHERSTUFF:
1551 self.assertEqual(t1 == badarg, False)
1552 self.assertEqual(t1 != badarg, True)
1553 self.assertEqual(badarg == t1, False)
1554 self.assertEqual(badarg != t1, True)
1556 self.assertRaises(TypeError, lambda: t1 <= badarg)
1557 self.assertRaises(TypeError, lambda: t1 < badarg)
1558 self.assertRaises(TypeError, lambda: t1 > badarg)
1559 self.assertRaises(TypeError, lambda: t1 >= badarg)
1560 self.assertRaises(TypeError, lambda: badarg <= t1)
1561 self.assertRaises(TypeError, lambda: badarg < t1)
1562 self.assertRaises(TypeError, lambda: badarg > t1)
1563 self.assertRaises(TypeError, lambda: badarg >= t1)
1565 def test_bad_constructor_arguments(self):
1566 # bad hours
1567 self.theclass(0, 0) # no exception
1568 self.theclass(23, 0) # no exception
1569 self.assertRaises(ValueError, self.theclass, -1, 0)
1570 self.assertRaises(ValueError, self.theclass, 24, 0)
1571 # bad minutes
1572 self.theclass(23, 0) # no exception
1573 self.theclass(23, 59) # no exception
1574 self.assertRaises(ValueError, self.theclass, 23, -1)
1575 self.assertRaises(ValueError, self.theclass, 23, 60)
1576 # bad seconds
1577 self.theclass(23, 59, 0) # no exception
1578 self.theclass(23, 59, 59) # no exception
1579 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
1580 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
1581 # bad microseconds
1582 self.theclass(23, 59, 59, 0) # no exception
1583 self.theclass(23, 59, 59, 999999) # no exception
1584 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
1585 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
1587 def test_hash_equality(self):
1588 d = self.theclass(23, 30, 17)
1589 e = self.theclass(23, 30, 17)
1590 self.assertEqual(d, e)
1591 self.assertEqual(hash(d), hash(e))
1593 dic = {d: 1}
1594 dic[e] = 2
1595 self.assertEqual(len(dic), 1)
1596 self.assertEqual(dic[d], 2)
1597 self.assertEqual(dic[e], 2)
1599 d = self.theclass(0, 5, 17)
1600 e = self.theclass(0, 5, 17)
1601 self.assertEqual(d, e)
1602 self.assertEqual(hash(d), hash(e))
1604 dic = {d: 1}
1605 dic[e] = 2
1606 self.assertEqual(len(dic), 1)
1607 self.assertEqual(dic[d], 2)
1608 self.assertEqual(dic[e], 2)
1610 def test_isoformat(self):
1611 t = self.theclass(4, 5, 1, 123)
1612 self.assertEqual(t.isoformat(), "04:05:01.000123")
1613 self.assertEqual(t.isoformat(), str(t))
1615 t = self.theclass()
1616 self.assertEqual(t.isoformat(), "00:00:00")
1617 self.assertEqual(t.isoformat(), str(t))
1619 t = self.theclass(microsecond=1)
1620 self.assertEqual(t.isoformat(), "00:00:00.000001")
1621 self.assertEqual(t.isoformat(), str(t))
1623 t = self.theclass(microsecond=10)
1624 self.assertEqual(t.isoformat(), "00:00:00.000010")
1625 self.assertEqual(t.isoformat(), str(t))
1627 t = self.theclass(microsecond=100)
1628 self.assertEqual(t.isoformat(), "00:00:00.000100")
1629 self.assertEqual(t.isoformat(), str(t))
1631 t = self.theclass(microsecond=1000)
1632 self.assertEqual(t.isoformat(), "00:00:00.001000")
1633 self.assertEqual(t.isoformat(), str(t))
1635 t = self.theclass(microsecond=10000)
1636 self.assertEqual(t.isoformat(), "00:00:00.010000")
1637 self.assertEqual(t.isoformat(), str(t))
1639 t = self.theclass(microsecond=100000)
1640 self.assertEqual(t.isoformat(), "00:00:00.100000")
1641 self.assertEqual(t.isoformat(), str(t))
1643 def test_strftime(self):
1644 t = self.theclass(1, 2, 3, 4)
1645 self.assertEqual(t.strftime('%H %M %S'), "01 02 03")
1646 # A naive object replaces %z and %Z with empty strings.
1647 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1649 def test_str(self):
1650 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
1651 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
1652 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
1653 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
1654 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
1656 def test_repr(self):
1657 name = 'datetime.' + self.theclass.__name__
1658 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
1659 "%s(1, 2, 3, 4)" % name)
1660 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
1661 "%s(10, 2, 3, 4000)" % name)
1662 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
1663 "%s(0, 2, 3, 400000)" % name)
1664 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
1665 "%s(12, 2, 3)" % name)
1666 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
1667 "%s(23, 15)" % name)
1669 def test_resolution_info(self):
1670 self.assert_(isinstance(self.theclass.min, self.theclass))
1671 self.assert_(isinstance(self.theclass.max, self.theclass))
1672 self.assert_(isinstance(self.theclass.resolution, timedelta))
1673 self.assert_(self.theclass.max > self.theclass.min)
1675 def test_pickling(self):
1676 args = 20, 59, 16, 64**2
1677 orig = self.theclass(*args)
1678 for pickler, unpickler, proto in pickle_choices:
1679 green = pickler.dumps(orig, proto)
1680 derived = unpickler.loads(green)
1681 self.assertEqual(orig, derived)
1683 def test_bool(self):
1684 cls = self.theclass
1685 self.failUnless(cls(1))
1686 self.failUnless(cls(0, 1))
1687 self.failUnless(cls(0, 0, 1))
1688 self.failUnless(cls(0, 0, 0, 1))
1689 self.failUnless(not cls(0))
1690 self.failUnless(not cls())
1692 def test_replace(self):
1693 cls = self.theclass
1694 args = [1, 2, 3, 4]
1695 base = cls(*args)
1696 self.assertEqual(base, base.replace())
1698 i = 0
1699 for name, newval in (("hour", 5),
1700 ("minute", 6),
1701 ("second", 7),
1702 ("microsecond", 8)):
1703 newargs = args[:]
1704 newargs[i] = newval
1705 expected = cls(*newargs)
1706 got = base.replace(**{name: newval})
1707 self.assertEqual(expected, got)
1708 i += 1
1710 # Out of bounds.
1711 base = cls(1)
1712 self.assertRaises(ValueError, base.replace, hour=24)
1713 self.assertRaises(ValueError, base.replace, minute=-1)
1714 self.assertRaises(ValueError, base.replace, second=100)
1715 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1717 def test_subclass_time(self):
1719 class C(self.theclass):
1720 theAnswer = 42
1722 def __new__(cls, *args, **kws):
1723 temp = kws.copy()
1724 extra = temp.pop('extra')
1725 result = self.theclass.__new__(cls, *args, **temp)
1726 result.extra = extra
1727 return result
1729 def newmeth(self, start):
1730 return start + self.hour + self.second
1732 args = 4, 5, 6
1734 dt1 = self.theclass(*args)
1735 dt2 = C(*args, **{'extra': 7})
1737 self.assertEqual(dt2.__class__, C)
1738 self.assertEqual(dt2.theAnswer, 42)
1739 self.assertEqual(dt2.extra, 7)
1740 self.assertEqual(dt1.isoformat(), dt2.isoformat())
1741 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
1743 # A mixin for classes with a tzinfo= argument. Subclasses must define
1744 # theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
1745 # must be legit (which is true for time and datetime).
1746 class TZInfoBase(unittest.TestCase):
1748 def test_argument_passing(self):
1749 cls = self.theclass
1750 # A datetime passes itself on, a time passes None.
1751 class introspective(tzinfo):
1752 def tzname(self, dt): return dt and "real" or "none"
1753 def utcoffset(self, dt):
1754 return timedelta(minutes = dt and 42 or -42)
1755 dst = utcoffset
1757 obj = cls(1, 2, 3, tzinfo=introspective())
1759 expected = cls is time and "none" or "real"
1760 self.assertEqual(obj.tzname(), expected)
1762 expected = timedelta(minutes=(cls is time and -42 or 42))
1763 self.assertEqual(obj.utcoffset(), expected)
1764 self.assertEqual(obj.dst(), expected)
1766 def test_bad_tzinfo_classes(self):
1767 cls = self.theclass
1768 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
1770 class NiceTry(object):
1771 def __init__(self): pass
1772 def utcoffset(self, dt): pass
1773 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
1775 class BetterTry(tzinfo):
1776 def __init__(self): pass
1777 def utcoffset(self, dt): pass
1778 b = BetterTry()
1779 t = cls(1, 1, 1, tzinfo=b)
1780 self.failUnless(t.tzinfo is b)
1782 def test_utc_offset_out_of_bounds(self):
1783 class Edgy(tzinfo):
1784 def __init__(self, offset):
1785 self.offset = timedelta(minutes=offset)
1786 def utcoffset(self, dt):
1787 return self.offset
1789 cls = self.theclass
1790 for offset, legit in ((-1440, False),
1791 (-1439, True),
1792 (1439, True),
1793 (1440, False)):
1794 if cls is time:
1795 t = cls(1, 2, 3, tzinfo=Edgy(offset))
1796 elif cls is datetime:
1797 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
1798 else:
1799 assert 0, "impossible"
1800 if legit:
1801 aofs = abs(offset)
1802 h, m = divmod(aofs, 60)
1803 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
1804 if isinstance(t, datetime):
1805 t = t.timetz()
1806 self.assertEqual(str(t), "01:02:03" + tag)
1807 else:
1808 self.assertRaises(ValueError, str, t)
1810 def test_tzinfo_classes(self):
1811 cls = self.theclass
1812 class C1(tzinfo):
1813 def utcoffset(self, dt): return None
1814 def dst(self, dt): return None
1815 def tzname(self, dt): return None
1816 for t in (cls(1, 1, 1),
1817 cls(1, 1, 1, tzinfo=None),
1818 cls(1, 1, 1, tzinfo=C1())):
1819 self.failUnless(t.utcoffset() is None)
1820 self.failUnless(t.dst() is None)
1821 self.failUnless(t.tzname() is None)
1823 class C3(tzinfo):
1824 def utcoffset(self, dt): return timedelta(minutes=-1439)
1825 def dst(self, dt): return timedelta(minutes=1439)
1826 def tzname(self, dt): return "aname"
1827 t = cls(1, 1, 1, tzinfo=C3())
1828 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
1829 self.assertEqual(t.dst(), timedelta(minutes=1439))
1830 self.assertEqual(t.tzname(), "aname")
1832 # Wrong types.
1833 class C4(tzinfo):
1834 def utcoffset(self, dt): return "aname"
1835 def dst(self, dt): return 7
1836 def tzname(self, dt): return 0
1837 t = cls(1, 1, 1, tzinfo=C4())
1838 self.assertRaises(TypeError, t.utcoffset)
1839 self.assertRaises(TypeError, t.dst)
1840 self.assertRaises(TypeError, t.tzname)
1842 # Offset out of range.
1843 class C6(tzinfo):
1844 def utcoffset(self, dt): return timedelta(hours=-24)
1845 def dst(self, dt): return timedelta(hours=24)
1846 t = cls(1, 1, 1, tzinfo=C6())
1847 self.assertRaises(ValueError, t.utcoffset)
1848 self.assertRaises(ValueError, t.dst)
1850 # Not a whole number of minutes.
1851 class C7(tzinfo):
1852 def utcoffset(self, dt): return timedelta(seconds=61)
1853 def dst(self, dt): return timedelta(microseconds=-81)
1854 t = cls(1, 1, 1, tzinfo=C7())
1855 self.assertRaises(ValueError, t.utcoffset)
1856 self.assertRaises(ValueError, t.dst)
1858 def test_aware_compare(self):
1859 cls = self.theclass
1861 # Ensure that utcoffset() gets ignored if the comparands have
1862 # the same tzinfo member.
1863 class OperandDependentOffset(tzinfo):
1864 def utcoffset(self, t):
1865 if t.minute < 10:
1866 # d0 and d1 equal after adjustment
1867 return timedelta(minutes=t.minute)
1868 else:
1869 # d2 off in the weeds
1870 return timedelta(minutes=59)
1872 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
1873 d0 = base.replace(minute=3)
1874 d1 = base.replace(minute=9)
1875 d2 = base.replace(minute=11)
1876 for x in d0, d1, d2:
1877 for y in d0, d1, d2:
1878 got = cmp(x, y)
1879 expected = cmp(x.minute, y.minute)
1880 self.assertEqual(got, expected)
1882 # However, if they're different members, uctoffset is not ignored.
1883 # Note that a time can't actually have an operand-depedent offset,
1884 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
1885 # so skip this test for time.
1886 if cls is not time:
1887 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
1888 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
1889 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
1890 for x in d0, d1, d2:
1891 for y in d0, d1, d2:
1892 got = cmp(x, y)
1893 if (x is d0 or x is d1) and (y is d0 or y is d1):
1894 expected = 0
1895 elif x is y is d2:
1896 expected = 0
1897 elif x is d2:
1898 expected = -1
1899 else:
1900 assert y is d2
1901 expected = 1
1902 self.assertEqual(got, expected)
1905 # Testing time objects with a non-None tzinfo.
1906 class TestTimeTZ(TestTime, TZInfoBase):
1907 theclass = time
1909 def test_empty(self):
1910 t = self.theclass()
1911 self.assertEqual(t.hour, 0)
1912 self.assertEqual(t.minute, 0)
1913 self.assertEqual(t.second, 0)
1914 self.assertEqual(t.microsecond, 0)
1915 self.failUnless(t.tzinfo is None)
1917 def test_zones(self):
1918 est = FixedOffset(-300, "EST", 1)
1919 utc = FixedOffset(0, "UTC", -2)
1920 met = FixedOffset(60, "MET", 3)
1921 t1 = time( 7, 47, tzinfo=est)
1922 t2 = time(12, 47, tzinfo=utc)
1923 t3 = time(13, 47, tzinfo=met)
1924 t4 = time(microsecond=40)
1925 t5 = time(microsecond=40, tzinfo=utc)
1927 self.assertEqual(t1.tzinfo, est)
1928 self.assertEqual(t2.tzinfo, utc)
1929 self.assertEqual(t3.tzinfo, met)
1930 self.failUnless(t4.tzinfo is None)
1931 self.assertEqual(t5.tzinfo, utc)
1933 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
1934 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
1935 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
1936 self.failUnless(t4.utcoffset() is None)
1937 self.assertRaises(TypeError, t1.utcoffset, "no args")
1939 self.assertEqual(t1.tzname(), "EST")
1940 self.assertEqual(t2.tzname(), "UTC")
1941 self.assertEqual(t3.tzname(), "MET")
1942 self.failUnless(t4.tzname() is None)
1943 self.assertRaises(TypeError, t1.tzname, "no args")
1945 self.assertEqual(t1.dst(), timedelta(minutes=1))
1946 self.assertEqual(t2.dst(), timedelta(minutes=-2))
1947 self.assertEqual(t3.dst(), timedelta(minutes=3))
1948 self.failUnless(t4.dst() is None)
1949 self.assertRaises(TypeError, t1.dst, "no args")
1951 self.assertEqual(hash(t1), hash(t2))
1952 self.assertEqual(hash(t1), hash(t3))
1953 self.assertEqual(hash(t2), hash(t3))
1955 self.assertEqual(t1, t2)
1956 self.assertEqual(t1, t3)
1957 self.assertEqual(t2, t3)
1958 self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
1959 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
1960 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
1962 self.assertEqual(str(t1), "07:47:00-05:00")
1963 self.assertEqual(str(t2), "12:47:00+00:00")
1964 self.assertEqual(str(t3), "13:47:00+01:00")
1965 self.assertEqual(str(t4), "00:00:00.000040")
1966 self.assertEqual(str(t5), "00:00:00.000040+00:00")
1968 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
1969 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
1970 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
1971 self.assertEqual(t4.isoformat(), "00:00:00.000040")
1972 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
1974 d = 'datetime.time'
1975 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
1976 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
1977 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
1978 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
1979 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
1981 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
1982 "07:47:00 %Z=EST %z=-0500")
1983 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
1984 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
1986 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
1987 t1 = time(23, 59, tzinfo=yuck)
1988 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
1989 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
1991 # Check that an invalid tzname result raises an exception.
1992 class Badtzname(tzinfo):
1993 def tzname(self, dt): return 42
1994 t = time(2, 3, 4, tzinfo=Badtzname())
1995 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
1996 self.assertRaises(TypeError, t.strftime, "%Z")
1998 def test_hash_edge_cases(self):
1999 # Offsets that overflow a basic time.
2000 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2001 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2002 self.assertEqual(hash(t1), hash(t2))
2004 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2005 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2006 self.assertEqual(hash(t1), hash(t2))
2008 def test_pickling(self):
2009 # Try one without a tzinfo.
2010 args = 20, 59, 16, 64**2
2011 orig = self.theclass(*args)
2012 for pickler, unpickler, proto in pickle_choices:
2013 green = pickler.dumps(orig, proto)
2014 derived = unpickler.loads(green)
2015 self.assertEqual(orig, derived)
2017 # Try one with a tzinfo.
2018 tinfo = PicklableFixedOffset(-300, 'cookie')
2019 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
2020 for pickler, unpickler, proto in pickle_choices:
2021 green = pickler.dumps(orig, proto)
2022 derived = unpickler.loads(green)
2023 self.assertEqual(orig, derived)
2024 self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
2025 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2026 self.assertEqual(derived.tzname(), 'cookie')
2028 def test_more_bool(self):
2029 # Test cases with non-None tzinfo.
2030 cls = self.theclass
2032 t = cls(0, tzinfo=FixedOffset(-300, ""))
2033 self.failUnless(t)
2035 t = cls(5, tzinfo=FixedOffset(-300, ""))
2036 self.failUnless(t)
2038 t = cls(5, tzinfo=FixedOffset(300, ""))
2039 self.failUnless(not t)
2041 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2042 self.failUnless(not t)
2044 # Mostly ensuring this doesn't overflow internally.
2045 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
2046 self.failUnless(t)
2048 # But this should yield a value error -- the utcoffset is bogus.
2049 t = cls(0, tzinfo=FixedOffset(24*60, ""))
2050 self.assertRaises(ValueError, lambda: bool(t))
2052 # Likewise.
2053 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
2054 self.assertRaises(ValueError, lambda: bool(t))
2056 def test_replace(self):
2057 cls = self.theclass
2058 z100 = FixedOffset(100, "+100")
2059 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2060 args = [1, 2, 3, 4, z100]
2061 base = cls(*args)
2062 self.assertEqual(base, base.replace())
2064 i = 0
2065 for name, newval in (("hour", 5),
2066 ("minute", 6),
2067 ("second", 7),
2068 ("microsecond", 8),
2069 ("tzinfo", zm200)):
2070 newargs = args[:]
2071 newargs[i] = newval
2072 expected = cls(*newargs)
2073 got = base.replace(**{name: newval})
2074 self.assertEqual(expected, got)
2075 i += 1
2077 # Ensure we can get rid of a tzinfo.
2078 self.assertEqual(base.tzname(), "+100")
2079 base2 = base.replace(tzinfo=None)
2080 self.failUnless(base2.tzinfo is None)
2081 self.failUnless(base2.tzname() is None)
2083 # Ensure we can add one.
2084 base3 = base2.replace(tzinfo=z100)
2085 self.assertEqual(base, base3)
2086 self.failUnless(base.tzinfo is base3.tzinfo)
2088 # Out of bounds.
2089 base = cls(1)
2090 self.assertRaises(ValueError, base.replace, hour=24)
2091 self.assertRaises(ValueError, base.replace, minute=-1)
2092 self.assertRaises(ValueError, base.replace, second=100)
2093 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2095 def test_mixed_compare(self):
2096 t1 = time(1, 2, 3)
2097 t2 = time(1, 2, 3)
2098 self.assertEqual(t1, t2)
2099 t2 = t2.replace(tzinfo=None)
2100 self.assertEqual(t1, t2)
2101 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2102 self.assertEqual(t1, t2)
2103 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2104 self.assertRaises(TypeError, lambda: t1 == t2)
2106 # In time w/ identical tzinfo objects, utcoffset is ignored.
2107 class Varies(tzinfo):
2108 def __init__(self):
2109 self.offset = timedelta(minutes=22)
2110 def utcoffset(self, t):
2111 self.offset += timedelta(minutes=1)
2112 return self.offset
2114 v = Varies()
2115 t1 = t2.replace(tzinfo=v)
2116 t2 = t2.replace(tzinfo=v)
2117 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2118 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2119 self.assertEqual(t1, t2)
2121 # But if they're not identical, it isn't ignored.
2122 t2 = t2.replace(tzinfo=Varies())
2123 self.failUnless(t1 < t2) # t1's offset counter still going up
2125 def test_subclass_timetz(self):
2127 class C(self.theclass):
2128 theAnswer = 42
2130 def __new__(cls, *args, **kws):
2131 temp = kws.copy()
2132 extra = temp.pop('extra')
2133 result = self.theclass.__new__(cls, *args, **temp)
2134 result.extra = extra
2135 return result
2137 def newmeth(self, start):
2138 return start + self.hour + self.second
2140 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2142 dt1 = self.theclass(*args)
2143 dt2 = C(*args, **{'extra': 7})
2145 self.assertEqual(dt2.__class__, C)
2146 self.assertEqual(dt2.theAnswer, 42)
2147 self.assertEqual(dt2.extra, 7)
2148 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2149 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2152 # Testing datetime objects with a non-None tzinfo.
2154 class TestDateTimeTZ(TestDateTime, TZInfoBase):
2155 theclass = datetime
2157 def test_trivial(self):
2158 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2159 self.assertEqual(dt.year, 1)
2160 self.assertEqual(dt.month, 2)
2161 self.assertEqual(dt.day, 3)
2162 self.assertEqual(dt.hour, 4)
2163 self.assertEqual(dt.minute, 5)
2164 self.assertEqual(dt.second, 6)
2165 self.assertEqual(dt.microsecond, 7)
2166 self.assertEqual(dt.tzinfo, None)
2168 def test_even_more_compare(self):
2169 # The test_compare() and test_more_compare() inherited from TestDate
2170 # and TestDateTime covered non-tzinfo cases.
2172 # Smallest possible after UTC adjustment.
2173 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2174 # Largest possible after UTC adjustment.
2175 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2176 tzinfo=FixedOffset(-1439, ""))
2178 # Make sure those compare correctly, and w/o overflow.
2179 self.failUnless(t1 < t2)
2180 self.failUnless(t1 != t2)
2181 self.failUnless(t2 > t1)
2183 self.failUnless(t1 == t1)
2184 self.failUnless(t2 == t2)
2186 # Equal afer adjustment.
2187 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2188 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2189 self.assertEqual(t1, t2)
2191 # Change t1 not to subtract a minute, and t1 should be larger.
2192 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2193 self.failUnless(t1 > t2)
2195 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2196 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2197 self.failUnless(t1 < t2)
2199 # Back to the original t1, but make seconds resolve it.
2200 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2201 second=1)
2202 self.failUnless(t1 > t2)
2204 # Likewise, but make microseconds resolve it.
2205 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2206 microsecond=1)
2207 self.failUnless(t1 > t2)
2209 # Make t2 naive and it should fail.
2210 t2 = self.theclass.min
2211 self.assertRaises(TypeError, lambda: t1 == t2)
2212 self.assertEqual(t2, t2)
2214 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2215 class Naive(tzinfo):
2216 def utcoffset(self, dt): return None
2217 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
2218 self.assertRaises(TypeError, lambda: t1 == t2)
2219 self.assertEqual(t2, t2)
2221 # OTOH, it's OK to compare two of these mixing the two ways of being
2222 # naive.
2223 t1 = self.theclass(5, 6, 7)
2224 self.assertEqual(t1, t2)
2226 # Try a bogus uctoffset.
2227 class Bogus(tzinfo):
2228 def utcoffset(self, dt):
2229 return timedelta(minutes=1440) # out of bounds
2230 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2231 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
2232 self.assertRaises(ValueError, lambda: t1 == t2)
2234 def test_pickling(self):
2235 # Try one without a tzinfo.
2236 args = 6, 7, 23, 20, 59, 1, 64**2
2237 orig = self.theclass(*args)
2238 for pickler, unpickler, proto in pickle_choices:
2239 green = pickler.dumps(orig, proto)
2240 derived = unpickler.loads(green)
2241 self.assertEqual(orig, derived)
2243 # Try one with a tzinfo.
2244 tinfo = PicklableFixedOffset(-300, 'cookie')
2245 orig = self.theclass(*args, **{'tzinfo': tinfo})
2246 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
2247 for pickler, unpickler, proto in pickle_choices:
2248 green = pickler.dumps(orig, proto)
2249 derived = unpickler.loads(green)
2250 self.assertEqual(orig, derived)
2251 self.failUnless(isinstance(derived.tzinfo,
2252 PicklableFixedOffset))
2253 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2254 self.assertEqual(derived.tzname(), 'cookie')
2256 def test_extreme_hashes(self):
2257 # If an attempt is made to hash these via subtracting the offset
2258 # then hashing a datetime object, OverflowError results. The
2259 # Python implementation used to blow up here.
2260 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2261 hash(t)
2262 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2263 tzinfo=FixedOffset(-1439, ""))
2264 hash(t)
2266 # OTOH, an OOB offset should blow up.
2267 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2268 self.assertRaises(ValueError, hash, t)
2270 def test_zones(self):
2271 est = FixedOffset(-300, "EST")
2272 utc = FixedOffset(0, "UTC")
2273 met = FixedOffset(60, "MET")
2274 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2275 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2276 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
2277 self.assertEqual(t1.tzinfo, est)
2278 self.assertEqual(t2.tzinfo, utc)
2279 self.assertEqual(t3.tzinfo, met)
2280 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2281 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2282 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
2283 self.assertEqual(t1.tzname(), "EST")
2284 self.assertEqual(t2.tzname(), "UTC")
2285 self.assertEqual(t3.tzname(), "MET")
2286 self.assertEqual(hash(t1), hash(t2))
2287 self.assertEqual(hash(t1), hash(t3))
2288 self.assertEqual(hash(t2), hash(t3))
2289 self.assertEqual(t1, t2)
2290 self.assertEqual(t1, t3)
2291 self.assertEqual(t2, t3)
2292 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2293 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2294 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
2295 d = 'datetime.datetime(2002, 3, 19, '
2296 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2297 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2298 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2300 def test_combine(self):
2301 met = FixedOffset(60, "MET")
2302 d = date(2002, 3, 4)
2303 tz = time(18, 45, 3, 1234, tzinfo=met)
2304 dt = datetime.combine(d, tz)
2305 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
2306 tzinfo=met))
2308 def test_extract(self):
2309 met = FixedOffset(60, "MET")
2310 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2311 self.assertEqual(dt.date(), date(2002, 3, 4))
2312 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2313 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
2315 def test_tz_aware_arithmetic(self):
2316 import random
2318 now = self.theclass.now()
2319 tz55 = FixedOffset(-330, "west 5:30")
2320 timeaware = now.time().replace(tzinfo=tz55)
2321 nowaware = self.theclass.combine(now.date(), timeaware)
2322 self.failUnless(nowaware.tzinfo is tz55)
2323 self.assertEqual(nowaware.timetz(), timeaware)
2325 # Can't mix aware and non-aware.
2326 self.assertRaises(TypeError, lambda: now - nowaware)
2327 self.assertRaises(TypeError, lambda: nowaware - now)
2329 # And adding datetime's doesn't make sense, aware or not.
2330 self.assertRaises(TypeError, lambda: now + nowaware)
2331 self.assertRaises(TypeError, lambda: nowaware + now)
2332 self.assertRaises(TypeError, lambda: nowaware + nowaware)
2334 # Subtracting should yield 0.
2335 self.assertEqual(now - now, timedelta(0))
2336 self.assertEqual(nowaware - nowaware, timedelta(0))
2338 # Adding a delta should preserve tzinfo.
2339 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
2340 nowawareplus = nowaware + delta
2341 self.failUnless(nowaware.tzinfo is tz55)
2342 nowawareplus2 = delta + nowaware
2343 self.failUnless(nowawareplus2.tzinfo is tz55)
2344 self.assertEqual(nowawareplus, nowawareplus2)
2346 # that - delta should be what we started with, and that - what we
2347 # started with should be delta.
2348 diff = nowawareplus - delta
2349 self.failUnless(diff.tzinfo is tz55)
2350 self.assertEqual(nowaware, diff)
2351 self.assertRaises(TypeError, lambda: delta - nowawareplus)
2352 self.assertEqual(nowawareplus - nowaware, delta)
2354 # Make up a random timezone.
2355 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
2356 # Attach it to nowawareplus.
2357 nowawareplus = nowawareplus.replace(tzinfo=tzr)
2358 self.failUnless(nowawareplus.tzinfo is tzr)
2359 # Make sure the difference takes the timezone adjustments into account.
2360 got = nowaware - nowawareplus
2361 # Expected: (nowaware base - nowaware offset) -
2362 # (nowawareplus base - nowawareplus offset) =
2363 # (nowaware base - nowawareplus base) +
2364 # (nowawareplus offset - nowaware offset) =
2365 # -delta + nowawareplus offset - nowaware offset
2366 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
2367 self.assertEqual(got, expected)
2369 # Try max possible difference.
2370 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
2371 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2372 tzinfo=FixedOffset(-1439, "max"))
2373 maxdiff = max - min
2374 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
2375 timedelta(minutes=2*1439))
2377 def test_tzinfo_now(self):
2378 meth = self.theclass.now
2379 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2380 base = meth()
2381 # Try with and without naming the keyword.
2382 off42 = FixedOffset(42, "42")
2383 another = meth(off42)
2384 again = meth(tz=off42)
2385 self.failUnless(another.tzinfo is again.tzinfo)
2386 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
2387 # Bad argument with and w/o naming the keyword.
2388 self.assertRaises(TypeError, meth, 16)
2389 self.assertRaises(TypeError, meth, tzinfo=16)
2390 # Bad keyword name.
2391 self.assertRaises(TypeError, meth, tinfo=off42)
2392 # Too many args.
2393 self.assertRaises(TypeError, meth, off42, off42)
2395 # We don't know which time zone we're in, and don't have a tzinfo
2396 # class to represent it, so seeing whether a tz argument actually
2397 # does a conversion is tricky.
2398 weirdtz = FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0)
2399 utc = FixedOffset(0, "utc", 0)
2400 for dummy in range(3):
2401 now = datetime.now(weirdtz)
2402 self.failUnless(now.tzinfo is weirdtz)
2403 utcnow = datetime.utcnow().replace(tzinfo=utc)
2404 now2 = utcnow.astimezone(weirdtz)
2405 if abs(now - now2) < timedelta(seconds=30):
2406 break
2407 # Else the code is broken, or more than 30 seconds passed between
2408 # calls; assuming the latter, just try again.
2409 else:
2410 # Three strikes and we're out.
2411 self.fail("utcnow(), now(tz), or astimezone() may be broken")
2413 def test_tzinfo_fromtimestamp(self):
2414 import time
2415 meth = self.theclass.fromtimestamp
2416 ts = time.time()
2417 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2418 base = meth(ts)
2419 # Try with and without naming the keyword.
2420 off42 = FixedOffset(42, "42")
2421 another = meth(ts, off42)
2422 again = meth(ts, tz=off42)
2423 self.failUnless(another.tzinfo is again.tzinfo)
2424 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
2425 # Bad argument with and w/o naming the keyword.
2426 self.assertRaises(TypeError, meth, ts, 16)
2427 self.assertRaises(TypeError, meth, ts, tzinfo=16)
2428 # Bad keyword name.
2429 self.assertRaises(TypeError, meth, ts, tinfo=off42)
2430 # Too many args.
2431 self.assertRaises(TypeError, meth, ts, off42, off42)
2432 # Too few args.
2433 self.assertRaises(TypeError, meth)
2435 # Try to make sure tz= actually does some conversion.
2436 timestamp = 1000000000
2437 utcdatetime = datetime.utcfromtimestamp(timestamp)
2438 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
2439 # But on some flavor of Mac, it's nowhere near that. So we can't have
2440 # any idea here what time that actually is, we can only test that
2441 # relative changes match.
2442 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
2443 tz = FixedOffset(utcoffset, "tz", 0)
2444 expected = utcdatetime + utcoffset
2445 got = datetime.fromtimestamp(timestamp, tz)
2446 self.assertEqual(expected, got.replace(tzinfo=None))
2448 def test_tzinfo_utcnow(self):
2449 meth = self.theclass.utcnow
2450 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2451 base = meth()
2452 # Try with and without naming the keyword; for whatever reason,
2453 # utcnow() doesn't accept a tzinfo argument.
2454 off42 = FixedOffset(42, "42")
2455 self.assertRaises(TypeError, meth, off42)
2456 self.assertRaises(TypeError, meth, tzinfo=off42)
2458 def test_tzinfo_utcfromtimestamp(self):
2459 import time
2460 meth = self.theclass.utcfromtimestamp
2461 ts = time.time()
2462 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2463 base = meth(ts)
2464 # Try with and without naming the keyword; for whatever reason,
2465 # utcfromtimestamp() doesn't accept a tzinfo argument.
2466 off42 = FixedOffset(42, "42")
2467 self.assertRaises(TypeError, meth, ts, off42)
2468 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
2470 def test_tzinfo_timetuple(self):
2471 # TestDateTime tested most of this. datetime adds a twist to the
2472 # DST flag.
2473 class DST(tzinfo):
2474 def __init__(self, dstvalue):
2475 if isinstance(dstvalue, int):
2476 dstvalue = timedelta(minutes=dstvalue)
2477 self.dstvalue = dstvalue
2478 def dst(self, dt):
2479 return self.dstvalue
2481 cls = self.theclass
2482 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
2483 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
2484 t = d.timetuple()
2485 self.assertEqual(1, t.tm_year)
2486 self.assertEqual(1, t.tm_mon)
2487 self.assertEqual(1, t.tm_mday)
2488 self.assertEqual(10, t.tm_hour)
2489 self.assertEqual(20, t.tm_min)
2490 self.assertEqual(30, t.tm_sec)
2491 self.assertEqual(0, t.tm_wday)
2492 self.assertEqual(1, t.tm_yday)
2493 self.assertEqual(flag, t.tm_isdst)
2495 # dst() returns wrong type.
2496 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
2498 # dst() at the edge.
2499 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
2500 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
2502 # dst() out of range.
2503 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
2504 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
2506 def test_utctimetuple(self):
2507 class DST(tzinfo):
2508 def __init__(self, dstvalue):
2509 if isinstance(dstvalue, int):
2510 dstvalue = timedelta(minutes=dstvalue)
2511 self.dstvalue = dstvalue
2512 def dst(self, dt):
2513 return self.dstvalue
2515 cls = self.theclass
2516 # This can't work: DST didn't implement utcoffset.
2517 self.assertRaises(NotImplementedError,
2518 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
2520 class UOFS(DST):
2521 def __init__(self, uofs, dofs=None):
2522 DST.__init__(self, dofs)
2523 self.uofs = timedelta(minutes=uofs)
2524 def utcoffset(self, dt):
2525 return self.uofs
2527 # Ensure tm_isdst is 0 regardless of what dst() says: DST is never
2528 # in effect for a UTC time.
2529 for dstvalue in -33, 33, 0, None:
2530 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
2531 t = d.utctimetuple()
2532 self.assertEqual(d.year, t.tm_year)
2533 self.assertEqual(d.month, t.tm_mon)
2534 self.assertEqual(d.day, t.tm_mday)
2535 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
2536 self.assertEqual(13, t.tm_min)
2537 self.assertEqual(d.second, t.tm_sec)
2538 self.assertEqual(d.weekday(), t.tm_wday)
2539 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
2540 t.tm_yday)
2541 self.assertEqual(0, t.tm_isdst)
2543 # At the edges, UTC adjustment can normalize into years out-of-range
2544 # for a datetime object. Ensure that a correct timetuple is
2545 # created anyway.
2546 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
2547 # That goes back 1 minute less than a full day.
2548 t = tiny.utctimetuple()
2549 self.assertEqual(t.tm_year, MINYEAR-1)
2550 self.assertEqual(t.tm_mon, 12)
2551 self.assertEqual(t.tm_mday, 31)
2552 self.assertEqual(t.tm_hour, 0)
2553 self.assertEqual(t.tm_min, 1)
2554 self.assertEqual(t.tm_sec, 37)
2555 self.assertEqual(t.tm_yday, 366) # "year 0" is a leap year
2556 self.assertEqual(t.tm_isdst, 0)
2558 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
2559 # That goes forward 1 minute less than a full day.
2560 t = huge.utctimetuple()
2561 self.assertEqual(t.tm_year, MAXYEAR+1)
2562 self.assertEqual(t.tm_mon, 1)
2563 self.assertEqual(t.tm_mday, 1)
2564 self.assertEqual(t.tm_hour, 23)
2565 self.assertEqual(t.tm_min, 58)
2566 self.assertEqual(t.tm_sec, 37)
2567 self.assertEqual(t.tm_yday, 1)
2568 self.assertEqual(t.tm_isdst, 0)
2570 def test_tzinfo_isoformat(self):
2571 zero = FixedOffset(0, "+00:00")
2572 plus = FixedOffset(220, "+03:40")
2573 minus = FixedOffset(-231, "-03:51")
2574 unknown = FixedOffset(None, "")
2576 cls = self.theclass
2577 datestr = '0001-02-03'
2578 for ofs in None, zero, plus, minus, unknown:
2579 for us in 0, 987001:
2580 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
2581 timestr = '04:05:59' + (us and '.987001' or '')
2582 ofsstr = ofs is not None and d.tzname() or ''
2583 tailstr = timestr + ofsstr
2584 iso = d.isoformat()
2585 self.assertEqual(iso, datestr + 'T' + tailstr)
2586 self.assertEqual(iso, d.isoformat('T'))
2587 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
2588 self.assertEqual(str(d), datestr + ' ' + tailstr)
2590 def test_replace(self):
2591 cls = self.theclass
2592 z100 = FixedOffset(100, "+100")
2593 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2594 args = [1, 2, 3, 4, 5, 6, 7, z100]
2595 base = cls(*args)
2596 self.assertEqual(base, base.replace())
2598 i = 0
2599 for name, newval in (("year", 2),
2600 ("month", 3),
2601 ("day", 4),
2602 ("hour", 5),
2603 ("minute", 6),
2604 ("second", 7),
2605 ("microsecond", 8),
2606 ("tzinfo", zm200)):
2607 newargs = args[:]
2608 newargs[i] = newval
2609 expected = cls(*newargs)
2610 got = base.replace(**{name: newval})
2611 self.assertEqual(expected, got)
2612 i += 1
2614 # Ensure we can get rid of a tzinfo.
2615 self.assertEqual(base.tzname(), "+100")
2616 base2 = base.replace(tzinfo=None)
2617 self.failUnless(base2.tzinfo is None)
2618 self.failUnless(base2.tzname() is None)
2620 # Ensure we can add one.
2621 base3 = base2.replace(tzinfo=z100)
2622 self.assertEqual(base, base3)
2623 self.failUnless(base.tzinfo is base3.tzinfo)
2625 # Out of bounds.
2626 base = cls(2000, 2, 29)
2627 self.assertRaises(ValueError, base.replace, year=2001)
2629 def test_more_astimezone(self):
2630 # The inherited test_astimezone covered some trivial and error cases.
2631 fnone = FixedOffset(None, "None")
2632 f44m = FixedOffset(44, "44")
2633 fm5h = FixedOffset(-timedelta(hours=5), "m300")
2635 dt = self.theclass.now(tz=f44m)
2636 self.failUnless(dt.tzinfo is f44m)
2637 # Replacing with degenerate tzinfo raises an exception.
2638 self.assertRaises(ValueError, dt.astimezone, fnone)
2639 # Ditto with None tz.
2640 self.assertRaises(TypeError, dt.astimezone, None)
2641 # Replacing with same tzinfo makes no change.
2642 x = dt.astimezone(dt.tzinfo)
2643 self.failUnless(x.tzinfo is f44m)
2644 self.assertEqual(x.date(), dt.date())
2645 self.assertEqual(x.time(), dt.time())
2647 # Replacing with different tzinfo does adjust.
2648 got = dt.astimezone(fm5h)
2649 self.failUnless(got.tzinfo is fm5h)
2650 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
2651 expected = dt - dt.utcoffset() # in effect, convert to UTC
2652 expected += fm5h.utcoffset(dt) # and from there to local time
2653 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
2654 self.assertEqual(got.date(), expected.date())
2655 self.assertEqual(got.time(), expected.time())
2656 self.assertEqual(got.timetz(), expected.timetz())
2657 self.failUnless(got.tzinfo is expected.tzinfo)
2658 self.assertEqual(got, expected)
2660 def test_aware_subtract(self):
2661 cls = self.theclass
2663 # Ensure that utcoffset() is ignored when the operands have the
2664 # same tzinfo member.
2665 class OperandDependentOffset(tzinfo):
2666 def utcoffset(self, t):
2667 if t.minute < 10:
2668 # d0 and d1 equal after adjustment
2669 return timedelta(minutes=t.minute)
2670 else:
2671 # d2 off in the weeds
2672 return timedelta(minutes=59)
2674 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
2675 d0 = base.replace(minute=3)
2676 d1 = base.replace(minute=9)
2677 d2 = base.replace(minute=11)
2678 for x in d0, d1, d2:
2679 for y in d0, d1, d2:
2680 got = x - y
2681 expected = timedelta(minutes=x.minute - y.minute)
2682 self.assertEqual(got, expected)
2684 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
2685 # ignored.
2686 base = cls(8, 9, 10, 11, 12, 13, 14)
2687 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2688 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2689 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2690 for x in d0, d1, d2:
2691 for y in d0, d1, d2:
2692 got = x - y
2693 if (x is d0 or x is d1) and (y is d0 or y is d1):
2694 expected = timedelta(0)
2695 elif x is y is d2:
2696 expected = timedelta(0)
2697 elif x is d2:
2698 expected = timedelta(minutes=(11-59)-0)
2699 else:
2700 assert y is d2
2701 expected = timedelta(minutes=0-(11-59))
2702 self.assertEqual(got, expected)
2704 def test_mixed_compare(self):
2705 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
2706 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
2707 self.assertEqual(t1, t2)
2708 t2 = t2.replace(tzinfo=None)
2709 self.assertEqual(t1, t2)
2710 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2711 self.assertEqual(t1, t2)
2712 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2713 self.assertRaises(TypeError, lambda: t1 == t2)
2715 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
2716 class Varies(tzinfo):
2717 def __init__(self):
2718 self.offset = timedelta(minutes=22)
2719 def utcoffset(self, t):
2720 self.offset += timedelta(minutes=1)
2721 return self.offset
2723 v = Varies()
2724 t1 = t2.replace(tzinfo=v)
2725 t2 = t2.replace(tzinfo=v)
2726 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2727 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2728 self.assertEqual(t1, t2)
2730 # But if they're not identical, it isn't ignored.
2731 t2 = t2.replace(tzinfo=Varies())
2732 self.failUnless(t1 < t2) # t1's offset counter still going up
2734 def test_subclass_datetimetz(self):
2736 class C(self.theclass):
2737 theAnswer = 42
2739 def __new__(cls, *args, **kws):
2740 temp = kws.copy()
2741 extra = temp.pop('extra')
2742 result = self.theclass.__new__(cls, *args, **temp)
2743 result.extra = extra
2744 return result
2746 def newmeth(self, start):
2747 return start + self.hour + self.year
2749 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2751 dt1 = self.theclass(*args)
2752 dt2 = C(*args, **{'extra': 7})
2754 self.assertEqual(dt2.__class__, C)
2755 self.assertEqual(dt2.theAnswer, 42)
2756 self.assertEqual(dt2.extra, 7)
2757 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2758 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
2760 # Pain to set up DST-aware tzinfo classes.
2762 def first_sunday_on_or_after(dt):
2763 days_to_go = 6 - dt.weekday()
2764 if days_to_go:
2765 dt += timedelta(days_to_go)
2766 return dt
2768 ZERO = timedelta(0)
2769 HOUR = timedelta(hours=1)
2770 DAY = timedelta(days=1)
2771 # In the US, DST starts at 2am (standard time) on the first Sunday in April.
2772 DSTSTART = datetime(1, 4, 1, 2)
2773 # and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
2774 # which is the first Sunday on or after Oct 25. Because we view 1:MM as
2775 # being standard time on that day, there is no spelling in local time of
2776 # the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
2777 DSTEND = datetime(1, 10, 25, 1)
2779 class USTimeZone(tzinfo):
2781 def __init__(self, hours, reprname, stdname, dstname):
2782 self.stdoffset = timedelta(hours=hours)
2783 self.reprname = reprname
2784 self.stdname = stdname
2785 self.dstname = dstname
2787 def __repr__(self):
2788 return self.reprname
2790 def tzname(self, dt):
2791 if self.dst(dt):
2792 return self.dstname
2793 else:
2794 return self.stdname
2796 def utcoffset(self, dt):
2797 return self.stdoffset + self.dst(dt)
2799 def dst(self, dt):
2800 if dt is None or dt.tzinfo is None:
2801 # An exception instead may be sensible here, in one or more of
2802 # the cases.
2803 return ZERO
2804 assert dt.tzinfo is self
2806 # Find first Sunday in April.
2807 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
2808 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
2810 # Find last Sunday in October.
2811 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
2812 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
2814 # Can't compare naive to aware objects, so strip the timezone from
2815 # dt first.
2816 if start <= dt.replace(tzinfo=None) < end:
2817 return HOUR
2818 else:
2819 return ZERO
2821 Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
2822 Central = USTimeZone(-6, "Central", "CST", "CDT")
2823 Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
2824 Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
2825 utc_real = FixedOffset(0, "UTC", 0)
2826 # For better test coverage, we want another flavor of UTC that's west of
2827 # the Eastern and Pacific timezones.
2828 utc_fake = FixedOffset(-12*60, "UTCfake", 0)
2830 class TestTimezoneConversions(unittest.TestCase):
2831 # The DST switch times for 2002, in std time.
2832 dston = datetime(2002, 4, 7, 2)
2833 dstoff = datetime(2002, 10, 27, 1)
2835 theclass = datetime
2837 # Check a time that's inside DST.
2838 def checkinside(self, dt, tz, utc, dston, dstoff):
2839 self.assertEqual(dt.dst(), HOUR)
2841 # Conversion to our own timezone is always an identity.
2842 self.assertEqual(dt.astimezone(tz), dt)
2844 asutc = dt.astimezone(utc)
2845 there_and_back = asutc.astimezone(tz)
2847 # Conversion to UTC and back isn't always an identity here,
2848 # because there are redundant spellings (in local time) of
2849 # UTC time when DST begins: the clock jumps from 1:59:59
2850 # to 3:00:00, and a local time of 2:MM:SS doesn't really
2851 # make sense then. The classes above treat 2:MM:SS as
2852 # daylight time then (it's "after 2am"), really an alias
2853 # for 1:MM:SS standard time. The latter form is what
2854 # conversion back from UTC produces.
2855 if dt.date() == dston.date() and dt.hour == 2:
2856 # We're in the redundant hour, and coming back from
2857 # UTC gives the 1:MM:SS standard-time spelling.
2858 self.assertEqual(there_and_back + HOUR, dt)
2859 # Although during was considered to be in daylight
2860 # time, there_and_back is not.
2861 self.assertEqual(there_and_back.dst(), ZERO)
2862 # They're the same times in UTC.
2863 self.assertEqual(there_and_back.astimezone(utc),
2864 dt.astimezone(utc))
2865 else:
2866 # We're not in the redundant hour.
2867 self.assertEqual(dt, there_and_back)
2869 # Because we have a redundant spelling when DST begins, there is
2870 # (unforunately) an hour when DST ends that can't be spelled at all in
2871 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
2872 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
2873 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
2874 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
2875 # expressed in local time. Nevertheless, we want conversion back
2876 # from UTC to mimic the local clock's "repeat an hour" behavior.
2877 nexthour_utc = asutc + HOUR
2878 nexthour_tz = nexthour_utc.astimezone(tz)
2879 if dt.date() == dstoff.date() and dt.hour == 0:
2880 # We're in the hour before the last DST hour. The last DST hour
2881 # is ineffable. We want the conversion back to repeat 1:MM.
2882 self.assertEqual(nexthour_tz, dt.replace(hour=1))
2883 nexthour_utc += HOUR
2884 nexthour_tz = nexthour_utc.astimezone(tz)
2885 self.assertEqual(nexthour_tz, dt.replace(hour=1))
2886 else:
2887 self.assertEqual(nexthour_tz - dt, HOUR)
2889 # Check a time that's outside DST.
2890 def checkoutside(self, dt, tz, utc):
2891 self.assertEqual(dt.dst(), ZERO)
2893 # Conversion to our own timezone is always an identity.
2894 self.assertEqual(dt.astimezone(tz), dt)
2896 # Converting to UTC and back is an identity too.
2897 asutc = dt.astimezone(utc)
2898 there_and_back = asutc.astimezone(tz)
2899 self.assertEqual(dt, there_and_back)
2901 def convert_between_tz_and_utc(self, tz, utc):
2902 dston = self.dston.replace(tzinfo=tz)
2903 # Because 1:MM on the day DST ends is taken as being standard time,
2904 # there is no spelling in tz for the last hour of daylight time.
2905 # For purposes of the test, the last hour of DST is 0:MM, which is
2906 # taken as being daylight time (and 1:MM is taken as being standard
2907 # time).
2908 dstoff = self.dstoff.replace(tzinfo=tz)
2909 for delta in (timedelta(weeks=13),
2910 DAY,
2911 HOUR,
2912 timedelta(minutes=1),
2913 timedelta(microseconds=1)):
2915 self.checkinside(dston, tz, utc, dston, dstoff)
2916 for during in dston + delta, dstoff - delta:
2917 self.checkinside(during, tz, utc, dston, dstoff)
2919 self.checkoutside(dstoff, tz, utc)
2920 for outside in dston - delta, dstoff + delta:
2921 self.checkoutside(outside, tz, utc)
2923 def test_easy(self):
2924 # Despite the name of this test, the endcases are excruciating.
2925 self.convert_between_tz_and_utc(Eastern, utc_real)
2926 self.convert_between_tz_and_utc(Pacific, utc_real)
2927 self.convert_between_tz_and_utc(Eastern, utc_fake)
2928 self.convert_between_tz_and_utc(Pacific, utc_fake)
2929 # The next is really dancing near the edge. It works because
2930 # Pacific and Eastern are far enough apart that their "problem
2931 # hours" don't overlap.
2932 self.convert_between_tz_and_utc(Eastern, Pacific)
2933 self.convert_between_tz_and_utc(Pacific, Eastern)
2934 # OTOH, these fail! Don't enable them. The difficulty is that
2935 # the edge case tests assume that every hour is representable in
2936 # the "utc" class. This is always true for a fixed-offset tzinfo
2937 # class (lke utc_real and utc_fake), but not for Eastern or Central.
2938 # For these adjacent DST-aware time zones, the range of time offsets
2939 # tested ends up creating hours in the one that aren't representable
2940 # in the other. For the same reason, we would see failures in the
2941 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
2942 # offset deltas in convert_between_tz_and_utc().
2944 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
2945 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
2947 def test_tricky(self):
2948 # 22:00 on day before daylight starts.
2949 fourback = self.dston - timedelta(hours=4)
2950 ninewest = FixedOffset(-9*60, "-0900", 0)
2951 fourback = fourback.replace(tzinfo=ninewest)
2952 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
2953 # 2", we should get the 3 spelling.
2954 # If we plug 22:00 the day before into Eastern, it "looks like std
2955 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
2956 # to 22:00 lands on 2:00, which makes no sense in local time (the
2957 # local clock jumps from 1 to 3). The point here is to make sure we
2958 # get the 3 spelling.
2959 expected = self.dston.replace(hour=3)
2960 got = fourback.astimezone(Eastern).replace(tzinfo=None)
2961 self.assertEqual(expected, got)
2963 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
2964 # case we want the 1:00 spelling.
2965 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
2966 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
2967 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
2968 # spelling.
2969 expected = self.dston.replace(hour=1)
2970 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
2971 self.assertEqual(expected, got)
2973 # Now on the day DST ends, we want "repeat an hour" behavior.
2974 # UTC 4:MM 5:MM 6:MM 7:MM checking these
2975 # EST 23:MM 0:MM 1:MM 2:MM
2976 # EDT 0:MM 1:MM 2:MM 3:MM
2977 # wall 0:MM 1:MM 1:MM 2:MM against these
2978 for utc in utc_real, utc_fake:
2979 for tz in Eastern, Pacific:
2980 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
2981 # Convert that to UTC.
2982 first_std_hour -= tz.utcoffset(None)
2983 # Adjust for possibly fake UTC.
2984 asutc = first_std_hour + utc.utcoffset(None)
2985 # First UTC hour to convert; this is 4:00 when utc=utc_real &
2986 # tz=Eastern.
2987 asutcbase = asutc.replace(tzinfo=utc)
2988 for tzhour in (0, 1, 1, 2):
2989 expectedbase = self.dstoff.replace(hour=tzhour)
2990 for minute in 0, 30, 59:
2991 expected = expectedbase.replace(minute=minute)
2992 asutc = asutcbase.replace(minute=minute)
2993 astz = asutc.astimezone(tz)
2994 self.assertEqual(astz.replace(tzinfo=None), expected)
2995 asutcbase += HOUR
2998 def test_bogus_dst(self):
2999 class ok(tzinfo):
3000 def utcoffset(self, dt): return HOUR
3001 def dst(self, dt): return HOUR
3003 now = self.theclass.now().replace(tzinfo=utc_real)
3004 # Doesn't blow up.
3005 now.astimezone(ok())
3007 # Does blow up.
3008 class notok(ok):
3009 def dst(self, dt): return None
3010 self.assertRaises(ValueError, now.astimezone, notok())
3012 def test_fromutc(self):
3013 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
3014 now = datetime.utcnow().replace(tzinfo=utc_real)
3015 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3016 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
3017 enow = Eastern.fromutc(now) # doesn't blow up
3018 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
3019 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
3020 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
3022 # Always converts UTC to standard time.
3023 class FauxUSTimeZone(USTimeZone):
3024 def fromutc(self, dt):
3025 return dt + self.stdoffset
3026 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
3028 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
3029 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
3030 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
3032 # Check around DST start.
3033 start = self.dston.replace(hour=4, tzinfo=Eastern)
3034 fstart = start.replace(tzinfo=FEastern)
3035 for wall in 23, 0, 1, 3, 4, 5:
3036 expected = start.replace(hour=wall)
3037 if wall == 23:
3038 expected -= timedelta(days=1)
3039 got = Eastern.fromutc(start)
3040 self.assertEqual(expected, got)
3042 expected = fstart + FEastern.stdoffset
3043 got = FEastern.fromutc(fstart)
3044 self.assertEqual(expected, got)
3046 # Ensure astimezone() calls fromutc() too.
3047 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3048 self.assertEqual(expected, got)
3050 start += HOUR
3051 fstart += HOUR
3053 # Check around DST end.
3054 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
3055 fstart = start.replace(tzinfo=FEastern)
3056 for wall in 0, 1, 1, 2, 3, 4:
3057 expected = start.replace(hour=wall)
3058 got = Eastern.fromutc(start)
3059 self.assertEqual(expected, got)
3061 expected = fstart + FEastern.stdoffset
3062 got = FEastern.fromutc(fstart)
3063 self.assertEqual(expected, got)
3065 # Ensure astimezone() calls fromutc() too.
3066 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3067 self.assertEqual(expected, got)
3069 start += HOUR
3070 fstart += HOUR
3073 def test_suite():
3074 allsuites = [unittest.makeSuite(klass, 'test')
3075 for klass in (TestModule,
3076 TestTZInfo,
3077 TestTimeDelta,
3078 TestDateOnly,
3079 TestDate,
3080 TestDateTime,
3081 TestTime,
3082 TestTimeTZ,
3083 TestDateTimeTZ,
3084 TestTimezoneConversions,
3087 return unittest.TestSuite(allsuites)
3089 def test_main():
3090 import gc
3091 import sys
3093 thesuite = test_suite()
3094 lastrc = None
3095 while True:
3096 test_support.run_suite(thesuite)
3097 if 1: # change to 0, under a debug build, for some leak detection
3098 break
3099 gc.collect()
3100 if gc.garbage:
3101 raise SystemError("gc.garbage not empty after test run: %r" %
3102 gc.garbage)
3103 if hasattr(sys, 'gettotalrefcount'):
3104 thisrc = sys.gettotalrefcount()
3105 print >> sys.stderr, '*' * 10, 'total refs:', thisrc,
3106 if lastrc:
3107 print >> sys.stderr, 'delta:', thisrc - lastrc
3108 else:
3109 print >> sys.stderr
3110 lastrc = thisrc
3112 if __name__ == "__main__":
3113 test_main()