move sections
[python/dscho.git] / Lib / test / test_datetime.py
blob905f7ef30aed13bb0296f0bdce798d813e78bcbf
1 """Test date/time type.
3 See http://www.zope.org/Members/fdrake/DateTimeWiki/TestCases
4 """
5 from __future__ import division
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.assertTrue(issubclass(NotEnough, tzinfo))
83 ne = NotEnough(3, "NotByALongShot")
84 self.assertIsInstance(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.assertIsInstance(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.assertTrue(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.assertTrue(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.assertIsInstance(orig, tzinfo)
115 self.assertTrue(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.assertIsInstance(derived, tzinfo)
122 self.assertTrue(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:
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.assertFalse(me == ())
140 self.assertTrue(me != ())
141 self.assertFalse(() == me)
142 self.assertTrue(() != me)
144 self.assertIn(me, [1, 20L, [], me])
145 self.assertIn([], [me, 1, 20L, []])
147 def test_harmful_mixed_comparison(self):
148 me = self.theclass(1, 1, 1)
150 self.assertRaises(TypeError, lambda: me < ())
151 self.assertRaises(TypeError, lambda: me <= ())
152 self.assertRaises(TypeError, lambda: me > ())
153 self.assertRaises(TypeError, lambda: me >= ())
155 self.assertRaises(TypeError, lambda: () < me)
156 self.assertRaises(TypeError, lambda: () <= me)
157 self.assertRaises(TypeError, lambda: () > me)
158 self.assertRaises(TypeError, lambda: () >= me)
160 self.assertRaises(TypeError, cmp, (), me)
161 self.assertRaises(TypeError, cmp, me, ())
163 #############################################################################
164 # timedelta tests
166 class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
168 theclass = timedelta
170 def test_constructor(self):
171 eq = self.assertEqual
172 td = timedelta
174 # Check keyword args to constructor
175 eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0,
176 milliseconds=0, microseconds=0))
177 eq(td(1), td(days=1))
178 eq(td(0, 1), td(seconds=1))
179 eq(td(0, 0, 1), td(microseconds=1))
180 eq(td(weeks=1), td(days=7))
181 eq(td(days=1), td(hours=24))
182 eq(td(hours=1), td(minutes=60))
183 eq(td(minutes=1), td(seconds=60))
184 eq(td(seconds=1), td(milliseconds=1000))
185 eq(td(milliseconds=1), td(microseconds=1000))
187 # Check float args to constructor
188 eq(td(weeks=1.0/7), td(days=1))
189 eq(td(days=1.0/24), td(hours=1))
190 eq(td(hours=1.0/60), td(minutes=1))
191 eq(td(minutes=1.0/60), td(seconds=1))
192 eq(td(seconds=0.001), td(milliseconds=1))
193 eq(td(milliseconds=0.001), td(microseconds=1))
195 def test_computations(self):
196 eq = self.assertEqual
197 td = timedelta
199 a = td(7) # One week
200 b = td(0, 60) # One minute
201 c = td(0, 0, 1000) # One millisecond
202 eq(a+b+c, td(7, 60, 1000))
203 eq(a-b, td(6, 24*3600 - 60))
204 eq(-a, td(-7))
205 eq(+a, td(7))
206 eq(-b, td(-1, 24*3600 - 60))
207 eq(-c, td(-1, 24*3600 - 1, 999000))
208 eq(abs(a), a)
209 eq(abs(-a), a)
210 eq(td(6, 24*3600), a)
211 eq(td(0, 0, 60*1000000), b)
212 eq(a*10, td(70))
213 eq(a*10, 10*a)
214 eq(a*10L, 10*a)
215 eq(b*10, td(0, 600))
216 eq(10*b, td(0, 600))
217 eq(b*10L, td(0, 600))
218 eq(c*10, td(0, 0, 10000))
219 eq(10*c, td(0, 0, 10000))
220 eq(c*10L, td(0, 0, 10000))
221 eq(a*-1, -a)
222 eq(b*-2, -b-b)
223 eq(c*-2, -c+-c)
224 eq(b*(60*24), (b*60)*24)
225 eq(b*(60*24), (60*b)*24)
226 eq(c*1000, td(0, 1))
227 eq(1000*c, td(0, 1))
228 eq(a//7, td(1))
229 eq(b//10, td(0, 6))
230 eq(c//1000, td(0, 0, 1))
231 eq(a//10, td(0, 7*24*360))
232 eq(a//3600000, td(0, 0, 7*24*1000))
234 def test_disallowed_computations(self):
235 a = timedelta(42)
237 # Add/sub ints, longs, floats should be illegal
238 for i in 1, 1L, 1.0:
239 self.assertRaises(TypeError, lambda: a+i)
240 self.assertRaises(TypeError, lambda: a-i)
241 self.assertRaises(TypeError, lambda: i+a)
242 self.assertRaises(TypeError, lambda: i-a)
244 # Mul/div by float isn't supported.
245 x = 2.3
246 self.assertRaises(TypeError, lambda: a*x)
247 self.assertRaises(TypeError, lambda: x*a)
248 self.assertRaises(TypeError, lambda: a/x)
249 self.assertRaises(TypeError, lambda: x/a)
250 self.assertRaises(TypeError, lambda: a // x)
251 self.assertRaises(TypeError, lambda: x // a)
253 # Division of int by timedelta doesn't make sense.
254 # Division by zero doesn't make sense.
255 for zero in 0, 0L:
256 self.assertRaises(TypeError, lambda: zero // a)
257 self.assertRaises(ZeroDivisionError, lambda: a // zero)
259 def test_basic_attributes(self):
260 days, seconds, us = 1, 7, 31
261 td = timedelta(days, seconds, us)
262 self.assertEqual(td.days, days)
263 self.assertEqual(td.seconds, seconds)
264 self.assertEqual(td.microseconds, us)
266 def test_total_seconds(self):
267 td = timedelta(days=365)
268 self.assertEqual(td.total_seconds(), 31536000.0)
269 for total_seconds in [123456.789012, -123456.789012, 0.123456, 0, 1e6]:
270 td = timedelta(seconds=total_seconds)
271 self.assertEqual(td.total_seconds(), total_seconds)
272 # Issue8644: Test that td.total_seconds() has the same
273 # accuracy as td / timedelta(seconds=1).
274 for ms in [-1, -2, -123]:
275 td = timedelta(microseconds=ms)
276 self.assertEqual(td.total_seconds(),
277 ((24*3600*td.days + td.seconds)*10**6
278 + td.microseconds)/10**6)
280 def test_carries(self):
281 t1 = timedelta(days=100,
282 weeks=-7,
283 hours=-24*(100-49),
284 minutes=-3,
285 seconds=12,
286 microseconds=(3*60 - 12) * 1e6 + 1)
287 t2 = timedelta(microseconds=1)
288 self.assertEqual(t1, t2)
290 def test_hash_equality(self):
291 t1 = timedelta(days=100,
292 weeks=-7,
293 hours=-24*(100-49),
294 minutes=-3,
295 seconds=12,
296 microseconds=(3*60 - 12) * 1000000)
297 t2 = timedelta()
298 self.assertEqual(hash(t1), hash(t2))
300 t1 += timedelta(weeks=7)
301 t2 += timedelta(days=7*7)
302 self.assertEqual(t1, t2)
303 self.assertEqual(hash(t1), hash(t2))
305 d = {t1: 1}
306 d[t2] = 2
307 self.assertEqual(len(d), 1)
308 self.assertEqual(d[t1], 2)
310 def test_pickling(self):
311 args = 12, 34, 56
312 orig = timedelta(*args)
313 for pickler, unpickler, proto in pickle_choices:
314 green = pickler.dumps(orig, proto)
315 derived = unpickler.loads(green)
316 self.assertEqual(orig, derived)
318 def test_compare(self):
319 t1 = timedelta(2, 3, 4)
320 t2 = timedelta(2, 3, 4)
321 self.assertTrue(t1 == t2)
322 self.assertTrue(t1 <= t2)
323 self.assertTrue(t1 >= t2)
324 self.assertTrue(not t1 != t2)
325 self.assertTrue(not t1 < t2)
326 self.assertTrue(not t1 > t2)
327 self.assertEqual(cmp(t1, t2), 0)
328 self.assertEqual(cmp(t2, t1), 0)
330 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
331 t2 = timedelta(*args) # this is larger than t1
332 self.assertTrue(t1 < t2)
333 self.assertTrue(t2 > t1)
334 self.assertTrue(t1 <= t2)
335 self.assertTrue(t2 >= t1)
336 self.assertTrue(t1 != t2)
337 self.assertTrue(t2 != t1)
338 self.assertTrue(not t1 == t2)
339 self.assertTrue(not t2 == t1)
340 self.assertTrue(not t1 > t2)
341 self.assertTrue(not t2 < t1)
342 self.assertTrue(not t1 >= t2)
343 self.assertTrue(not t2 <= t1)
344 self.assertEqual(cmp(t1, t2), -1)
345 self.assertEqual(cmp(t2, t1), 1)
347 for badarg in OTHERSTUFF:
348 self.assertEqual(t1 == badarg, False)
349 self.assertEqual(t1 != badarg, True)
350 self.assertEqual(badarg == t1, False)
351 self.assertEqual(badarg != t1, True)
353 self.assertRaises(TypeError, lambda: t1 <= badarg)
354 self.assertRaises(TypeError, lambda: t1 < badarg)
355 self.assertRaises(TypeError, lambda: t1 > badarg)
356 self.assertRaises(TypeError, lambda: t1 >= badarg)
357 self.assertRaises(TypeError, lambda: badarg <= t1)
358 self.assertRaises(TypeError, lambda: badarg < t1)
359 self.assertRaises(TypeError, lambda: badarg > t1)
360 self.assertRaises(TypeError, lambda: badarg >= t1)
362 def test_str(self):
363 td = timedelta
364 eq = self.assertEqual
366 eq(str(td(1)), "1 day, 0:00:00")
367 eq(str(td(-1)), "-1 day, 0:00:00")
368 eq(str(td(2)), "2 days, 0:00:00")
369 eq(str(td(-2)), "-2 days, 0:00:00")
371 eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
372 eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
373 eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
374 "-210 days, 23:12:34")
376 eq(str(td(milliseconds=1)), "0:00:00.001000")
377 eq(str(td(microseconds=3)), "0:00:00.000003")
379 eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
380 microseconds=999999)),
381 "999999999 days, 23:59:59.999999")
383 def test_roundtrip(self):
384 for td in (timedelta(days=999999999, hours=23, minutes=59,
385 seconds=59, microseconds=999999),
386 timedelta(days=-999999999),
387 timedelta(days=1, seconds=2, microseconds=3)):
389 # Verify td -> string -> td identity.
390 s = repr(td)
391 self.assertTrue(s.startswith('datetime.'))
392 s = s[9:]
393 td2 = eval(s)
394 self.assertEqual(td, td2)
396 # Verify identity via reconstructing from pieces.
397 td2 = timedelta(td.days, td.seconds, td.microseconds)
398 self.assertEqual(td, td2)
400 def test_resolution_info(self):
401 self.assertIsInstance(timedelta.min, timedelta)
402 self.assertIsInstance(timedelta.max, timedelta)
403 self.assertIsInstance(timedelta.resolution, timedelta)
404 self.assertTrue(timedelta.max > timedelta.min)
405 self.assertEqual(timedelta.min, timedelta(-999999999))
406 self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
407 self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
409 def test_overflow(self):
410 tiny = timedelta.resolution
412 td = timedelta.min + tiny
413 td -= tiny # no problem
414 self.assertRaises(OverflowError, td.__sub__, tiny)
415 self.assertRaises(OverflowError, td.__add__, -tiny)
417 td = timedelta.max - tiny
418 td += tiny # no problem
419 self.assertRaises(OverflowError, td.__add__, tiny)
420 self.assertRaises(OverflowError, td.__sub__, -tiny)
422 self.assertRaises(OverflowError, lambda: -timedelta.max)
424 def test_microsecond_rounding(self):
425 td = timedelta
426 eq = self.assertEqual
428 # Single-field rounding.
429 eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
430 eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
431 eq(td(milliseconds=0.6/1000), td(microseconds=1))
432 eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
434 # Rounding due to contributions from more than one field.
435 us_per_hour = 3600e6
436 us_per_day = us_per_hour * 24
437 eq(td(days=.4/us_per_day), td(0))
438 eq(td(hours=.2/us_per_hour), td(0))
439 eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
441 eq(td(days=-.4/us_per_day), td(0))
442 eq(td(hours=-.2/us_per_hour), td(0))
443 eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
445 def test_massive_normalization(self):
446 td = timedelta(microseconds=-1)
447 self.assertEqual((td.days, td.seconds, td.microseconds),
448 (-1, 24*3600-1, 999999))
450 def test_bool(self):
451 self.assertTrue(timedelta(1))
452 self.assertTrue(timedelta(0, 1))
453 self.assertTrue(timedelta(0, 0, 1))
454 self.assertTrue(timedelta(microseconds=1))
455 self.assertTrue(not timedelta(0))
457 def test_subclass_timedelta(self):
459 class T(timedelta):
460 @staticmethod
461 def from_td(td):
462 return T(td.days, td.seconds, td.microseconds)
464 def as_hours(self):
465 sum = (self.days * 24 +
466 self.seconds / 3600.0 +
467 self.microseconds / 3600e6)
468 return round(sum)
470 t1 = T(days=1)
471 self.assertTrue(type(t1) is T)
472 self.assertEqual(t1.as_hours(), 24)
474 t2 = T(days=-1, seconds=-3600)
475 self.assertTrue(type(t2) is T)
476 self.assertEqual(t2.as_hours(), -25)
478 t3 = t1 + t2
479 self.assertTrue(type(t3) is timedelta)
480 t4 = T.from_td(t3)
481 self.assertTrue(type(t4) is T)
482 self.assertEqual(t3.days, t4.days)
483 self.assertEqual(t3.seconds, t4.seconds)
484 self.assertEqual(t3.microseconds, t4.microseconds)
485 self.assertEqual(str(t3), str(t4))
486 self.assertEqual(t4.as_hours(), -1)
488 #############################################################################
489 # date tests
491 class TestDateOnly(unittest.TestCase):
492 # Tests here won't pass if also run on datetime objects, so don't
493 # subclass this to test datetimes too.
495 def test_delta_non_days_ignored(self):
496 dt = date(2000, 1, 2)
497 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
498 microseconds=5)
499 days = timedelta(delta.days)
500 self.assertEqual(days, timedelta(1))
502 dt2 = dt + delta
503 self.assertEqual(dt2, dt + days)
505 dt2 = delta + dt
506 self.assertEqual(dt2, dt + days)
508 dt2 = dt - delta
509 self.assertEqual(dt2, dt - days)
511 delta = -delta
512 days = timedelta(delta.days)
513 self.assertEqual(days, timedelta(-2))
515 dt2 = dt + delta
516 self.assertEqual(dt2, dt + days)
518 dt2 = delta + dt
519 self.assertEqual(dt2, dt + days)
521 dt2 = dt - delta
522 self.assertEqual(dt2, dt - days)
524 class SubclassDate(date):
525 sub_var = 1
527 class TestDate(HarmlessMixedComparison, unittest.TestCase):
528 # Tests here should pass for both dates and datetimes, except for a
529 # few tests that TestDateTime overrides.
531 theclass = date
533 def test_basic_attributes(self):
534 dt = self.theclass(2002, 3, 1)
535 self.assertEqual(dt.year, 2002)
536 self.assertEqual(dt.month, 3)
537 self.assertEqual(dt.day, 1)
539 def test_roundtrip(self):
540 for dt in (self.theclass(1, 2, 3),
541 self.theclass.today()):
542 # Verify dt -> string -> date identity.
543 s = repr(dt)
544 self.assertTrue(s.startswith('datetime.'))
545 s = s[9:]
546 dt2 = eval(s)
547 self.assertEqual(dt, dt2)
549 # Verify identity via reconstructing from pieces.
550 dt2 = self.theclass(dt.year, dt.month, dt.day)
551 self.assertEqual(dt, dt2)
553 def test_ordinal_conversions(self):
554 # Check some fixed values.
555 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
556 (1, 12, 31, 365),
557 (2, 1, 1, 366),
558 # first example from "Calendrical Calculations"
559 (1945, 11, 12, 710347)]:
560 d = self.theclass(y, m, d)
561 self.assertEqual(n, d.toordinal())
562 fromord = self.theclass.fromordinal(n)
563 self.assertEqual(d, fromord)
564 if hasattr(fromord, "hour"):
565 # if we're checking something fancier than a date, verify
566 # the extra fields have been zeroed out
567 self.assertEqual(fromord.hour, 0)
568 self.assertEqual(fromord.minute, 0)
569 self.assertEqual(fromord.second, 0)
570 self.assertEqual(fromord.microsecond, 0)
572 # Check first and last days of year spottily across the whole
573 # range of years supported.
574 for year in xrange(MINYEAR, MAXYEAR+1, 7):
575 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
576 d = self.theclass(year, 1, 1)
577 n = d.toordinal()
578 d2 = self.theclass.fromordinal(n)
579 self.assertEqual(d, d2)
580 # Verify that moving back a day gets to the end of year-1.
581 if year > 1:
582 d = self.theclass.fromordinal(n-1)
583 d2 = self.theclass(year-1, 12, 31)
584 self.assertEqual(d, d2)
585 self.assertEqual(d2.toordinal(), n-1)
587 # Test every day in a leap-year and a non-leap year.
588 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
589 for year, isleap in (2000, True), (2002, False):
590 n = self.theclass(year, 1, 1).toordinal()
591 for month, maxday in zip(range(1, 13), dim):
592 if month == 2 and isleap:
593 maxday += 1
594 for day in range(1, maxday+1):
595 d = self.theclass(year, month, day)
596 self.assertEqual(d.toordinal(), n)
597 self.assertEqual(d, self.theclass.fromordinal(n))
598 n += 1
600 def test_extreme_ordinals(self):
601 a = self.theclass.min
602 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
603 aord = a.toordinal()
604 b = a.fromordinal(aord)
605 self.assertEqual(a, b)
607 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
609 b = a + timedelta(days=1)
610 self.assertEqual(b.toordinal(), aord + 1)
611 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
613 a = self.theclass.max
614 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
615 aord = a.toordinal()
616 b = a.fromordinal(aord)
617 self.assertEqual(a, b)
619 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
621 b = a - timedelta(days=1)
622 self.assertEqual(b.toordinal(), aord - 1)
623 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
625 def test_bad_constructor_arguments(self):
626 # bad years
627 self.theclass(MINYEAR, 1, 1) # no exception
628 self.theclass(MAXYEAR, 1, 1) # no exception
629 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
630 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
631 # bad months
632 self.theclass(2000, 1, 1) # no exception
633 self.theclass(2000, 12, 1) # no exception
634 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
635 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
636 # bad days
637 self.theclass(2000, 2, 29) # no exception
638 self.theclass(2004, 2, 29) # no exception
639 self.theclass(2400, 2, 29) # no exception
640 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
641 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
642 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
643 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
644 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
645 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
647 def test_hash_equality(self):
648 d = self.theclass(2000, 12, 31)
649 # same thing
650 e = self.theclass(2000, 12, 31)
651 self.assertEqual(d, e)
652 self.assertEqual(hash(d), hash(e))
654 dic = {d: 1}
655 dic[e] = 2
656 self.assertEqual(len(dic), 1)
657 self.assertEqual(dic[d], 2)
658 self.assertEqual(dic[e], 2)
660 d = self.theclass(2001, 1, 1)
661 # same thing
662 e = self.theclass(2001, 1, 1)
663 self.assertEqual(d, e)
664 self.assertEqual(hash(d), hash(e))
666 dic = {d: 1}
667 dic[e] = 2
668 self.assertEqual(len(dic), 1)
669 self.assertEqual(dic[d], 2)
670 self.assertEqual(dic[e], 2)
672 def test_computations(self):
673 a = self.theclass(2002, 1, 31)
674 b = self.theclass(1956, 1, 31)
676 diff = a-b
677 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
678 self.assertEqual(diff.seconds, 0)
679 self.assertEqual(diff.microseconds, 0)
681 day = timedelta(1)
682 week = timedelta(7)
683 a = self.theclass(2002, 3, 2)
684 self.assertEqual(a + day, self.theclass(2002, 3, 3))
685 self.assertEqual(day + a, self.theclass(2002, 3, 3))
686 self.assertEqual(a - day, self.theclass(2002, 3, 1))
687 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
688 self.assertEqual(a + week, self.theclass(2002, 3, 9))
689 self.assertEqual(a - week, self.theclass(2002, 2, 23))
690 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
691 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
692 self.assertEqual((a + week) - a, week)
693 self.assertEqual((a + day) - a, day)
694 self.assertEqual((a - week) - a, -week)
695 self.assertEqual((a - day) - a, -day)
696 self.assertEqual(a - (a + week), -week)
697 self.assertEqual(a - (a + day), -day)
698 self.assertEqual(a - (a - week), week)
699 self.assertEqual(a - (a - day), day)
701 # Add/sub ints, longs, floats should be illegal
702 for i in 1, 1L, 1.0:
703 self.assertRaises(TypeError, lambda: a+i)
704 self.assertRaises(TypeError, lambda: a-i)
705 self.assertRaises(TypeError, lambda: i+a)
706 self.assertRaises(TypeError, lambda: i-a)
708 # delta - date is senseless.
709 self.assertRaises(TypeError, lambda: day - a)
710 # mixing date and (delta or date) via * or // is senseless
711 self.assertRaises(TypeError, lambda: day * a)
712 self.assertRaises(TypeError, lambda: a * day)
713 self.assertRaises(TypeError, lambda: day // a)
714 self.assertRaises(TypeError, lambda: a // day)
715 self.assertRaises(TypeError, lambda: a * a)
716 self.assertRaises(TypeError, lambda: a // a)
717 # date + date is senseless
718 self.assertRaises(TypeError, lambda: a + a)
720 def test_overflow(self):
721 tiny = self.theclass.resolution
723 for delta in [tiny, timedelta(1), timedelta(2)]:
724 dt = self.theclass.min + delta
725 dt -= delta # no problem
726 self.assertRaises(OverflowError, dt.__sub__, delta)
727 self.assertRaises(OverflowError, dt.__add__, -delta)
729 dt = self.theclass.max - delta
730 dt += delta # no problem
731 self.assertRaises(OverflowError, dt.__add__, delta)
732 self.assertRaises(OverflowError, dt.__sub__, -delta)
734 def test_fromtimestamp(self):
735 import time
737 # Try an arbitrary fixed value.
738 year, month, day = 1999, 9, 19
739 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
740 d = self.theclass.fromtimestamp(ts)
741 self.assertEqual(d.year, year)
742 self.assertEqual(d.month, month)
743 self.assertEqual(d.day, day)
745 def test_insane_fromtimestamp(self):
746 # It's possible that some platform maps time_t to double,
747 # and that this test will fail there. This test should
748 # exempt such platforms (provided they return reasonable
749 # results!).
750 for insane in -1e200, 1e200:
751 self.assertRaises(ValueError, self.theclass.fromtimestamp,
752 insane)
754 def test_today(self):
755 import time
757 # We claim that today() is like fromtimestamp(time.time()), so
758 # prove it.
759 for dummy in range(3):
760 today = self.theclass.today()
761 ts = time.time()
762 todayagain = self.theclass.fromtimestamp(ts)
763 if today == todayagain:
764 break
765 # There are several legit reasons that could fail:
766 # 1. It recently became midnight, between the today() and the
767 # time() calls.
768 # 2. The platform time() has such fine resolution that we'll
769 # never get the same value twice.
770 # 3. The platform time() has poor resolution, and we just
771 # happened to call today() right before a resolution quantum
772 # boundary.
773 # 4. The system clock got fiddled between calls.
774 # In any case, wait a little while and try again.
775 time.sleep(0.1)
777 # It worked or it didn't. If it didn't, assume it's reason #2, and
778 # let the test pass if they're within half a second of each other.
779 self.assertTrue(today == todayagain or
780 abs(todayagain - today) < timedelta(seconds=0.5))
782 def test_weekday(self):
783 for i in range(7):
784 # March 4, 2002 is a Monday
785 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
786 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
787 # January 2, 1956 is a Monday
788 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
789 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
791 def test_isocalendar(self):
792 # Check examples from
793 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
794 for i in range(7):
795 d = self.theclass(2003, 12, 22+i)
796 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
797 d = self.theclass(2003, 12, 29) + timedelta(i)
798 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
799 d = self.theclass(2004, 1, 5+i)
800 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
801 d = self.theclass(2009, 12, 21+i)
802 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
803 d = self.theclass(2009, 12, 28) + timedelta(i)
804 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
805 d = self.theclass(2010, 1, 4+i)
806 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
808 def test_iso_long_years(self):
809 # Calculate long ISO years and compare to table from
810 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
811 ISO_LONG_YEARS_TABLE = """
812 4 32 60 88
813 9 37 65 93
814 15 43 71 99
815 20 48 76
816 26 54 82
818 105 133 161 189
819 111 139 167 195
820 116 144 172
821 122 150 178
822 128 156 184
824 201 229 257 285
825 207 235 263 291
826 212 240 268 296
827 218 246 274
828 224 252 280
830 303 331 359 387
831 308 336 364 392
832 314 342 370 398
833 320 348 376
834 325 353 381
836 iso_long_years = map(int, ISO_LONG_YEARS_TABLE.split())
837 iso_long_years.sort()
838 L = []
839 for i in range(400):
840 d = self.theclass(2000+i, 12, 31)
841 d1 = self.theclass(1600+i, 12, 31)
842 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
843 if d.isocalendar()[1] == 53:
844 L.append(i)
845 self.assertEqual(L, iso_long_years)
847 def test_isoformat(self):
848 t = self.theclass(2, 3, 2)
849 self.assertEqual(t.isoformat(), "0002-03-02")
851 def test_ctime(self):
852 t = self.theclass(2002, 3, 2)
853 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
855 def test_strftime(self):
856 t = self.theclass(2005, 3, 2)
857 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
858 self.assertEqual(t.strftime(""), "") # SF bug #761337
859 self.assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784
861 self.assertRaises(TypeError, t.strftime) # needs an arg
862 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
863 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
865 # test that unicode input is allowed (issue 2782)
866 self.assertEqual(t.strftime(u"%m"), "03")
868 # A naive object replaces %z and %Z w/ empty strings.
869 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
871 #make sure that invalid format specifiers are handled correctly
872 #self.assertRaises(ValueError, t.strftime, "%e")
873 #self.assertRaises(ValueError, t.strftime, "%")
874 #self.assertRaises(ValueError, t.strftime, "%#")
876 #oh well, some systems just ignore those invalid ones.
877 #at least, excercise them to make sure that no crashes
878 #are generated
879 for f in ["%e", "%", "%#"]:
880 try:
881 t.strftime(f)
882 except ValueError:
883 pass
885 #check that this standard extension works
886 t.strftime("%f")
889 def test_format(self):
890 dt = self.theclass(2007, 9, 10)
891 self.assertEqual(dt.__format__(''), str(dt))
893 # check that a derived class's __str__() gets called
894 class A(self.theclass):
895 def __str__(self):
896 return 'A'
897 a = A(2007, 9, 10)
898 self.assertEqual(a.__format__(''), 'A')
900 # check that a derived class's strftime gets called
901 class B(self.theclass):
902 def strftime(self, format_spec):
903 return 'B'
904 b = B(2007, 9, 10)
905 self.assertEqual(b.__format__(''), str(dt))
907 for fmt in ["m:%m d:%d y:%y",
908 "m:%m d:%d y:%y H:%H M:%M S:%S",
909 "%z %Z",
911 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
912 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
913 self.assertEqual(b.__format__(fmt), 'B')
915 def test_resolution_info(self):
916 self.assertIsInstance(self.theclass.min, self.theclass)
917 self.assertIsInstance(self.theclass.max, self.theclass)
918 self.assertIsInstance(self.theclass.resolution, timedelta)
919 self.assertTrue(self.theclass.max > self.theclass.min)
921 def test_extreme_timedelta(self):
922 big = self.theclass.max - self.theclass.min
923 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
924 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
925 # n == 315537897599999999 ~= 2**58.13
926 justasbig = timedelta(0, 0, n)
927 self.assertEqual(big, justasbig)
928 self.assertEqual(self.theclass.min + big, self.theclass.max)
929 self.assertEqual(self.theclass.max - big, self.theclass.min)
931 def test_timetuple(self):
932 for i in range(7):
933 # January 2, 1956 is a Monday (0)
934 d = self.theclass(1956, 1, 2+i)
935 t = d.timetuple()
936 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
937 # February 1, 1956 is a Wednesday (2)
938 d = self.theclass(1956, 2, 1+i)
939 t = d.timetuple()
940 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
941 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
942 # of the year.
943 d = self.theclass(1956, 3, 1+i)
944 t = d.timetuple()
945 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
946 self.assertEqual(t.tm_year, 1956)
947 self.assertEqual(t.tm_mon, 3)
948 self.assertEqual(t.tm_mday, 1+i)
949 self.assertEqual(t.tm_hour, 0)
950 self.assertEqual(t.tm_min, 0)
951 self.assertEqual(t.tm_sec, 0)
952 self.assertEqual(t.tm_wday, (3+i)%7)
953 self.assertEqual(t.tm_yday, 61+i)
954 self.assertEqual(t.tm_isdst, -1)
956 def test_pickling(self):
957 args = 6, 7, 23
958 orig = self.theclass(*args)
959 for pickler, unpickler, proto in pickle_choices:
960 green = pickler.dumps(orig, proto)
961 derived = unpickler.loads(green)
962 self.assertEqual(orig, derived)
964 def test_compare(self):
965 t1 = self.theclass(2, 3, 4)
966 t2 = self.theclass(2, 3, 4)
967 self.assertTrue(t1 == t2)
968 self.assertTrue(t1 <= t2)
969 self.assertTrue(t1 >= t2)
970 self.assertTrue(not t1 != t2)
971 self.assertTrue(not t1 < t2)
972 self.assertTrue(not t1 > t2)
973 self.assertEqual(cmp(t1, t2), 0)
974 self.assertEqual(cmp(t2, t1), 0)
976 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
977 t2 = self.theclass(*args) # this is larger than t1
978 self.assertTrue(t1 < t2)
979 self.assertTrue(t2 > t1)
980 self.assertTrue(t1 <= t2)
981 self.assertTrue(t2 >= t1)
982 self.assertTrue(t1 != t2)
983 self.assertTrue(t2 != t1)
984 self.assertTrue(not t1 == t2)
985 self.assertTrue(not t2 == t1)
986 self.assertTrue(not t1 > t2)
987 self.assertTrue(not t2 < t1)
988 self.assertTrue(not t1 >= t2)
989 self.assertTrue(not t2 <= t1)
990 self.assertEqual(cmp(t1, t2), -1)
991 self.assertEqual(cmp(t2, t1), 1)
993 for badarg in OTHERSTUFF:
994 self.assertEqual(t1 == badarg, False)
995 self.assertEqual(t1 != badarg, True)
996 self.assertEqual(badarg == t1, False)
997 self.assertEqual(badarg != t1, True)
999 self.assertRaises(TypeError, lambda: t1 < badarg)
1000 self.assertRaises(TypeError, lambda: t1 > badarg)
1001 self.assertRaises(TypeError, lambda: t1 >= badarg)
1002 self.assertRaises(TypeError, lambda: badarg <= t1)
1003 self.assertRaises(TypeError, lambda: badarg < t1)
1004 self.assertRaises(TypeError, lambda: badarg > t1)
1005 self.assertRaises(TypeError, lambda: badarg >= t1)
1007 def test_mixed_compare(self):
1008 our = self.theclass(2000, 4, 5)
1009 self.assertRaises(TypeError, cmp, our, 1)
1010 self.assertRaises(TypeError, cmp, 1, our)
1012 class AnotherDateTimeClass(object):
1013 def __cmp__(self, other):
1014 # Return "equal" so calling this can't be confused with
1015 # compare-by-address (which never says "equal" for distinct
1016 # objects).
1017 return 0
1018 __hash__ = None # Silence Py3k warning
1020 # This still errors, because date and datetime comparison raise
1021 # TypeError instead of NotImplemented when they don't know what to
1022 # do, in order to stop comparison from falling back to the default
1023 # compare-by-address.
1024 their = AnotherDateTimeClass()
1025 self.assertRaises(TypeError, cmp, our, their)
1026 # Oops: The next stab raises TypeError in the C implementation,
1027 # but not in the Python implementation of datetime. The difference
1028 # is due to that the Python implementation defines __cmp__ but
1029 # the C implementation defines tp_richcompare. This is more pain
1030 # to fix than it's worth, so commenting out the test.
1031 # self.assertEqual(cmp(their, our), 0)
1033 # But date and datetime comparison return NotImplemented instead if the
1034 # other object has a timetuple attr. This gives the other object a
1035 # chance to do the comparison.
1036 class Comparable(AnotherDateTimeClass):
1037 def timetuple(self):
1038 return ()
1040 their = Comparable()
1041 self.assertEqual(cmp(our, their), 0)
1042 self.assertEqual(cmp(their, our), 0)
1043 self.assertTrue(our == their)
1044 self.assertTrue(their == our)
1046 def test_bool(self):
1047 # All dates are considered true.
1048 self.assertTrue(self.theclass.min)
1049 self.assertTrue(self.theclass.max)
1051 def test_strftime_out_of_range(self):
1052 # For nasty technical reasons, we can't handle years before 1900.
1053 cls = self.theclass
1054 self.assertEqual(cls(1900, 1, 1).strftime("%Y"), "1900")
1055 for y in 1, 49, 51, 99, 100, 1000, 1899:
1056 self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y")
1058 def test_replace(self):
1059 cls = self.theclass
1060 args = [1, 2, 3]
1061 base = cls(*args)
1062 self.assertEqual(base, base.replace())
1064 i = 0
1065 for name, newval in (("year", 2),
1066 ("month", 3),
1067 ("day", 4)):
1068 newargs = args[:]
1069 newargs[i] = newval
1070 expected = cls(*newargs)
1071 got = base.replace(**{name: newval})
1072 self.assertEqual(expected, got)
1073 i += 1
1075 # Out of bounds.
1076 base = cls(2000, 2, 29)
1077 self.assertRaises(ValueError, base.replace, year=2001)
1079 def test_subclass_date(self):
1081 class C(self.theclass):
1082 theAnswer = 42
1084 def __new__(cls, *args, **kws):
1085 temp = kws.copy()
1086 extra = temp.pop('extra')
1087 result = self.theclass.__new__(cls, *args, **temp)
1088 result.extra = extra
1089 return result
1091 def newmeth(self, start):
1092 return start + self.year + self.month
1094 args = 2003, 4, 14
1096 dt1 = self.theclass(*args)
1097 dt2 = C(*args, **{'extra': 7})
1099 self.assertEqual(dt2.__class__, C)
1100 self.assertEqual(dt2.theAnswer, 42)
1101 self.assertEqual(dt2.extra, 7)
1102 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1103 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1105 def test_pickling_subclass_date(self):
1107 args = 6, 7, 23
1108 orig = SubclassDate(*args)
1109 for pickler, unpickler, proto in pickle_choices:
1110 green = pickler.dumps(orig, proto)
1111 derived = unpickler.loads(green)
1112 self.assertEqual(orig, derived)
1114 def test_backdoor_resistance(self):
1115 # For fast unpickling, the constructor accepts a pickle string.
1116 # This is a low-overhead backdoor. A user can (by intent or
1117 # mistake) pass a string directly, which (if it's the right length)
1118 # will get treated like a pickle, and bypass the normal sanity
1119 # checks in the constructor. This can create insane objects.
1120 # The constructor doesn't want to burn the time to validate all
1121 # fields, but does check the month field. This stops, e.g.,
1122 # datetime.datetime('1995-03-25') from yielding an insane object.
1123 base = '1995-03-25'
1124 if not issubclass(self.theclass, datetime):
1125 base = base[:4]
1126 for month_byte in '9', chr(0), chr(13), '\xff':
1127 self.assertRaises(TypeError, self.theclass,
1128 base[:2] + month_byte + base[3:])
1129 for ord_byte in range(1, 13):
1130 # This shouldn't blow up because of the month byte alone. If
1131 # the implementation changes to do more-careful checking, it may
1132 # blow up because other fields are insane.
1133 self.theclass(base[:2] + chr(ord_byte) + base[3:])
1135 #############################################################################
1136 # datetime tests
1138 class SubclassDatetime(datetime):
1139 sub_var = 1
1141 class TestDateTime(TestDate):
1143 theclass = datetime
1145 def test_basic_attributes(self):
1146 dt = self.theclass(2002, 3, 1, 12, 0)
1147 self.assertEqual(dt.year, 2002)
1148 self.assertEqual(dt.month, 3)
1149 self.assertEqual(dt.day, 1)
1150 self.assertEqual(dt.hour, 12)
1151 self.assertEqual(dt.minute, 0)
1152 self.assertEqual(dt.second, 0)
1153 self.assertEqual(dt.microsecond, 0)
1155 def test_basic_attributes_nonzero(self):
1156 # Make sure all attributes are non-zero so bugs in
1157 # bit-shifting access show up.
1158 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1159 self.assertEqual(dt.year, 2002)
1160 self.assertEqual(dt.month, 3)
1161 self.assertEqual(dt.day, 1)
1162 self.assertEqual(dt.hour, 12)
1163 self.assertEqual(dt.minute, 59)
1164 self.assertEqual(dt.second, 59)
1165 self.assertEqual(dt.microsecond, 8000)
1167 def test_roundtrip(self):
1168 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1169 self.theclass.now()):
1170 # Verify dt -> string -> datetime identity.
1171 s = repr(dt)
1172 self.assertTrue(s.startswith('datetime.'))
1173 s = s[9:]
1174 dt2 = eval(s)
1175 self.assertEqual(dt, dt2)
1177 # Verify identity via reconstructing from pieces.
1178 dt2 = self.theclass(dt.year, dt.month, dt.day,
1179 dt.hour, dt.minute, dt.second,
1180 dt.microsecond)
1181 self.assertEqual(dt, dt2)
1183 def test_isoformat(self):
1184 t = self.theclass(2, 3, 2, 4, 5, 1, 123)
1185 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123")
1186 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123")
1187 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123")
1188 self.assertEqual(t.isoformat('\x00'), "0002-03-02\x0004:05:01.000123")
1189 # str is ISO format with the separator forced to a blank.
1190 self.assertEqual(str(t), "0002-03-02 04:05:01.000123")
1192 t = self.theclass(2, 3, 2)
1193 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1194 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1195 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1196 # str is ISO format with the separator forced to a blank.
1197 self.assertEqual(str(t), "0002-03-02 00:00:00")
1199 def test_format(self):
1200 dt = self.theclass(2007, 9, 10, 4, 5, 1, 123)
1201 self.assertEqual(dt.__format__(''), str(dt))
1203 # check that a derived class's __str__() gets called
1204 class A(self.theclass):
1205 def __str__(self):
1206 return 'A'
1207 a = A(2007, 9, 10, 4, 5, 1, 123)
1208 self.assertEqual(a.__format__(''), 'A')
1210 # check that a derived class's strftime gets called
1211 class B(self.theclass):
1212 def strftime(self, format_spec):
1213 return 'B'
1214 b = B(2007, 9, 10, 4, 5, 1, 123)
1215 self.assertEqual(b.__format__(''), str(dt))
1217 for fmt in ["m:%m d:%d y:%y",
1218 "m:%m d:%d y:%y H:%H M:%M S:%S",
1219 "%z %Z",
1221 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1222 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1223 self.assertEqual(b.__format__(fmt), 'B')
1225 def test_more_ctime(self):
1226 # Test fields that TestDate doesn't touch.
1227 import time
1229 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1230 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1231 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1232 # out. The difference is that t.ctime() produces " 2" for the day,
1233 # but platform ctime() produces "02" for the day. According to
1234 # C99, t.ctime() is correct here.
1235 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1237 # So test a case where that difference doesn't matter.
1238 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1239 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1241 def test_tz_independent_comparing(self):
1242 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1243 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1244 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1245 self.assertEqual(dt1, dt3)
1246 self.assertTrue(dt2 > dt3)
1248 # Make sure comparison doesn't forget microseconds, and isn't done
1249 # via comparing a float timestamp (an IEEE double doesn't have enough
1250 # precision to span microsecond resolution across years 1 thru 9999,
1251 # so comparing via timestamp necessarily calls some distinct values
1252 # equal).
1253 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1254 us = timedelta(microseconds=1)
1255 dt2 = dt1 + us
1256 self.assertEqual(dt2 - dt1, us)
1257 self.assertTrue(dt1 < dt2)
1259 def test_strftime_with_bad_tzname_replace(self):
1260 # verify ok if tzinfo.tzname().replace() returns a non-string
1261 class MyTzInfo(FixedOffset):
1262 def tzname(self, dt):
1263 class MyStr(str):
1264 def replace(self, *args):
1265 return None
1266 return MyStr('name')
1267 t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
1268 self.assertRaises(TypeError, t.strftime, '%Z')
1270 def test_bad_constructor_arguments(self):
1271 # bad years
1272 self.theclass(MINYEAR, 1, 1) # no exception
1273 self.theclass(MAXYEAR, 1, 1) # no exception
1274 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1275 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1276 # bad months
1277 self.theclass(2000, 1, 1) # no exception
1278 self.theclass(2000, 12, 1) # no exception
1279 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1280 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1281 # bad days
1282 self.theclass(2000, 2, 29) # no exception
1283 self.theclass(2004, 2, 29) # no exception
1284 self.theclass(2400, 2, 29) # no exception
1285 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1286 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1287 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1288 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1289 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1290 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1291 # bad hours
1292 self.theclass(2000, 1, 31, 0) # no exception
1293 self.theclass(2000, 1, 31, 23) # no exception
1294 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1295 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1296 # bad minutes
1297 self.theclass(2000, 1, 31, 23, 0) # no exception
1298 self.theclass(2000, 1, 31, 23, 59) # no exception
1299 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1300 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1301 # bad seconds
1302 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1303 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1304 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1305 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1306 # bad microseconds
1307 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1308 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1309 self.assertRaises(ValueError, self.theclass,
1310 2000, 1, 31, 23, 59, 59, -1)
1311 self.assertRaises(ValueError, self.theclass,
1312 2000, 1, 31, 23, 59, 59,
1313 1000000)
1315 def test_hash_equality(self):
1316 d = self.theclass(2000, 12, 31, 23, 30, 17)
1317 e = self.theclass(2000, 12, 31, 23, 30, 17)
1318 self.assertEqual(d, e)
1319 self.assertEqual(hash(d), hash(e))
1321 dic = {d: 1}
1322 dic[e] = 2
1323 self.assertEqual(len(dic), 1)
1324 self.assertEqual(dic[d], 2)
1325 self.assertEqual(dic[e], 2)
1327 d = self.theclass(2001, 1, 1, 0, 5, 17)
1328 e = self.theclass(2001, 1, 1, 0, 5, 17)
1329 self.assertEqual(d, e)
1330 self.assertEqual(hash(d), hash(e))
1332 dic = {d: 1}
1333 dic[e] = 2
1334 self.assertEqual(len(dic), 1)
1335 self.assertEqual(dic[d], 2)
1336 self.assertEqual(dic[e], 2)
1338 def test_computations(self):
1339 a = self.theclass(2002, 1, 31)
1340 b = self.theclass(1956, 1, 31)
1341 diff = a-b
1342 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1343 self.assertEqual(diff.seconds, 0)
1344 self.assertEqual(diff.microseconds, 0)
1345 a = self.theclass(2002, 3, 2, 17, 6)
1346 millisec = timedelta(0, 0, 1000)
1347 hour = timedelta(0, 3600)
1348 day = timedelta(1)
1349 week = timedelta(7)
1350 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1351 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1352 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1353 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1354 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1355 self.assertEqual(a - hour, a + -hour)
1356 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1357 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1358 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1359 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1360 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1361 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1362 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1363 self.assertEqual((a + week) - a, week)
1364 self.assertEqual((a + day) - a, day)
1365 self.assertEqual((a + hour) - a, hour)
1366 self.assertEqual((a + millisec) - a, millisec)
1367 self.assertEqual((a - week) - a, -week)
1368 self.assertEqual((a - day) - a, -day)
1369 self.assertEqual((a - hour) - a, -hour)
1370 self.assertEqual((a - millisec) - a, -millisec)
1371 self.assertEqual(a - (a + week), -week)
1372 self.assertEqual(a - (a + day), -day)
1373 self.assertEqual(a - (a + hour), -hour)
1374 self.assertEqual(a - (a + millisec), -millisec)
1375 self.assertEqual(a - (a - week), week)
1376 self.assertEqual(a - (a - day), day)
1377 self.assertEqual(a - (a - hour), hour)
1378 self.assertEqual(a - (a - millisec), millisec)
1379 self.assertEqual(a + (week + day + hour + millisec),
1380 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1381 self.assertEqual(a + (week + day + hour + millisec),
1382 (((a + week) + day) + hour) + millisec)
1383 self.assertEqual(a - (week + day + hour + millisec),
1384 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1385 self.assertEqual(a - (week + day + hour + millisec),
1386 (((a - week) - day) - hour) - millisec)
1387 # Add/sub ints, longs, floats should be illegal
1388 for i in 1, 1L, 1.0:
1389 self.assertRaises(TypeError, lambda: a+i)
1390 self.assertRaises(TypeError, lambda: a-i)
1391 self.assertRaises(TypeError, lambda: i+a)
1392 self.assertRaises(TypeError, lambda: i-a)
1394 # delta - datetime is senseless.
1395 self.assertRaises(TypeError, lambda: day - a)
1396 # mixing datetime and (delta or datetime) via * or // is senseless
1397 self.assertRaises(TypeError, lambda: day * a)
1398 self.assertRaises(TypeError, lambda: a * day)
1399 self.assertRaises(TypeError, lambda: day // a)
1400 self.assertRaises(TypeError, lambda: a // day)
1401 self.assertRaises(TypeError, lambda: a * a)
1402 self.assertRaises(TypeError, lambda: a // a)
1403 # datetime + datetime is senseless
1404 self.assertRaises(TypeError, lambda: a + a)
1406 def test_pickling(self):
1407 args = 6, 7, 23, 20, 59, 1, 64**2
1408 orig = self.theclass(*args)
1409 for pickler, unpickler, proto in pickle_choices:
1410 green = pickler.dumps(orig, proto)
1411 derived = unpickler.loads(green)
1412 self.assertEqual(orig, derived)
1414 def test_more_pickling(self):
1415 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
1416 s = pickle.dumps(a)
1417 b = pickle.loads(s)
1418 self.assertEqual(b.year, 2003)
1419 self.assertEqual(b.month, 2)
1420 self.assertEqual(b.day, 7)
1422 def test_pickling_subclass_datetime(self):
1423 args = 6, 7, 23, 20, 59, 1, 64**2
1424 orig = SubclassDatetime(*args)
1425 for pickler, unpickler, proto in pickle_choices:
1426 green = pickler.dumps(orig, proto)
1427 derived = unpickler.loads(green)
1428 self.assertEqual(orig, derived)
1430 def test_more_compare(self):
1431 # The test_compare() inherited from TestDate covers the error cases.
1432 # We just want to test lexicographic ordering on the members datetime
1433 # has that date lacks.
1434 args = [2000, 11, 29, 20, 58, 16, 999998]
1435 t1 = self.theclass(*args)
1436 t2 = self.theclass(*args)
1437 self.assertTrue(t1 == t2)
1438 self.assertTrue(t1 <= t2)
1439 self.assertTrue(t1 >= t2)
1440 self.assertTrue(not t1 != t2)
1441 self.assertTrue(not t1 < t2)
1442 self.assertTrue(not t1 > t2)
1443 self.assertEqual(cmp(t1, t2), 0)
1444 self.assertEqual(cmp(t2, t1), 0)
1446 for i in range(len(args)):
1447 newargs = args[:]
1448 newargs[i] = args[i] + 1
1449 t2 = self.theclass(*newargs) # this is larger than t1
1450 self.assertTrue(t1 < t2)
1451 self.assertTrue(t2 > t1)
1452 self.assertTrue(t1 <= t2)
1453 self.assertTrue(t2 >= t1)
1454 self.assertTrue(t1 != t2)
1455 self.assertTrue(t2 != t1)
1456 self.assertTrue(not t1 == t2)
1457 self.assertTrue(not t2 == t1)
1458 self.assertTrue(not t1 > t2)
1459 self.assertTrue(not t2 < t1)
1460 self.assertTrue(not t1 >= t2)
1461 self.assertTrue(not t2 <= t1)
1462 self.assertEqual(cmp(t1, t2), -1)
1463 self.assertEqual(cmp(t2, t1), 1)
1466 # A helper for timestamp constructor tests.
1467 def verify_field_equality(self, expected, got):
1468 self.assertEqual(expected.tm_year, got.year)
1469 self.assertEqual(expected.tm_mon, got.month)
1470 self.assertEqual(expected.tm_mday, got.day)
1471 self.assertEqual(expected.tm_hour, got.hour)
1472 self.assertEqual(expected.tm_min, got.minute)
1473 self.assertEqual(expected.tm_sec, got.second)
1475 def test_fromtimestamp(self):
1476 import time
1478 ts = time.time()
1479 expected = time.localtime(ts)
1480 got = self.theclass.fromtimestamp(ts)
1481 self.verify_field_equality(expected, got)
1483 def test_utcfromtimestamp(self):
1484 import time
1486 ts = time.time()
1487 expected = time.gmtime(ts)
1488 got = self.theclass.utcfromtimestamp(ts)
1489 self.verify_field_equality(expected, got)
1491 def test_microsecond_rounding(self):
1492 # Test whether fromtimestamp "rounds up" floats that are less
1493 # than one microsecond smaller than an integer.
1494 self.assertEquals(self.theclass.fromtimestamp(0.9999999),
1495 self.theclass.fromtimestamp(1))
1497 def test_insane_fromtimestamp(self):
1498 # It's possible that some platform maps time_t to double,
1499 # and that this test will fail there. This test should
1500 # exempt such platforms (provided they return reasonable
1501 # results!).
1502 for insane in -1e200, 1e200:
1503 self.assertRaises(ValueError, self.theclass.fromtimestamp,
1504 insane)
1506 def test_insane_utcfromtimestamp(self):
1507 # It's possible that some platform maps time_t to double,
1508 # and that this test will fail there. This test should
1509 # exempt such platforms (provided they return reasonable
1510 # results!).
1511 for insane in -1e200, 1e200:
1512 self.assertRaises(ValueError, self.theclass.utcfromtimestamp,
1513 insane)
1514 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
1515 def test_negative_float_fromtimestamp(self):
1516 # The result is tz-dependent; at least test that this doesn't
1517 # fail (like it did before bug 1646728 was fixed).
1518 self.theclass.fromtimestamp(-1.05)
1520 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
1521 def test_negative_float_utcfromtimestamp(self):
1522 d = self.theclass.utcfromtimestamp(-1.05)
1523 self.assertEquals(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
1525 def test_utcnow(self):
1526 import time
1528 # Call it a success if utcnow() and utcfromtimestamp() are within
1529 # a second of each other.
1530 tolerance = timedelta(seconds=1)
1531 for dummy in range(3):
1532 from_now = self.theclass.utcnow()
1533 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1534 if abs(from_timestamp - from_now) <= tolerance:
1535 break
1536 # Else try again a few times.
1537 self.assertTrue(abs(from_timestamp - from_now) <= tolerance)
1539 def test_strptime(self):
1540 import _strptime
1542 string = '2004-12-01 13:02:47.197'
1543 format = '%Y-%m-%d %H:%M:%S.%f'
1544 result, frac = _strptime._strptime(string, format)
1545 expected = self.theclass(*(result[0:6]+(frac,)))
1546 got = self.theclass.strptime(string, format)
1547 self.assertEqual(expected, got)
1549 def test_more_timetuple(self):
1550 # This tests fields beyond those tested by the TestDate.test_timetuple.
1551 t = self.theclass(2004, 12, 31, 6, 22, 33)
1552 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
1553 self.assertEqual(t.timetuple(),
1554 (t.year, t.month, t.day,
1555 t.hour, t.minute, t.second,
1556 t.weekday(),
1557 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
1558 -1))
1559 tt = t.timetuple()
1560 self.assertEqual(tt.tm_year, t.year)
1561 self.assertEqual(tt.tm_mon, t.month)
1562 self.assertEqual(tt.tm_mday, t.day)
1563 self.assertEqual(tt.tm_hour, t.hour)
1564 self.assertEqual(tt.tm_min, t.minute)
1565 self.assertEqual(tt.tm_sec, t.second)
1566 self.assertEqual(tt.tm_wday, t.weekday())
1567 self.assertEqual(tt.tm_yday, t.toordinal() -
1568 date(t.year, 1, 1).toordinal() + 1)
1569 self.assertEqual(tt.tm_isdst, -1)
1571 def test_more_strftime(self):
1572 # This tests fields beyond those tested by the TestDate.test_strftime.
1573 t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
1574 self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
1575 "12 31 04 000047 33 22 06 366")
1577 def test_extract(self):
1578 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1579 self.assertEqual(dt.date(), date(2002, 3, 4))
1580 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
1582 def test_combine(self):
1583 d = date(2002, 3, 4)
1584 t = time(18, 45, 3, 1234)
1585 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1586 combine = self.theclass.combine
1587 dt = combine(d, t)
1588 self.assertEqual(dt, expected)
1590 dt = combine(time=t, date=d)
1591 self.assertEqual(dt, expected)
1593 self.assertEqual(d, dt.date())
1594 self.assertEqual(t, dt.time())
1595 self.assertEqual(dt, combine(dt.date(), dt.time()))
1597 self.assertRaises(TypeError, combine) # need an arg
1598 self.assertRaises(TypeError, combine, d) # need two args
1599 self.assertRaises(TypeError, combine, t, d) # args reversed
1600 self.assertRaises(TypeError, combine, d, t, 1) # too many args
1601 self.assertRaises(TypeError, combine, "date", "time") # wrong types
1603 def test_replace(self):
1604 cls = self.theclass
1605 args = [1, 2, 3, 4, 5, 6, 7]
1606 base = cls(*args)
1607 self.assertEqual(base, base.replace())
1609 i = 0
1610 for name, newval in (("year", 2),
1611 ("month", 3),
1612 ("day", 4),
1613 ("hour", 5),
1614 ("minute", 6),
1615 ("second", 7),
1616 ("microsecond", 8)):
1617 newargs = args[:]
1618 newargs[i] = newval
1619 expected = cls(*newargs)
1620 got = base.replace(**{name: newval})
1621 self.assertEqual(expected, got)
1622 i += 1
1624 # Out of bounds.
1625 base = cls(2000, 2, 29)
1626 self.assertRaises(ValueError, base.replace, year=2001)
1628 def test_astimezone(self):
1629 # Pretty boring! The TZ test is more interesting here. astimezone()
1630 # simply can't be applied to a naive object.
1631 dt = self.theclass.now()
1632 f = FixedOffset(44, "")
1633 self.assertRaises(TypeError, dt.astimezone) # not enough args
1634 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
1635 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
1636 self.assertRaises(ValueError, dt.astimezone, f) # naive
1637 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
1639 class Bogus(tzinfo):
1640 def utcoffset(self, dt): return None
1641 def dst(self, dt): return timedelta(0)
1642 bog = Bogus()
1643 self.assertRaises(ValueError, dt.astimezone, bog) # naive
1645 class AlsoBogus(tzinfo):
1646 def utcoffset(self, dt): return timedelta(0)
1647 def dst(self, dt): return None
1648 alsobog = AlsoBogus()
1649 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
1651 def test_subclass_datetime(self):
1653 class C(self.theclass):
1654 theAnswer = 42
1656 def __new__(cls, *args, **kws):
1657 temp = kws.copy()
1658 extra = temp.pop('extra')
1659 result = self.theclass.__new__(cls, *args, **temp)
1660 result.extra = extra
1661 return result
1663 def newmeth(self, start):
1664 return start + self.year + self.month + self.second
1666 args = 2003, 4, 14, 12, 13, 41
1668 dt1 = self.theclass(*args)
1669 dt2 = C(*args, **{'extra': 7})
1671 self.assertEqual(dt2.__class__, C)
1672 self.assertEqual(dt2.theAnswer, 42)
1673 self.assertEqual(dt2.extra, 7)
1674 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1675 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
1676 dt1.second - 7)
1678 class SubclassTime(time):
1679 sub_var = 1
1681 class TestTime(HarmlessMixedComparison, unittest.TestCase):
1683 theclass = time
1685 def test_basic_attributes(self):
1686 t = self.theclass(12, 0)
1687 self.assertEqual(t.hour, 12)
1688 self.assertEqual(t.minute, 0)
1689 self.assertEqual(t.second, 0)
1690 self.assertEqual(t.microsecond, 0)
1692 def test_basic_attributes_nonzero(self):
1693 # Make sure all attributes are non-zero so bugs in
1694 # bit-shifting access show up.
1695 t = self.theclass(12, 59, 59, 8000)
1696 self.assertEqual(t.hour, 12)
1697 self.assertEqual(t.minute, 59)
1698 self.assertEqual(t.second, 59)
1699 self.assertEqual(t.microsecond, 8000)
1701 def test_roundtrip(self):
1702 t = self.theclass(1, 2, 3, 4)
1704 # Verify t -> string -> time identity.
1705 s = repr(t)
1706 self.assertTrue(s.startswith('datetime.'))
1707 s = s[9:]
1708 t2 = eval(s)
1709 self.assertEqual(t, t2)
1711 # Verify identity via reconstructing from pieces.
1712 t2 = self.theclass(t.hour, t.minute, t.second,
1713 t.microsecond)
1714 self.assertEqual(t, t2)
1716 def test_comparing(self):
1717 args = [1, 2, 3, 4]
1718 t1 = self.theclass(*args)
1719 t2 = self.theclass(*args)
1720 self.assertTrue(t1 == t2)
1721 self.assertTrue(t1 <= t2)
1722 self.assertTrue(t1 >= t2)
1723 self.assertTrue(not t1 != t2)
1724 self.assertTrue(not t1 < t2)
1725 self.assertTrue(not t1 > t2)
1726 self.assertEqual(cmp(t1, t2), 0)
1727 self.assertEqual(cmp(t2, t1), 0)
1729 for i in range(len(args)):
1730 newargs = args[:]
1731 newargs[i] = args[i] + 1
1732 t2 = self.theclass(*newargs) # this is larger than t1
1733 self.assertTrue(t1 < t2)
1734 self.assertTrue(t2 > t1)
1735 self.assertTrue(t1 <= t2)
1736 self.assertTrue(t2 >= t1)
1737 self.assertTrue(t1 != t2)
1738 self.assertTrue(t2 != t1)
1739 self.assertTrue(not t1 == t2)
1740 self.assertTrue(not t2 == t1)
1741 self.assertTrue(not t1 > t2)
1742 self.assertTrue(not t2 < t1)
1743 self.assertTrue(not t1 >= t2)
1744 self.assertTrue(not t2 <= t1)
1745 self.assertEqual(cmp(t1, t2), -1)
1746 self.assertEqual(cmp(t2, t1), 1)
1748 for badarg in OTHERSTUFF:
1749 self.assertEqual(t1 == badarg, False)
1750 self.assertEqual(t1 != badarg, True)
1751 self.assertEqual(badarg == t1, False)
1752 self.assertEqual(badarg != t1, True)
1754 self.assertRaises(TypeError, lambda: t1 <= badarg)
1755 self.assertRaises(TypeError, lambda: t1 < badarg)
1756 self.assertRaises(TypeError, lambda: t1 > badarg)
1757 self.assertRaises(TypeError, lambda: t1 >= badarg)
1758 self.assertRaises(TypeError, lambda: badarg <= t1)
1759 self.assertRaises(TypeError, lambda: badarg < t1)
1760 self.assertRaises(TypeError, lambda: badarg > t1)
1761 self.assertRaises(TypeError, lambda: badarg >= t1)
1763 def test_bad_constructor_arguments(self):
1764 # bad hours
1765 self.theclass(0, 0) # no exception
1766 self.theclass(23, 0) # no exception
1767 self.assertRaises(ValueError, self.theclass, -1, 0)
1768 self.assertRaises(ValueError, self.theclass, 24, 0)
1769 # bad minutes
1770 self.theclass(23, 0) # no exception
1771 self.theclass(23, 59) # no exception
1772 self.assertRaises(ValueError, self.theclass, 23, -1)
1773 self.assertRaises(ValueError, self.theclass, 23, 60)
1774 # bad seconds
1775 self.theclass(23, 59, 0) # no exception
1776 self.theclass(23, 59, 59) # no exception
1777 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
1778 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
1779 # bad microseconds
1780 self.theclass(23, 59, 59, 0) # no exception
1781 self.theclass(23, 59, 59, 999999) # no exception
1782 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
1783 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
1785 def test_hash_equality(self):
1786 d = self.theclass(23, 30, 17)
1787 e = self.theclass(23, 30, 17)
1788 self.assertEqual(d, e)
1789 self.assertEqual(hash(d), hash(e))
1791 dic = {d: 1}
1792 dic[e] = 2
1793 self.assertEqual(len(dic), 1)
1794 self.assertEqual(dic[d], 2)
1795 self.assertEqual(dic[e], 2)
1797 d = self.theclass(0, 5, 17)
1798 e = self.theclass(0, 5, 17)
1799 self.assertEqual(d, e)
1800 self.assertEqual(hash(d), hash(e))
1802 dic = {d: 1}
1803 dic[e] = 2
1804 self.assertEqual(len(dic), 1)
1805 self.assertEqual(dic[d], 2)
1806 self.assertEqual(dic[e], 2)
1808 def test_isoformat(self):
1809 t = self.theclass(4, 5, 1, 123)
1810 self.assertEqual(t.isoformat(), "04:05:01.000123")
1811 self.assertEqual(t.isoformat(), str(t))
1813 t = self.theclass()
1814 self.assertEqual(t.isoformat(), "00:00:00")
1815 self.assertEqual(t.isoformat(), str(t))
1817 t = self.theclass(microsecond=1)
1818 self.assertEqual(t.isoformat(), "00:00:00.000001")
1819 self.assertEqual(t.isoformat(), str(t))
1821 t = self.theclass(microsecond=10)
1822 self.assertEqual(t.isoformat(), "00:00:00.000010")
1823 self.assertEqual(t.isoformat(), str(t))
1825 t = self.theclass(microsecond=100)
1826 self.assertEqual(t.isoformat(), "00:00:00.000100")
1827 self.assertEqual(t.isoformat(), str(t))
1829 t = self.theclass(microsecond=1000)
1830 self.assertEqual(t.isoformat(), "00:00:00.001000")
1831 self.assertEqual(t.isoformat(), str(t))
1833 t = self.theclass(microsecond=10000)
1834 self.assertEqual(t.isoformat(), "00:00:00.010000")
1835 self.assertEqual(t.isoformat(), str(t))
1837 t = self.theclass(microsecond=100000)
1838 self.assertEqual(t.isoformat(), "00:00:00.100000")
1839 self.assertEqual(t.isoformat(), str(t))
1841 def test_1653736(self):
1842 # verify it doesn't accept extra keyword arguments
1843 t = self.theclass(second=1)
1844 self.assertRaises(TypeError, t.isoformat, foo=3)
1846 def test_strftime(self):
1847 t = self.theclass(1, 2, 3, 4)
1848 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
1849 # A naive object replaces %z and %Z with empty strings.
1850 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1852 def test_format(self):
1853 t = self.theclass(1, 2, 3, 4)
1854 self.assertEqual(t.__format__(''), str(t))
1856 # check that a derived class's __str__() gets called
1857 class A(self.theclass):
1858 def __str__(self):
1859 return 'A'
1860 a = A(1, 2, 3, 4)
1861 self.assertEqual(a.__format__(''), 'A')
1863 # check that a derived class's strftime gets called
1864 class B(self.theclass):
1865 def strftime(self, format_spec):
1866 return 'B'
1867 b = B(1, 2, 3, 4)
1868 self.assertEqual(b.__format__(''), str(t))
1870 for fmt in ['%H %M %S',
1872 self.assertEqual(t.__format__(fmt), t.strftime(fmt))
1873 self.assertEqual(a.__format__(fmt), t.strftime(fmt))
1874 self.assertEqual(b.__format__(fmt), 'B')
1876 def test_str(self):
1877 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
1878 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
1879 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
1880 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
1881 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
1883 def test_repr(self):
1884 name = 'datetime.' + self.theclass.__name__
1885 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
1886 "%s(1, 2, 3, 4)" % name)
1887 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
1888 "%s(10, 2, 3, 4000)" % name)
1889 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
1890 "%s(0, 2, 3, 400000)" % name)
1891 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
1892 "%s(12, 2, 3)" % name)
1893 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
1894 "%s(23, 15)" % name)
1896 def test_resolution_info(self):
1897 self.assertIsInstance(self.theclass.min, self.theclass)
1898 self.assertIsInstance(self.theclass.max, self.theclass)
1899 self.assertIsInstance(self.theclass.resolution, timedelta)
1900 self.assertTrue(self.theclass.max > self.theclass.min)
1902 def test_pickling(self):
1903 args = 20, 59, 16, 64**2
1904 orig = self.theclass(*args)
1905 for pickler, unpickler, proto in pickle_choices:
1906 green = pickler.dumps(orig, proto)
1907 derived = unpickler.loads(green)
1908 self.assertEqual(orig, derived)
1910 def test_pickling_subclass_time(self):
1911 args = 20, 59, 16, 64**2
1912 orig = SubclassTime(*args)
1913 for pickler, unpickler, proto in pickle_choices:
1914 green = pickler.dumps(orig, proto)
1915 derived = unpickler.loads(green)
1916 self.assertEqual(orig, derived)
1918 def test_bool(self):
1919 cls = self.theclass
1920 self.assertTrue(cls(1))
1921 self.assertTrue(cls(0, 1))
1922 self.assertTrue(cls(0, 0, 1))
1923 self.assertTrue(cls(0, 0, 0, 1))
1924 self.assertTrue(not cls(0))
1925 self.assertTrue(not cls())
1927 def test_replace(self):
1928 cls = self.theclass
1929 args = [1, 2, 3, 4]
1930 base = cls(*args)
1931 self.assertEqual(base, base.replace())
1933 i = 0
1934 for name, newval in (("hour", 5),
1935 ("minute", 6),
1936 ("second", 7),
1937 ("microsecond", 8)):
1938 newargs = args[:]
1939 newargs[i] = newval
1940 expected = cls(*newargs)
1941 got = base.replace(**{name: newval})
1942 self.assertEqual(expected, got)
1943 i += 1
1945 # Out of bounds.
1946 base = cls(1)
1947 self.assertRaises(ValueError, base.replace, hour=24)
1948 self.assertRaises(ValueError, base.replace, minute=-1)
1949 self.assertRaises(ValueError, base.replace, second=100)
1950 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1952 def test_subclass_time(self):
1954 class C(self.theclass):
1955 theAnswer = 42
1957 def __new__(cls, *args, **kws):
1958 temp = kws.copy()
1959 extra = temp.pop('extra')
1960 result = self.theclass.__new__(cls, *args, **temp)
1961 result.extra = extra
1962 return result
1964 def newmeth(self, start):
1965 return start + self.hour + self.second
1967 args = 4, 5, 6
1969 dt1 = self.theclass(*args)
1970 dt2 = C(*args, **{'extra': 7})
1972 self.assertEqual(dt2.__class__, C)
1973 self.assertEqual(dt2.theAnswer, 42)
1974 self.assertEqual(dt2.extra, 7)
1975 self.assertEqual(dt1.isoformat(), dt2.isoformat())
1976 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
1978 def test_backdoor_resistance(self):
1979 # see TestDate.test_backdoor_resistance().
1980 base = '2:59.0'
1981 for hour_byte in ' ', '9', chr(24), '\xff':
1982 self.assertRaises(TypeError, self.theclass,
1983 hour_byte + base[1:])
1985 # A mixin for classes with a tzinfo= argument. Subclasses must define
1986 # theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
1987 # must be legit (which is true for time and datetime).
1988 class TZInfoBase:
1990 def test_argument_passing(self):
1991 cls = self.theclass
1992 # A datetime passes itself on, a time passes None.
1993 class introspective(tzinfo):
1994 def tzname(self, dt): return dt and "real" or "none"
1995 def utcoffset(self, dt):
1996 return timedelta(minutes = dt and 42 or -42)
1997 dst = utcoffset
1999 obj = cls(1, 2, 3, tzinfo=introspective())
2001 expected = cls is time and "none" or "real"
2002 self.assertEqual(obj.tzname(), expected)
2004 expected = timedelta(minutes=(cls is time and -42 or 42))
2005 self.assertEqual(obj.utcoffset(), expected)
2006 self.assertEqual(obj.dst(), expected)
2008 def test_bad_tzinfo_classes(self):
2009 cls = self.theclass
2010 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
2012 class NiceTry(object):
2013 def __init__(self): pass
2014 def utcoffset(self, dt): pass
2015 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
2017 class BetterTry(tzinfo):
2018 def __init__(self): pass
2019 def utcoffset(self, dt): pass
2020 b = BetterTry()
2021 t = cls(1, 1, 1, tzinfo=b)
2022 self.assertTrue(t.tzinfo is b)
2024 def test_utc_offset_out_of_bounds(self):
2025 class Edgy(tzinfo):
2026 def __init__(self, offset):
2027 self.offset = timedelta(minutes=offset)
2028 def utcoffset(self, dt):
2029 return self.offset
2031 cls = self.theclass
2032 for offset, legit in ((-1440, False),
2033 (-1439, True),
2034 (1439, True),
2035 (1440, False)):
2036 if cls is time:
2037 t = cls(1, 2, 3, tzinfo=Edgy(offset))
2038 elif cls is datetime:
2039 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
2040 else:
2041 assert 0, "impossible"
2042 if legit:
2043 aofs = abs(offset)
2044 h, m = divmod(aofs, 60)
2045 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
2046 if isinstance(t, datetime):
2047 t = t.timetz()
2048 self.assertEqual(str(t), "01:02:03" + tag)
2049 else:
2050 self.assertRaises(ValueError, str, t)
2052 def test_tzinfo_classes(self):
2053 cls = self.theclass
2054 class C1(tzinfo):
2055 def utcoffset(self, dt): return None
2056 def dst(self, dt): return None
2057 def tzname(self, dt): return None
2058 for t in (cls(1, 1, 1),
2059 cls(1, 1, 1, tzinfo=None),
2060 cls(1, 1, 1, tzinfo=C1())):
2061 self.assertTrue(t.utcoffset() is None)
2062 self.assertTrue(t.dst() is None)
2063 self.assertTrue(t.tzname() is None)
2065 class C3(tzinfo):
2066 def utcoffset(self, dt): return timedelta(minutes=-1439)
2067 def dst(self, dt): return timedelta(minutes=1439)
2068 def tzname(self, dt): return "aname"
2069 t = cls(1, 1, 1, tzinfo=C3())
2070 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
2071 self.assertEqual(t.dst(), timedelta(minutes=1439))
2072 self.assertEqual(t.tzname(), "aname")
2074 # Wrong types.
2075 class C4(tzinfo):
2076 def utcoffset(self, dt): return "aname"
2077 def dst(self, dt): return 7
2078 def tzname(self, dt): return 0
2079 t = cls(1, 1, 1, tzinfo=C4())
2080 self.assertRaises(TypeError, t.utcoffset)
2081 self.assertRaises(TypeError, t.dst)
2082 self.assertRaises(TypeError, t.tzname)
2084 # Offset out of range.
2085 class C6(tzinfo):
2086 def utcoffset(self, dt): return timedelta(hours=-24)
2087 def dst(self, dt): return timedelta(hours=24)
2088 t = cls(1, 1, 1, tzinfo=C6())
2089 self.assertRaises(ValueError, t.utcoffset)
2090 self.assertRaises(ValueError, t.dst)
2092 # Not a whole number of minutes.
2093 class C7(tzinfo):
2094 def utcoffset(self, dt): return timedelta(seconds=61)
2095 def dst(self, dt): return timedelta(microseconds=-81)
2096 t = cls(1, 1, 1, tzinfo=C7())
2097 self.assertRaises(ValueError, t.utcoffset)
2098 self.assertRaises(ValueError, t.dst)
2100 def test_aware_compare(self):
2101 cls = self.theclass
2103 # Ensure that utcoffset() gets ignored if the comparands have
2104 # the same tzinfo member.
2105 class OperandDependentOffset(tzinfo):
2106 def utcoffset(self, t):
2107 if t.minute < 10:
2108 # d0 and d1 equal after adjustment
2109 return timedelta(minutes=t.minute)
2110 else:
2111 # d2 off in the weeds
2112 return timedelta(minutes=59)
2114 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
2115 d0 = base.replace(minute=3)
2116 d1 = base.replace(minute=9)
2117 d2 = base.replace(minute=11)
2118 for x in d0, d1, d2:
2119 for y in d0, d1, d2:
2120 got = cmp(x, y)
2121 expected = cmp(x.minute, y.minute)
2122 self.assertEqual(got, expected)
2124 # However, if they're different members, uctoffset is not ignored.
2125 # Note that a time can't actually have an operand-depedent offset,
2126 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
2127 # so skip this test for time.
2128 if cls is not time:
2129 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2130 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2131 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2132 for x in d0, d1, d2:
2133 for y in d0, d1, d2:
2134 got = cmp(x, y)
2135 if (x is d0 or x is d1) and (y is d0 or y is d1):
2136 expected = 0
2137 elif x is y is d2:
2138 expected = 0
2139 elif x is d2:
2140 expected = -1
2141 else:
2142 assert y is d2
2143 expected = 1
2144 self.assertEqual(got, expected)
2147 # Testing time objects with a non-None tzinfo.
2148 class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
2149 theclass = time
2151 def test_empty(self):
2152 t = self.theclass()
2153 self.assertEqual(t.hour, 0)
2154 self.assertEqual(t.minute, 0)
2155 self.assertEqual(t.second, 0)
2156 self.assertEqual(t.microsecond, 0)
2157 self.assertTrue(t.tzinfo is None)
2159 def test_zones(self):
2160 est = FixedOffset(-300, "EST", 1)
2161 utc = FixedOffset(0, "UTC", -2)
2162 met = FixedOffset(60, "MET", 3)
2163 t1 = time( 7, 47, tzinfo=est)
2164 t2 = time(12, 47, tzinfo=utc)
2165 t3 = time(13, 47, tzinfo=met)
2166 t4 = time(microsecond=40)
2167 t5 = time(microsecond=40, tzinfo=utc)
2169 self.assertEqual(t1.tzinfo, est)
2170 self.assertEqual(t2.tzinfo, utc)
2171 self.assertEqual(t3.tzinfo, met)
2172 self.assertTrue(t4.tzinfo is None)
2173 self.assertEqual(t5.tzinfo, utc)
2175 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2176 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2177 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
2178 self.assertTrue(t4.utcoffset() is None)
2179 self.assertRaises(TypeError, t1.utcoffset, "no args")
2181 self.assertEqual(t1.tzname(), "EST")
2182 self.assertEqual(t2.tzname(), "UTC")
2183 self.assertEqual(t3.tzname(), "MET")
2184 self.assertTrue(t4.tzname() is None)
2185 self.assertRaises(TypeError, t1.tzname, "no args")
2187 self.assertEqual(t1.dst(), timedelta(minutes=1))
2188 self.assertEqual(t2.dst(), timedelta(minutes=-2))
2189 self.assertEqual(t3.dst(), timedelta(minutes=3))
2190 self.assertTrue(t4.dst() is None)
2191 self.assertRaises(TypeError, t1.dst, "no args")
2193 self.assertEqual(hash(t1), hash(t2))
2194 self.assertEqual(hash(t1), hash(t3))
2195 self.assertEqual(hash(t2), hash(t3))
2197 self.assertEqual(t1, t2)
2198 self.assertEqual(t1, t3)
2199 self.assertEqual(t2, t3)
2200 self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
2201 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
2202 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
2204 self.assertEqual(str(t1), "07:47:00-05:00")
2205 self.assertEqual(str(t2), "12:47:00+00:00")
2206 self.assertEqual(str(t3), "13:47:00+01:00")
2207 self.assertEqual(str(t4), "00:00:00.000040")
2208 self.assertEqual(str(t5), "00:00:00.000040+00:00")
2210 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
2211 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
2212 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
2213 self.assertEqual(t4.isoformat(), "00:00:00.000040")
2214 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
2216 d = 'datetime.time'
2217 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
2218 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
2219 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
2220 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
2221 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
2223 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
2224 "07:47:00 %Z=EST %z=-0500")
2225 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
2226 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
2228 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
2229 t1 = time(23, 59, tzinfo=yuck)
2230 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
2231 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
2233 # Check that an invalid tzname result raises an exception.
2234 class Badtzname(tzinfo):
2235 def tzname(self, dt): return 42
2236 t = time(2, 3, 4, tzinfo=Badtzname())
2237 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
2238 self.assertRaises(TypeError, t.strftime, "%Z")
2240 def test_hash_edge_cases(self):
2241 # Offsets that overflow a basic time.
2242 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2243 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2244 self.assertEqual(hash(t1), hash(t2))
2246 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2247 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2248 self.assertEqual(hash(t1), hash(t2))
2250 def test_pickling(self):
2251 # Try one without a tzinfo.
2252 args = 20, 59, 16, 64**2
2253 orig = self.theclass(*args)
2254 for pickler, unpickler, proto in pickle_choices:
2255 green = pickler.dumps(orig, proto)
2256 derived = unpickler.loads(green)
2257 self.assertEqual(orig, derived)
2259 # Try one with a tzinfo.
2260 tinfo = PicklableFixedOffset(-300, 'cookie')
2261 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
2262 for pickler, unpickler, proto in pickle_choices:
2263 green = pickler.dumps(orig, proto)
2264 derived = unpickler.loads(green)
2265 self.assertEqual(orig, derived)
2266 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
2267 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2268 self.assertEqual(derived.tzname(), 'cookie')
2270 def test_more_bool(self):
2271 # Test cases with non-None tzinfo.
2272 cls = self.theclass
2274 t = cls(0, tzinfo=FixedOffset(-300, ""))
2275 self.assertTrue(t)
2277 t = cls(5, tzinfo=FixedOffset(-300, ""))
2278 self.assertTrue(t)
2280 t = cls(5, tzinfo=FixedOffset(300, ""))
2281 self.assertTrue(not t)
2283 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2284 self.assertTrue(not t)
2286 # Mostly ensuring this doesn't overflow internally.
2287 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
2288 self.assertTrue(t)
2290 # But this should yield a value error -- the utcoffset is bogus.
2291 t = cls(0, tzinfo=FixedOffset(24*60, ""))
2292 self.assertRaises(ValueError, lambda: bool(t))
2294 # Likewise.
2295 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
2296 self.assertRaises(ValueError, lambda: bool(t))
2298 def test_replace(self):
2299 cls = self.theclass
2300 z100 = FixedOffset(100, "+100")
2301 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2302 args = [1, 2, 3, 4, z100]
2303 base = cls(*args)
2304 self.assertEqual(base, base.replace())
2306 i = 0
2307 for name, newval in (("hour", 5),
2308 ("minute", 6),
2309 ("second", 7),
2310 ("microsecond", 8),
2311 ("tzinfo", zm200)):
2312 newargs = args[:]
2313 newargs[i] = newval
2314 expected = cls(*newargs)
2315 got = base.replace(**{name: newval})
2316 self.assertEqual(expected, got)
2317 i += 1
2319 # Ensure we can get rid of a tzinfo.
2320 self.assertEqual(base.tzname(), "+100")
2321 base2 = base.replace(tzinfo=None)
2322 self.assertTrue(base2.tzinfo is None)
2323 self.assertTrue(base2.tzname() is None)
2325 # Ensure we can add one.
2326 base3 = base2.replace(tzinfo=z100)
2327 self.assertEqual(base, base3)
2328 self.assertTrue(base.tzinfo is base3.tzinfo)
2330 # Out of bounds.
2331 base = cls(1)
2332 self.assertRaises(ValueError, base.replace, hour=24)
2333 self.assertRaises(ValueError, base.replace, minute=-1)
2334 self.assertRaises(ValueError, base.replace, second=100)
2335 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2337 def test_mixed_compare(self):
2338 t1 = time(1, 2, 3)
2339 t2 = time(1, 2, 3)
2340 self.assertEqual(t1, t2)
2341 t2 = t2.replace(tzinfo=None)
2342 self.assertEqual(t1, t2)
2343 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2344 self.assertEqual(t1, t2)
2345 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2346 self.assertRaises(TypeError, lambda: t1 == t2)
2348 # In time w/ identical tzinfo objects, utcoffset is ignored.
2349 class Varies(tzinfo):
2350 def __init__(self):
2351 self.offset = timedelta(minutes=22)
2352 def utcoffset(self, t):
2353 self.offset += timedelta(minutes=1)
2354 return self.offset
2356 v = Varies()
2357 t1 = t2.replace(tzinfo=v)
2358 t2 = t2.replace(tzinfo=v)
2359 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2360 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2361 self.assertEqual(t1, t2)
2363 # But if they're not identical, it isn't ignored.
2364 t2 = t2.replace(tzinfo=Varies())
2365 self.assertTrue(t1 < t2) # t1's offset counter still going up
2367 def test_subclass_timetz(self):
2369 class C(self.theclass):
2370 theAnswer = 42
2372 def __new__(cls, *args, **kws):
2373 temp = kws.copy()
2374 extra = temp.pop('extra')
2375 result = self.theclass.__new__(cls, *args, **temp)
2376 result.extra = extra
2377 return result
2379 def newmeth(self, start):
2380 return start + self.hour + self.second
2382 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2384 dt1 = self.theclass(*args)
2385 dt2 = C(*args, **{'extra': 7})
2387 self.assertEqual(dt2.__class__, C)
2388 self.assertEqual(dt2.theAnswer, 42)
2389 self.assertEqual(dt2.extra, 7)
2390 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2391 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2394 # Testing datetime objects with a non-None tzinfo.
2396 class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
2397 theclass = datetime
2399 def test_trivial(self):
2400 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2401 self.assertEqual(dt.year, 1)
2402 self.assertEqual(dt.month, 2)
2403 self.assertEqual(dt.day, 3)
2404 self.assertEqual(dt.hour, 4)
2405 self.assertEqual(dt.minute, 5)
2406 self.assertEqual(dt.second, 6)
2407 self.assertEqual(dt.microsecond, 7)
2408 self.assertEqual(dt.tzinfo, None)
2410 def test_even_more_compare(self):
2411 # The test_compare() and test_more_compare() inherited from TestDate
2412 # and TestDateTime covered non-tzinfo cases.
2414 # Smallest possible after UTC adjustment.
2415 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2416 # Largest possible after UTC adjustment.
2417 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2418 tzinfo=FixedOffset(-1439, ""))
2420 # Make sure those compare correctly, and w/o overflow.
2421 self.assertTrue(t1 < t2)
2422 self.assertTrue(t1 != t2)
2423 self.assertTrue(t2 > t1)
2425 self.assertTrue(t1 == t1)
2426 self.assertTrue(t2 == t2)
2428 # Equal afer adjustment.
2429 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2430 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2431 self.assertEqual(t1, t2)
2433 # Change t1 not to subtract a minute, and t1 should be larger.
2434 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2435 self.assertTrue(t1 > t2)
2437 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2438 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2439 self.assertTrue(t1 < t2)
2441 # Back to the original t1, but make seconds resolve it.
2442 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2443 second=1)
2444 self.assertTrue(t1 > t2)
2446 # Likewise, but make microseconds resolve it.
2447 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2448 microsecond=1)
2449 self.assertTrue(t1 > t2)
2451 # Make t2 naive and it should fail.
2452 t2 = self.theclass.min
2453 self.assertRaises(TypeError, lambda: t1 == t2)
2454 self.assertEqual(t2, t2)
2456 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2457 class Naive(tzinfo):
2458 def utcoffset(self, dt): return None
2459 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
2460 self.assertRaises(TypeError, lambda: t1 == t2)
2461 self.assertEqual(t2, t2)
2463 # OTOH, it's OK to compare two of these mixing the two ways of being
2464 # naive.
2465 t1 = self.theclass(5, 6, 7)
2466 self.assertEqual(t1, t2)
2468 # Try a bogus uctoffset.
2469 class Bogus(tzinfo):
2470 def utcoffset(self, dt):
2471 return timedelta(minutes=1440) # out of bounds
2472 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2473 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
2474 self.assertRaises(ValueError, lambda: t1 == t2)
2476 def test_pickling(self):
2477 # Try one without a tzinfo.
2478 args = 6, 7, 23, 20, 59, 1, 64**2
2479 orig = self.theclass(*args)
2480 for pickler, unpickler, proto in pickle_choices:
2481 green = pickler.dumps(orig, proto)
2482 derived = unpickler.loads(green)
2483 self.assertEqual(orig, derived)
2485 # Try one with a tzinfo.
2486 tinfo = PicklableFixedOffset(-300, 'cookie')
2487 orig = self.theclass(*args, **{'tzinfo': tinfo})
2488 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
2489 for pickler, unpickler, proto in pickle_choices:
2490 green = pickler.dumps(orig, proto)
2491 derived = unpickler.loads(green)
2492 self.assertEqual(orig, derived)
2493 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
2494 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2495 self.assertEqual(derived.tzname(), 'cookie')
2497 def test_extreme_hashes(self):
2498 # If an attempt is made to hash these via subtracting the offset
2499 # then hashing a datetime object, OverflowError results. The
2500 # Python implementation used to blow up here.
2501 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2502 hash(t)
2503 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2504 tzinfo=FixedOffset(-1439, ""))
2505 hash(t)
2507 # OTOH, an OOB offset should blow up.
2508 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2509 self.assertRaises(ValueError, hash, t)
2511 def test_zones(self):
2512 est = FixedOffset(-300, "EST")
2513 utc = FixedOffset(0, "UTC")
2514 met = FixedOffset(60, "MET")
2515 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2516 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2517 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
2518 self.assertEqual(t1.tzinfo, est)
2519 self.assertEqual(t2.tzinfo, utc)
2520 self.assertEqual(t3.tzinfo, met)
2521 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2522 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2523 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
2524 self.assertEqual(t1.tzname(), "EST")
2525 self.assertEqual(t2.tzname(), "UTC")
2526 self.assertEqual(t3.tzname(), "MET")
2527 self.assertEqual(hash(t1), hash(t2))
2528 self.assertEqual(hash(t1), hash(t3))
2529 self.assertEqual(hash(t2), hash(t3))
2530 self.assertEqual(t1, t2)
2531 self.assertEqual(t1, t3)
2532 self.assertEqual(t2, t3)
2533 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2534 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2535 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
2536 d = 'datetime.datetime(2002, 3, 19, '
2537 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2538 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2539 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2541 def test_combine(self):
2542 met = FixedOffset(60, "MET")
2543 d = date(2002, 3, 4)
2544 tz = time(18, 45, 3, 1234, tzinfo=met)
2545 dt = datetime.combine(d, tz)
2546 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
2547 tzinfo=met))
2549 def test_extract(self):
2550 met = FixedOffset(60, "MET")
2551 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2552 self.assertEqual(dt.date(), date(2002, 3, 4))
2553 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2554 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
2556 def test_tz_aware_arithmetic(self):
2557 import random
2559 now = self.theclass.now()
2560 tz55 = FixedOffset(-330, "west 5:30")
2561 timeaware = now.time().replace(tzinfo=tz55)
2562 nowaware = self.theclass.combine(now.date(), timeaware)
2563 self.assertTrue(nowaware.tzinfo is tz55)
2564 self.assertEqual(nowaware.timetz(), timeaware)
2566 # Can't mix aware and non-aware.
2567 self.assertRaises(TypeError, lambda: now - nowaware)
2568 self.assertRaises(TypeError, lambda: nowaware - now)
2570 # And adding datetime's doesn't make sense, aware or not.
2571 self.assertRaises(TypeError, lambda: now + nowaware)
2572 self.assertRaises(TypeError, lambda: nowaware + now)
2573 self.assertRaises(TypeError, lambda: nowaware + nowaware)
2575 # Subtracting should yield 0.
2576 self.assertEqual(now - now, timedelta(0))
2577 self.assertEqual(nowaware - nowaware, timedelta(0))
2579 # Adding a delta should preserve tzinfo.
2580 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
2581 nowawareplus = nowaware + delta
2582 self.assertTrue(nowaware.tzinfo is tz55)
2583 nowawareplus2 = delta + nowaware
2584 self.assertTrue(nowawareplus2.tzinfo is tz55)
2585 self.assertEqual(nowawareplus, nowawareplus2)
2587 # that - delta should be what we started with, and that - what we
2588 # started with should be delta.
2589 diff = nowawareplus - delta
2590 self.assertTrue(diff.tzinfo is tz55)
2591 self.assertEqual(nowaware, diff)
2592 self.assertRaises(TypeError, lambda: delta - nowawareplus)
2593 self.assertEqual(nowawareplus - nowaware, delta)
2595 # Make up a random timezone.
2596 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
2597 # Attach it to nowawareplus.
2598 nowawareplus = nowawareplus.replace(tzinfo=tzr)
2599 self.assertTrue(nowawareplus.tzinfo is tzr)
2600 # Make sure the difference takes the timezone adjustments into account.
2601 got = nowaware - nowawareplus
2602 # Expected: (nowaware base - nowaware offset) -
2603 # (nowawareplus base - nowawareplus offset) =
2604 # (nowaware base - nowawareplus base) +
2605 # (nowawareplus offset - nowaware offset) =
2606 # -delta + nowawareplus offset - nowaware offset
2607 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
2608 self.assertEqual(got, expected)
2610 # Try max possible difference.
2611 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
2612 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2613 tzinfo=FixedOffset(-1439, "max"))
2614 maxdiff = max - min
2615 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
2616 timedelta(minutes=2*1439))
2618 def test_tzinfo_now(self):
2619 meth = self.theclass.now
2620 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2621 base = meth()
2622 # Try with and without naming the keyword.
2623 off42 = FixedOffset(42, "42")
2624 another = meth(off42)
2625 again = meth(tz=off42)
2626 self.assertTrue(another.tzinfo is again.tzinfo)
2627 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
2628 # Bad argument with and w/o naming the keyword.
2629 self.assertRaises(TypeError, meth, 16)
2630 self.assertRaises(TypeError, meth, tzinfo=16)
2631 # Bad keyword name.
2632 self.assertRaises(TypeError, meth, tinfo=off42)
2633 # Too many args.
2634 self.assertRaises(TypeError, meth, off42, off42)
2636 # We don't know which time zone we're in, and don't have a tzinfo
2637 # class to represent it, so seeing whether a tz argument actually
2638 # does a conversion is tricky.
2639 weirdtz = FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0)
2640 utc = FixedOffset(0, "utc", 0)
2641 for dummy in range(3):
2642 now = datetime.now(weirdtz)
2643 self.assertTrue(now.tzinfo is weirdtz)
2644 utcnow = datetime.utcnow().replace(tzinfo=utc)
2645 now2 = utcnow.astimezone(weirdtz)
2646 if abs(now - now2) < timedelta(seconds=30):
2647 break
2648 # Else the code is broken, or more than 30 seconds passed between
2649 # calls; assuming the latter, just try again.
2650 else:
2651 # Three strikes and we're out.
2652 self.fail("utcnow(), now(tz), or astimezone() may be broken")
2654 def test_tzinfo_fromtimestamp(self):
2655 import time
2656 meth = self.theclass.fromtimestamp
2657 ts = time.time()
2658 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2659 base = meth(ts)
2660 # Try with and without naming the keyword.
2661 off42 = FixedOffset(42, "42")
2662 another = meth(ts, off42)
2663 again = meth(ts, tz=off42)
2664 self.assertTrue(another.tzinfo is again.tzinfo)
2665 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
2666 # Bad argument with and w/o naming the keyword.
2667 self.assertRaises(TypeError, meth, ts, 16)
2668 self.assertRaises(TypeError, meth, ts, tzinfo=16)
2669 # Bad keyword name.
2670 self.assertRaises(TypeError, meth, ts, tinfo=off42)
2671 # Too many args.
2672 self.assertRaises(TypeError, meth, ts, off42, off42)
2673 # Too few args.
2674 self.assertRaises(TypeError, meth)
2676 # Try to make sure tz= actually does some conversion.
2677 timestamp = 1000000000
2678 utcdatetime = datetime.utcfromtimestamp(timestamp)
2679 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
2680 # But on some flavor of Mac, it's nowhere near that. So we can't have
2681 # any idea here what time that actually is, we can only test that
2682 # relative changes match.
2683 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
2684 tz = FixedOffset(utcoffset, "tz", 0)
2685 expected = utcdatetime + utcoffset
2686 got = datetime.fromtimestamp(timestamp, tz)
2687 self.assertEqual(expected, got.replace(tzinfo=None))
2689 def test_tzinfo_utcnow(self):
2690 meth = self.theclass.utcnow
2691 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2692 base = meth()
2693 # Try with and without naming the keyword; for whatever reason,
2694 # utcnow() doesn't accept a tzinfo argument.
2695 off42 = FixedOffset(42, "42")
2696 self.assertRaises(TypeError, meth, off42)
2697 self.assertRaises(TypeError, meth, tzinfo=off42)
2699 def test_tzinfo_utcfromtimestamp(self):
2700 import time
2701 meth = self.theclass.utcfromtimestamp
2702 ts = time.time()
2703 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2704 base = meth(ts)
2705 # Try with and without naming the keyword; for whatever reason,
2706 # utcfromtimestamp() doesn't accept a tzinfo argument.
2707 off42 = FixedOffset(42, "42")
2708 self.assertRaises(TypeError, meth, ts, off42)
2709 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
2711 def test_tzinfo_timetuple(self):
2712 # TestDateTime tested most of this. datetime adds a twist to the
2713 # DST flag.
2714 class DST(tzinfo):
2715 def __init__(self, dstvalue):
2716 if isinstance(dstvalue, int):
2717 dstvalue = timedelta(minutes=dstvalue)
2718 self.dstvalue = dstvalue
2719 def dst(self, dt):
2720 return self.dstvalue
2722 cls = self.theclass
2723 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
2724 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
2725 t = d.timetuple()
2726 self.assertEqual(1, t.tm_year)
2727 self.assertEqual(1, t.tm_mon)
2728 self.assertEqual(1, t.tm_mday)
2729 self.assertEqual(10, t.tm_hour)
2730 self.assertEqual(20, t.tm_min)
2731 self.assertEqual(30, t.tm_sec)
2732 self.assertEqual(0, t.tm_wday)
2733 self.assertEqual(1, t.tm_yday)
2734 self.assertEqual(flag, t.tm_isdst)
2736 # dst() returns wrong type.
2737 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
2739 # dst() at the edge.
2740 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
2741 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
2743 # dst() out of range.
2744 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
2745 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
2747 def test_utctimetuple(self):
2748 class DST(tzinfo):
2749 def __init__(self, dstvalue):
2750 if isinstance(dstvalue, int):
2751 dstvalue = timedelta(minutes=dstvalue)
2752 self.dstvalue = dstvalue
2753 def dst(self, dt):
2754 return self.dstvalue
2756 cls = self.theclass
2757 # This can't work: DST didn't implement utcoffset.
2758 self.assertRaises(NotImplementedError,
2759 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
2761 class UOFS(DST):
2762 def __init__(self, uofs, dofs=None):
2763 DST.__init__(self, dofs)
2764 self.uofs = timedelta(minutes=uofs)
2765 def utcoffset(self, dt):
2766 return self.uofs
2768 # Ensure tm_isdst is 0 regardless of what dst() says: DST is never
2769 # in effect for a UTC time.
2770 for dstvalue in -33, 33, 0, None:
2771 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
2772 t = d.utctimetuple()
2773 self.assertEqual(d.year, t.tm_year)
2774 self.assertEqual(d.month, t.tm_mon)
2775 self.assertEqual(d.day, t.tm_mday)
2776 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
2777 self.assertEqual(13, t.tm_min)
2778 self.assertEqual(d.second, t.tm_sec)
2779 self.assertEqual(d.weekday(), t.tm_wday)
2780 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
2781 t.tm_yday)
2782 self.assertEqual(0, t.tm_isdst)
2784 # At the edges, UTC adjustment can normalize into years out-of-range
2785 # for a datetime object. Ensure that a correct timetuple is
2786 # created anyway.
2787 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
2788 # That goes back 1 minute less than a full day.
2789 t = tiny.utctimetuple()
2790 self.assertEqual(t.tm_year, MINYEAR-1)
2791 self.assertEqual(t.tm_mon, 12)
2792 self.assertEqual(t.tm_mday, 31)
2793 self.assertEqual(t.tm_hour, 0)
2794 self.assertEqual(t.tm_min, 1)
2795 self.assertEqual(t.tm_sec, 37)
2796 self.assertEqual(t.tm_yday, 366) # "year 0" is a leap year
2797 self.assertEqual(t.tm_isdst, 0)
2799 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
2800 # That goes forward 1 minute less than a full day.
2801 t = huge.utctimetuple()
2802 self.assertEqual(t.tm_year, MAXYEAR+1)
2803 self.assertEqual(t.tm_mon, 1)
2804 self.assertEqual(t.tm_mday, 1)
2805 self.assertEqual(t.tm_hour, 23)
2806 self.assertEqual(t.tm_min, 58)
2807 self.assertEqual(t.tm_sec, 37)
2808 self.assertEqual(t.tm_yday, 1)
2809 self.assertEqual(t.tm_isdst, 0)
2811 def test_tzinfo_isoformat(self):
2812 zero = FixedOffset(0, "+00:00")
2813 plus = FixedOffset(220, "+03:40")
2814 minus = FixedOffset(-231, "-03:51")
2815 unknown = FixedOffset(None, "")
2817 cls = self.theclass
2818 datestr = '0001-02-03'
2819 for ofs in None, zero, plus, minus, unknown:
2820 for us in 0, 987001:
2821 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
2822 timestr = '04:05:59' + (us and '.987001' or '')
2823 ofsstr = ofs is not None and d.tzname() or ''
2824 tailstr = timestr + ofsstr
2825 iso = d.isoformat()
2826 self.assertEqual(iso, datestr + 'T' + tailstr)
2827 self.assertEqual(iso, d.isoformat('T'))
2828 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
2829 self.assertEqual(str(d), datestr + ' ' + tailstr)
2831 def test_replace(self):
2832 cls = self.theclass
2833 z100 = FixedOffset(100, "+100")
2834 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2835 args = [1, 2, 3, 4, 5, 6, 7, z100]
2836 base = cls(*args)
2837 self.assertEqual(base, base.replace())
2839 i = 0
2840 for name, newval in (("year", 2),
2841 ("month", 3),
2842 ("day", 4),
2843 ("hour", 5),
2844 ("minute", 6),
2845 ("second", 7),
2846 ("microsecond", 8),
2847 ("tzinfo", zm200)):
2848 newargs = args[:]
2849 newargs[i] = newval
2850 expected = cls(*newargs)
2851 got = base.replace(**{name: newval})
2852 self.assertEqual(expected, got)
2853 i += 1
2855 # Ensure we can get rid of a tzinfo.
2856 self.assertEqual(base.tzname(), "+100")
2857 base2 = base.replace(tzinfo=None)
2858 self.assertTrue(base2.tzinfo is None)
2859 self.assertTrue(base2.tzname() is None)
2861 # Ensure we can add one.
2862 base3 = base2.replace(tzinfo=z100)
2863 self.assertEqual(base, base3)
2864 self.assertTrue(base.tzinfo is base3.tzinfo)
2866 # Out of bounds.
2867 base = cls(2000, 2, 29)
2868 self.assertRaises(ValueError, base.replace, year=2001)
2870 def test_more_astimezone(self):
2871 # The inherited test_astimezone covered some trivial and error cases.
2872 fnone = FixedOffset(None, "None")
2873 f44m = FixedOffset(44, "44")
2874 fm5h = FixedOffset(-timedelta(hours=5), "m300")
2876 dt = self.theclass.now(tz=f44m)
2877 self.assertTrue(dt.tzinfo is f44m)
2878 # Replacing with degenerate tzinfo raises an exception.
2879 self.assertRaises(ValueError, dt.astimezone, fnone)
2880 # Ditto with None tz.
2881 self.assertRaises(TypeError, dt.astimezone, None)
2882 # Replacing with same tzinfo makes no change.
2883 x = dt.astimezone(dt.tzinfo)
2884 self.assertTrue(x.tzinfo is f44m)
2885 self.assertEqual(x.date(), dt.date())
2886 self.assertEqual(x.time(), dt.time())
2888 # Replacing with different tzinfo does adjust.
2889 got = dt.astimezone(fm5h)
2890 self.assertTrue(got.tzinfo is fm5h)
2891 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
2892 expected = dt - dt.utcoffset() # in effect, convert to UTC
2893 expected += fm5h.utcoffset(dt) # and from there to local time
2894 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
2895 self.assertEqual(got.date(), expected.date())
2896 self.assertEqual(got.time(), expected.time())
2897 self.assertEqual(got.timetz(), expected.timetz())
2898 self.assertTrue(got.tzinfo is expected.tzinfo)
2899 self.assertEqual(got, expected)
2901 def test_aware_subtract(self):
2902 cls = self.theclass
2904 # Ensure that utcoffset() is ignored when the operands have the
2905 # same tzinfo member.
2906 class OperandDependentOffset(tzinfo):
2907 def utcoffset(self, t):
2908 if t.minute < 10:
2909 # d0 and d1 equal after adjustment
2910 return timedelta(minutes=t.minute)
2911 else:
2912 # d2 off in the weeds
2913 return timedelta(minutes=59)
2915 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
2916 d0 = base.replace(minute=3)
2917 d1 = base.replace(minute=9)
2918 d2 = base.replace(minute=11)
2919 for x in d0, d1, d2:
2920 for y in d0, d1, d2:
2921 got = x - y
2922 expected = timedelta(minutes=x.minute - y.minute)
2923 self.assertEqual(got, expected)
2925 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
2926 # ignored.
2927 base = cls(8, 9, 10, 11, 12, 13, 14)
2928 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2929 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2930 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2931 for x in d0, d1, d2:
2932 for y in d0, d1, d2:
2933 got = x - y
2934 if (x is d0 or x is d1) and (y is d0 or y is d1):
2935 expected = timedelta(0)
2936 elif x is y is d2:
2937 expected = timedelta(0)
2938 elif x is d2:
2939 expected = timedelta(minutes=(11-59)-0)
2940 else:
2941 assert y is d2
2942 expected = timedelta(minutes=0-(11-59))
2943 self.assertEqual(got, expected)
2945 def test_mixed_compare(self):
2946 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
2947 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
2948 self.assertEqual(t1, t2)
2949 t2 = t2.replace(tzinfo=None)
2950 self.assertEqual(t1, t2)
2951 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2952 self.assertEqual(t1, t2)
2953 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2954 self.assertRaises(TypeError, lambda: t1 == t2)
2956 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
2957 class Varies(tzinfo):
2958 def __init__(self):
2959 self.offset = timedelta(minutes=22)
2960 def utcoffset(self, t):
2961 self.offset += timedelta(minutes=1)
2962 return self.offset
2964 v = Varies()
2965 t1 = t2.replace(tzinfo=v)
2966 t2 = t2.replace(tzinfo=v)
2967 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2968 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2969 self.assertEqual(t1, t2)
2971 # But if they're not identical, it isn't ignored.
2972 t2 = t2.replace(tzinfo=Varies())
2973 self.assertTrue(t1 < t2) # t1's offset counter still going up
2975 def test_subclass_datetimetz(self):
2977 class C(self.theclass):
2978 theAnswer = 42
2980 def __new__(cls, *args, **kws):
2981 temp = kws.copy()
2982 extra = temp.pop('extra')
2983 result = self.theclass.__new__(cls, *args, **temp)
2984 result.extra = extra
2985 return result
2987 def newmeth(self, start):
2988 return start + self.hour + self.year
2990 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2992 dt1 = self.theclass(*args)
2993 dt2 = C(*args, **{'extra': 7})
2995 self.assertEqual(dt2.__class__, C)
2996 self.assertEqual(dt2.theAnswer, 42)
2997 self.assertEqual(dt2.extra, 7)
2998 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2999 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
3001 # Pain to set up DST-aware tzinfo classes.
3003 def first_sunday_on_or_after(dt):
3004 days_to_go = 6 - dt.weekday()
3005 if days_to_go:
3006 dt += timedelta(days_to_go)
3007 return dt
3009 ZERO = timedelta(0)
3010 HOUR = timedelta(hours=1)
3011 DAY = timedelta(days=1)
3012 # In the US, DST starts at 2am (standard time) on the first Sunday in April.
3013 DSTSTART = datetime(1, 4, 1, 2)
3014 # and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
3015 # which is the first Sunday on or after Oct 25. Because we view 1:MM as
3016 # being standard time on that day, there is no spelling in local time of
3017 # the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
3018 DSTEND = datetime(1, 10, 25, 1)
3020 class USTimeZone(tzinfo):
3022 def __init__(self, hours, reprname, stdname, dstname):
3023 self.stdoffset = timedelta(hours=hours)
3024 self.reprname = reprname
3025 self.stdname = stdname
3026 self.dstname = dstname
3028 def __repr__(self):
3029 return self.reprname
3031 def tzname(self, dt):
3032 if self.dst(dt):
3033 return self.dstname
3034 else:
3035 return self.stdname
3037 def utcoffset(self, dt):
3038 return self.stdoffset + self.dst(dt)
3040 def dst(self, dt):
3041 if dt is None or dt.tzinfo is None:
3042 # An exception instead may be sensible here, in one or more of
3043 # the cases.
3044 return ZERO
3045 assert dt.tzinfo is self
3047 # Find first Sunday in April.
3048 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
3049 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
3051 # Find last Sunday in October.
3052 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
3053 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
3055 # Can't compare naive to aware objects, so strip the timezone from
3056 # dt first.
3057 if start <= dt.replace(tzinfo=None) < end:
3058 return HOUR
3059 else:
3060 return ZERO
3062 Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
3063 Central = USTimeZone(-6, "Central", "CST", "CDT")
3064 Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
3065 Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
3066 utc_real = FixedOffset(0, "UTC", 0)
3067 # For better test coverage, we want another flavor of UTC that's west of
3068 # the Eastern and Pacific timezones.
3069 utc_fake = FixedOffset(-12*60, "UTCfake", 0)
3071 class TestTimezoneConversions(unittest.TestCase):
3072 # The DST switch times for 2002, in std time.
3073 dston = datetime(2002, 4, 7, 2)
3074 dstoff = datetime(2002, 10, 27, 1)
3076 theclass = datetime
3078 # Check a time that's inside DST.
3079 def checkinside(self, dt, tz, utc, dston, dstoff):
3080 self.assertEqual(dt.dst(), HOUR)
3082 # Conversion to our own timezone is always an identity.
3083 self.assertEqual(dt.astimezone(tz), dt)
3085 asutc = dt.astimezone(utc)
3086 there_and_back = asutc.astimezone(tz)
3088 # Conversion to UTC and back isn't always an identity here,
3089 # because there are redundant spellings (in local time) of
3090 # UTC time when DST begins: the clock jumps from 1:59:59
3091 # to 3:00:00, and a local time of 2:MM:SS doesn't really
3092 # make sense then. The classes above treat 2:MM:SS as
3093 # daylight time then (it's "after 2am"), really an alias
3094 # for 1:MM:SS standard time. The latter form is what
3095 # conversion back from UTC produces.
3096 if dt.date() == dston.date() and dt.hour == 2:
3097 # We're in the redundant hour, and coming back from
3098 # UTC gives the 1:MM:SS standard-time spelling.
3099 self.assertEqual(there_and_back + HOUR, dt)
3100 # Although during was considered to be in daylight
3101 # time, there_and_back is not.
3102 self.assertEqual(there_and_back.dst(), ZERO)
3103 # They're the same times in UTC.
3104 self.assertEqual(there_and_back.astimezone(utc),
3105 dt.astimezone(utc))
3106 else:
3107 # We're not in the redundant hour.
3108 self.assertEqual(dt, there_and_back)
3110 # Because we have a redundant spelling when DST begins, there is
3111 # (unforunately) an hour when DST ends that can't be spelled at all in
3112 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
3113 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
3114 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
3115 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
3116 # expressed in local time. Nevertheless, we want conversion back
3117 # from UTC to mimic the local clock's "repeat an hour" behavior.
3118 nexthour_utc = asutc + HOUR
3119 nexthour_tz = nexthour_utc.astimezone(tz)
3120 if dt.date() == dstoff.date() and dt.hour == 0:
3121 # We're in the hour before the last DST hour. The last DST hour
3122 # is ineffable. We want the conversion back to repeat 1:MM.
3123 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3124 nexthour_utc += HOUR
3125 nexthour_tz = nexthour_utc.astimezone(tz)
3126 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3127 else:
3128 self.assertEqual(nexthour_tz - dt, HOUR)
3130 # Check a time that's outside DST.
3131 def checkoutside(self, dt, tz, utc):
3132 self.assertEqual(dt.dst(), ZERO)
3134 # Conversion to our own timezone is always an identity.
3135 self.assertEqual(dt.astimezone(tz), dt)
3137 # Converting to UTC and back is an identity too.
3138 asutc = dt.astimezone(utc)
3139 there_and_back = asutc.astimezone(tz)
3140 self.assertEqual(dt, there_and_back)
3142 def convert_between_tz_and_utc(self, tz, utc):
3143 dston = self.dston.replace(tzinfo=tz)
3144 # Because 1:MM on the day DST ends is taken as being standard time,
3145 # there is no spelling in tz for the last hour of daylight time.
3146 # For purposes of the test, the last hour of DST is 0:MM, which is
3147 # taken as being daylight time (and 1:MM is taken as being standard
3148 # time).
3149 dstoff = self.dstoff.replace(tzinfo=tz)
3150 for delta in (timedelta(weeks=13),
3151 DAY,
3152 HOUR,
3153 timedelta(minutes=1),
3154 timedelta(microseconds=1)):
3156 self.checkinside(dston, tz, utc, dston, dstoff)
3157 for during in dston + delta, dstoff - delta:
3158 self.checkinside(during, tz, utc, dston, dstoff)
3160 self.checkoutside(dstoff, tz, utc)
3161 for outside in dston - delta, dstoff + delta:
3162 self.checkoutside(outside, tz, utc)
3164 def test_easy(self):
3165 # Despite the name of this test, the endcases are excruciating.
3166 self.convert_between_tz_and_utc(Eastern, utc_real)
3167 self.convert_between_tz_and_utc(Pacific, utc_real)
3168 self.convert_between_tz_and_utc(Eastern, utc_fake)
3169 self.convert_between_tz_and_utc(Pacific, utc_fake)
3170 # The next is really dancing near the edge. It works because
3171 # Pacific and Eastern are far enough apart that their "problem
3172 # hours" don't overlap.
3173 self.convert_between_tz_and_utc(Eastern, Pacific)
3174 self.convert_between_tz_and_utc(Pacific, Eastern)
3175 # OTOH, these fail! Don't enable them. The difficulty is that
3176 # the edge case tests assume that every hour is representable in
3177 # the "utc" class. This is always true for a fixed-offset tzinfo
3178 # class (lke utc_real and utc_fake), but not for Eastern or Central.
3179 # For these adjacent DST-aware time zones, the range of time offsets
3180 # tested ends up creating hours in the one that aren't representable
3181 # in the other. For the same reason, we would see failures in the
3182 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
3183 # offset deltas in convert_between_tz_and_utc().
3185 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
3186 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
3188 def test_tricky(self):
3189 # 22:00 on day before daylight starts.
3190 fourback = self.dston - timedelta(hours=4)
3191 ninewest = FixedOffset(-9*60, "-0900", 0)
3192 fourback = fourback.replace(tzinfo=ninewest)
3193 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
3194 # 2", we should get the 3 spelling.
3195 # If we plug 22:00 the day before into Eastern, it "looks like std
3196 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
3197 # to 22:00 lands on 2:00, which makes no sense in local time (the
3198 # local clock jumps from 1 to 3). The point here is to make sure we
3199 # get the 3 spelling.
3200 expected = self.dston.replace(hour=3)
3201 got = fourback.astimezone(Eastern).replace(tzinfo=None)
3202 self.assertEqual(expected, got)
3204 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
3205 # case we want the 1:00 spelling.
3206 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
3207 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
3208 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
3209 # spelling.
3210 expected = self.dston.replace(hour=1)
3211 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
3212 self.assertEqual(expected, got)
3214 # Now on the day DST ends, we want "repeat an hour" behavior.
3215 # UTC 4:MM 5:MM 6:MM 7:MM checking these
3216 # EST 23:MM 0:MM 1:MM 2:MM
3217 # EDT 0:MM 1:MM 2:MM 3:MM
3218 # wall 0:MM 1:MM 1:MM 2:MM against these
3219 for utc in utc_real, utc_fake:
3220 for tz in Eastern, Pacific:
3221 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
3222 # Convert that to UTC.
3223 first_std_hour -= tz.utcoffset(None)
3224 # Adjust for possibly fake UTC.
3225 asutc = first_std_hour + utc.utcoffset(None)
3226 # First UTC hour to convert; this is 4:00 when utc=utc_real &
3227 # tz=Eastern.
3228 asutcbase = asutc.replace(tzinfo=utc)
3229 for tzhour in (0, 1, 1, 2):
3230 expectedbase = self.dstoff.replace(hour=tzhour)
3231 for minute in 0, 30, 59:
3232 expected = expectedbase.replace(minute=minute)
3233 asutc = asutcbase.replace(minute=minute)
3234 astz = asutc.astimezone(tz)
3235 self.assertEqual(astz.replace(tzinfo=None), expected)
3236 asutcbase += HOUR
3239 def test_bogus_dst(self):
3240 class ok(tzinfo):
3241 def utcoffset(self, dt): return HOUR
3242 def dst(self, dt): return HOUR
3244 now = self.theclass.now().replace(tzinfo=utc_real)
3245 # Doesn't blow up.
3246 now.astimezone(ok())
3248 # Does blow up.
3249 class notok(ok):
3250 def dst(self, dt): return None
3251 self.assertRaises(ValueError, now.astimezone, notok())
3253 def test_fromutc(self):
3254 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
3255 now = datetime.utcnow().replace(tzinfo=utc_real)
3256 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3257 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
3258 enow = Eastern.fromutc(now) # doesn't blow up
3259 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
3260 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
3261 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
3263 # Always converts UTC to standard time.
3264 class FauxUSTimeZone(USTimeZone):
3265 def fromutc(self, dt):
3266 return dt + self.stdoffset
3267 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
3269 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
3270 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
3271 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
3273 # Check around DST start.
3274 start = self.dston.replace(hour=4, tzinfo=Eastern)
3275 fstart = start.replace(tzinfo=FEastern)
3276 for wall in 23, 0, 1, 3, 4, 5:
3277 expected = start.replace(hour=wall)
3278 if wall == 23:
3279 expected -= timedelta(days=1)
3280 got = Eastern.fromutc(start)
3281 self.assertEqual(expected, got)
3283 expected = fstart + FEastern.stdoffset
3284 got = FEastern.fromutc(fstart)
3285 self.assertEqual(expected, got)
3287 # Ensure astimezone() calls fromutc() too.
3288 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3289 self.assertEqual(expected, got)
3291 start += HOUR
3292 fstart += HOUR
3294 # Check around DST end.
3295 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
3296 fstart = start.replace(tzinfo=FEastern)
3297 for wall in 0, 1, 1, 2, 3, 4:
3298 expected = start.replace(hour=wall)
3299 got = Eastern.fromutc(start)
3300 self.assertEqual(expected, got)
3302 expected = fstart + FEastern.stdoffset
3303 got = FEastern.fromutc(fstart)
3304 self.assertEqual(expected, got)
3306 # Ensure astimezone() calls fromutc() too.
3307 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3308 self.assertEqual(expected, got)
3310 start += HOUR
3311 fstart += HOUR
3314 #############################################################################
3315 # oddballs
3317 class Oddballs(unittest.TestCase):
3319 def test_bug_1028306(self):
3320 # Trying to compare a date to a datetime should act like a mixed-
3321 # type comparison, despite that datetime is a subclass of date.
3322 as_date = date.today()
3323 as_datetime = datetime.combine(as_date, time())
3324 self.assertTrue(as_date != as_datetime)
3325 self.assertTrue(as_datetime != as_date)
3326 self.assertTrue(not as_date == as_datetime)
3327 self.assertTrue(not as_datetime == as_date)
3328 self.assertRaises(TypeError, lambda: as_date < as_datetime)
3329 self.assertRaises(TypeError, lambda: as_datetime < as_date)
3330 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
3331 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
3332 self.assertRaises(TypeError, lambda: as_date > as_datetime)
3333 self.assertRaises(TypeError, lambda: as_datetime > as_date)
3334 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
3335 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
3337 # Neverthelss, comparison should work with the base-class (date)
3338 # projection if use of a date method is forced.
3339 self.assertTrue(as_date.__eq__(as_datetime))
3340 different_day = (as_date.day + 1) % 20 + 1
3341 self.assertTrue(not as_date.__eq__(as_datetime.replace(day=
3342 different_day)))
3344 # And date should compare with other subclasses of date. If a
3345 # subclass wants to stop this, it's up to the subclass to do so.
3346 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
3347 self.assertEqual(as_date, date_sc)
3348 self.assertEqual(date_sc, as_date)
3350 # Ditto for datetimes.
3351 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
3352 as_date.day, 0, 0, 0)
3353 self.assertEqual(as_datetime, datetime_sc)
3354 self.assertEqual(datetime_sc, as_datetime)
3356 def test_main():
3357 test_support.run_unittest(__name__)
3359 if __name__ == "__main__":
3360 test_main()