Issue #5788: `datetime.timedelta` objects get a new `total_seconds()` method returning
[python.git] / Lib / test / test_datetime.py
blob1fb3d1bf2009fc67206d9bc08a4d261b8dd2f343
1 """Test date/time type.
3 See http://www.zope.org/Members/fdrake/DateTimeWiki/TestCases
4 """
6 import os
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.assertTrue(isinstance(ne, tzinfo))
86 dt = datetime.now()
87 self.assertRaises(NotImplementedError, ne.tzname, dt)
88 self.assertRaises(NotImplementedError, ne.utcoffset, dt)
89 self.assertRaises(NotImplementedError, ne.dst, dt)
91 def test_normal(self):
92 fo = FixedOffset(3, "Three")
93 self.assertTrue(isinstance(fo, tzinfo))
94 for dt in datetime.now(), None:
95 self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3))
96 self.assertEqual(fo.tzname(dt), "Three")
97 self.assertEqual(fo.dst(dt), timedelta(minutes=42))
99 def test_pickling_base(self):
100 # There's no point to pickling tzinfo objects on their own (they
101 # carry no data), but they need to be picklable anyway else
102 # concrete subclasses can't be pickled.
103 orig = tzinfo.__new__(tzinfo)
104 self.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.assertTrue(isinstance(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.assertTrue(isinstance(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.assertTrue(me in [1, 20L, [], me])
145 self.assertFalse(me not in [1, 20L, [], me])
147 self.assertTrue([] in [me, 1, 20L, []])
148 self.assertFalse([] not in [me, 1, 20L, []])
150 def test_harmful_mixed_comparison(self):
151 me = self.theclass(1, 1, 1)
153 self.assertRaises(TypeError, lambda: me < ())
154 self.assertRaises(TypeError, lambda: me <= ())
155 self.assertRaises(TypeError, lambda: me > ())
156 self.assertRaises(TypeError, lambda: me >= ())
158 self.assertRaises(TypeError, lambda: () < me)
159 self.assertRaises(TypeError, lambda: () <= me)
160 self.assertRaises(TypeError, lambda: () > me)
161 self.assertRaises(TypeError, lambda: () >= me)
163 self.assertRaises(TypeError, cmp, (), me)
164 self.assertRaises(TypeError, cmp, me, ())
166 #############################################################################
167 # timedelta tests
169 class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
171 theclass = timedelta
173 def test_constructor(self):
174 eq = self.assertEqual
175 td = timedelta
177 # Check keyword args to constructor
178 eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0,
179 milliseconds=0, microseconds=0))
180 eq(td(1), td(days=1))
181 eq(td(0, 1), td(seconds=1))
182 eq(td(0, 0, 1), td(microseconds=1))
183 eq(td(weeks=1), td(days=7))
184 eq(td(days=1), td(hours=24))
185 eq(td(hours=1), td(minutes=60))
186 eq(td(minutes=1), td(seconds=60))
187 eq(td(seconds=1), td(milliseconds=1000))
188 eq(td(milliseconds=1), td(microseconds=1000))
190 # Check float args to constructor
191 eq(td(weeks=1.0/7), td(days=1))
192 eq(td(days=1.0/24), td(hours=1))
193 eq(td(hours=1.0/60), td(minutes=1))
194 eq(td(minutes=1.0/60), td(seconds=1))
195 eq(td(seconds=0.001), td(milliseconds=1))
196 eq(td(milliseconds=0.001), td(microseconds=1))
198 def test_computations(self):
199 eq = self.assertEqual
200 td = timedelta
202 a = td(7) # One week
203 b = td(0, 60) # One minute
204 c = td(0, 0, 1000) # One millisecond
205 eq(a+b+c, td(7, 60, 1000))
206 eq(a-b, td(6, 24*3600 - 60))
207 eq(-a, td(-7))
208 eq(+a, td(7))
209 eq(-b, td(-1, 24*3600 - 60))
210 eq(-c, td(-1, 24*3600 - 1, 999000))
211 eq(abs(a), a)
212 eq(abs(-a), a)
213 eq(td(6, 24*3600), a)
214 eq(td(0, 0, 60*1000000), b)
215 eq(a*10, td(70))
216 eq(a*10, 10*a)
217 eq(a*10L, 10*a)
218 eq(b*10, td(0, 600))
219 eq(10*b, td(0, 600))
220 eq(b*10L, td(0, 600))
221 eq(c*10, td(0, 0, 10000))
222 eq(10*c, td(0, 0, 10000))
223 eq(c*10L, td(0, 0, 10000))
224 eq(a*-1, -a)
225 eq(b*-2, -b-b)
226 eq(c*-2, -c+-c)
227 eq(b*(60*24), (b*60)*24)
228 eq(b*(60*24), (60*b)*24)
229 eq(c*1000, td(0, 1))
230 eq(1000*c, td(0, 1))
231 eq(a//7, td(1))
232 eq(b//10, td(0, 6))
233 eq(c//1000, td(0, 0, 1))
234 eq(a//10, td(0, 7*24*360))
235 eq(a//3600000, td(0, 0, 7*24*1000))
237 def test_disallowed_computations(self):
238 a = timedelta(42)
240 # Add/sub ints, longs, floats should be illegal
241 for i in 1, 1L, 1.0:
242 self.assertRaises(TypeError, lambda: a+i)
243 self.assertRaises(TypeError, lambda: a-i)
244 self.assertRaises(TypeError, lambda: i+a)
245 self.assertRaises(TypeError, lambda: i-a)
247 # Mul/div by float isn't supported.
248 x = 2.3
249 self.assertRaises(TypeError, lambda: a*x)
250 self.assertRaises(TypeError, lambda: x*a)
251 self.assertRaises(TypeError, lambda: a/x)
252 self.assertRaises(TypeError, lambda: x/a)
253 self.assertRaises(TypeError, lambda: a // x)
254 self.assertRaises(TypeError, lambda: x // a)
256 # Division of int by timedelta doesn't make sense.
257 # Division by zero doesn't make sense.
258 for zero in 0, 0L:
259 self.assertRaises(TypeError, lambda: zero // a)
260 self.assertRaises(ZeroDivisionError, lambda: a // zero)
262 def test_basic_attributes(self):
263 days, seconds, us = 1, 7, 31
264 td = timedelta(days, seconds, us)
265 self.assertEqual(td.days, days)
266 self.assertEqual(td.seconds, seconds)
267 self.assertEqual(td.microseconds, us)
269 def test_total_seconds(self):
270 td = timedelta(days=365)
271 self.assertEqual(td.total_seconds(), 31536000.0)
272 for total_seconds in [123456.789012, -123456.789012, 0.123456, 0, 1e6]:
273 td = timedelta(seconds=total_seconds)
274 self.assertEqual(td.total_seconds(), total_seconds)
276 def test_carries(self):
277 t1 = timedelta(days=100,
278 weeks=-7,
279 hours=-24*(100-49),
280 minutes=-3,
281 seconds=12,
282 microseconds=(3*60 - 12) * 1e6 + 1)
283 t2 = timedelta(microseconds=1)
284 self.assertEqual(t1, t2)
286 def test_hash_equality(self):
287 t1 = timedelta(days=100,
288 weeks=-7,
289 hours=-24*(100-49),
290 minutes=-3,
291 seconds=12,
292 microseconds=(3*60 - 12) * 1000000)
293 t2 = timedelta()
294 self.assertEqual(hash(t1), hash(t2))
296 t1 += timedelta(weeks=7)
297 t2 += timedelta(days=7*7)
298 self.assertEqual(t1, t2)
299 self.assertEqual(hash(t1), hash(t2))
301 d = {t1: 1}
302 d[t2] = 2
303 self.assertEqual(len(d), 1)
304 self.assertEqual(d[t1], 2)
306 def test_pickling(self):
307 args = 12, 34, 56
308 orig = timedelta(*args)
309 for pickler, unpickler, proto in pickle_choices:
310 green = pickler.dumps(orig, proto)
311 derived = unpickler.loads(green)
312 self.assertEqual(orig, derived)
314 def test_compare(self):
315 t1 = timedelta(2, 3, 4)
316 t2 = timedelta(2, 3, 4)
317 self.assertTrue(t1 == t2)
318 self.assertTrue(t1 <= t2)
319 self.assertTrue(t1 >= t2)
320 self.assertTrue(not t1 != t2)
321 self.assertTrue(not t1 < t2)
322 self.assertTrue(not t1 > t2)
323 self.assertEqual(cmp(t1, t2), 0)
324 self.assertEqual(cmp(t2, t1), 0)
326 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
327 t2 = timedelta(*args) # this is larger than t1
328 self.assertTrue(t1 < t2)
329 self.assertTrue(t2 > t1)
330 self.assertTrue(t1 <= t2)
331 self.assertTrue(t2 >= t1)
332 self.assertTrue(t1 != t2)
333 self.assertTrue(t2 != t1)
334 self.assertTrue(not t1 == t2)
335 self.assertTrue(not t2 == t1)
336 self.assertTrue(not t1 > t2)
337 self.assertTrue(not t2 < t1)
338 self.assertTrue(not t1 >= t2)
339 self.assertTrue(not t2 <= t1)
340 self.assertEqual(cmp(t1, t2), -1)
341 self.assertEqual(cmp(t2, t1), 1)
343 for badarg in OTHERSTUFF:
344 self.assertEqual(t1 == badarg, False)
345 self.assertEqual(t1 != badarg, True)
346 self.assertEqual(badarg == t1, False)
347 self.assertEqual(badarg != t1, True)
349 self.assertRaises(TypeError, lambda: t1 <= badarg)
350 self.assertRaises(TypeError, lambda: t1 < badarg)
351 self.assertRaises(TypeError, lambda: t1 > badarg)
352 self.assertRaises(TypeError, lambda: t1 >= badarg)
353 self.assertRaises(TypeError, lambda: badarg <= t1)
354 self.assertRaises(TypeError, lambda: badarg < t1)
355 self.assertRaises(TypeError, lambda: badarg > t1)
356 self.assertRaises(TypeError, lambda: badarg >= t1)
358 def test_str(self):
359 td = timedelta
360 eq = self.assertEqual
362 eq(str(td(1)), "1 day, 0:00:00")
363 eq(str(td(-1)), "-1 day, 0:00:00")
364 eq(str(td(2)), "2 days, 0:00:00")
365 eq(str(td(-2)), "-2 days, 0:00:00")
367 eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
368 eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
369 eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
370 "-210 days, 23:12:34")
372 eq(str(td(milliseconds=1)), "0:00:00.001000")
373 eq(str(td(microseconds=3)), "0:00:00.000003")
375 eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
376 microseconds=999999)),
377 "999999999 days, 23:59:59.999999")
379 def test_roundtrip(self):
380 for td in (timedelta(days=999999999, hours=23, minutes=59,
381 seconds=59, microseconds=999999),
382 timedelta(days=-999999999),
383 timedelta(days=1, seconds=2, microseconds=3)):
385 # Verify td -> string -> td identity.
386 s = repr(td)
387 self.assertTrue(s.startswith('datetime.'))
388 s = s[9:]
389 td2 = eval(s)
390 self.assertEqual(td, td2)
392 # Verify identity via reconstructing from pieces.
393 td2 = timedelta(td.days, td.seconds, td.microseconds)
394 self.assertEqual(td, td2)
396 def test_resolution_info(self):
397 self.assertTrue(isinstance(timedelta.min, timedelta))
398 self.assertTrue(isinstance(timedelta.max, timedelta))
399 self.assertTrue(isinstance(timedelta.resolution, timedelta))
400 self.assertTrue(timedelta.max > timedelta.min)
401 self.assertEqual(timedelta.min, timedelta(-999999999))
402 self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
403 self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
405 def test_overflow(self):
406 tiny = timedelta.resolution
408 td = timedelta.min + tiny
409 td -= tiny # no problem
410 self.assertRaises(OverflowError, td.__sub__, tiny)
411 self.assertRaises(OverflowError, td.__add__, -tiny)
413 td = timedelta.max - tiny
414 td += tiny # no problem
415 self.assertRaises(OverflowError, td.__add__, tiny)
416 self.assertRaises(OverflowError, td.__sub__, -tiny)
418 self.assertRaises(OverflowError, lambda: -timedelta.max)
420 def test_microsecond_rounding(self):
421 td = timedelta
422 eq = self.assertEqual
424 # Single-field rounding.
425 eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
426 eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
427 eq(td(milliseconds=0.6/1000), td(microseconds=1))
428 eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
430 # Rounding due to contributions from more than one field.
431 us_per_hour = 3600e6
432 us_per_day = us_per_hour * 24
433 eq(td(days=.4/us_per_day), td(0))
434 eq(td(hours=.2/us_per_hour), td(0))
435 eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
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 def test_massive_normalization(self):
442 td = timedelta(microseconds=-1)
443 self.assertEqual((td.days, td.seconds, td.microseconds),
444 (-1, 24*3600-1, 999999))
446 def test_bool(self):
447 self.assertTrue(timedelta(1))
448 self.assertTrue(timedelta(0, 1))
449 self.assertTrue(timedelta(0, 0, 1))
450 self.assertTrue(timedelta(microseconds=1))
451 self.assertTrue(not timedelta(0))
453 def test_subclass_timedelta(self):
455 class T(timedelta):
456 @staticmethod
457 def from_td(td):
458 return T(td.days, td.seconds, td.microseconds)
460 def as_hours(self):
461 sum = (self.days * 24 +
462 self.seconds / 3600.0 +
463 self.microseconds / 3600e6)
464 return round(sum)
466 t1 = T(days=1)
467 self.assertTrue(type(t1) is T)
468 self.assertEqual(t1.as_hours(), 24)
470 t2 = T(days=-1, seconds=-3600)
471 self.assertTrue(type(t2) is T)
472 self.assertEqual(t2.as_hours(), -25)
474 t3 = t1 + t2
475 self.assertTrue(type(t3) is timedelta)
476 t4 = T.from_td(t3)
477 self.assertTrue(type(t4) is T)
478 self.assertEqual(t3.days, t4.days)
479 self.assertEqual(t3.seconds, t4.seconds)
480 self.assertEqual(t3.microseconds, t4.microseconds)
481 self.assertEqual(str(t3), str(t4))
482 self.assertEqual(t4.as_hours(), -1)
484 #############################################################################
485 # date tests
487 class TestDateOnly(unittest.TestCase):
488 # Tests here won't pass if also run on datetime objects, so don't
489 # subclass this to test datetimes too.
491 def test_delta_non_days_ignored(self):
492 dt = date(2000, 1, 2)
493 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
494 microseconds=5)
495 days = timedelta(delta.days)
496 self.assertEqual(days, timedelta(1))
498 dt2 = dt + delta
499 self.assertEqual(dt2, dt + days)
501 dt2 = delta + dt
502 self.assertEqual(dt2, dt + days)
504 dt2 = dt - delta
505 self.assertEqual(dt2, dt - days)
507 delta = -delta
508 days = timedelta(delta.days)
509 self.assertEqual(days, timedelta(-2))
511 dt2 = dt + delta
512 self.assertEqual(dt2, dt + days)
514 dt2 = delta + dt
515 self.assertEqual(dt2, dt + days)
517 dt2 = dt - delta
518 self.assertEqual(dt2, dt - days)
520 class SubclassDate(date):
521 sub_var = 1
523 class TestDate(HarmlessMixedComparison, unittest.TestCase):
524 # Tests here should pass for both dates and datetimes, except for a
525 # few tests that TestDateTime overrides.
527 theclass = date
529 def test_basic_attributes(self):
530 dt = self.theclass(2002, 3, 1)
531 self.assertEqual(dt.year, 2002)
532 self.assertEqual(dt.month, 3)
533 self.assertEqual(dt.day, 1)
535 def test_roundtrip(self):
536 for dt in (self.theclass(1, 2, 3),
537 self.theclass.today()):
538 # Verify dt -> string -> date identity.
539 s = repr(dt)
540 self.assertTrue(s.startswith('datetime.'))
541 s = s[9:]
542 dt2 = eval(s)
543 self.assertEqual(dt, dt2)
545 # Verify identity via reconstructing from pieces.
546 dt2 = self.theclass(dt.year, dt.month, dt.day)
547 self.assertEqual(dt, dt2)
549 def test_ordinal_conversions(self):
550 # Check some fixed values.
551 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
552 (1, 12, 31, 365),
553 (2, 1, 1, 366),
554 # first example from "Calendrical Calculations"
555 (1945, 11, 12, 710347)]:
556 d = self.theclass(y, m, d)
557 self.assertEqual(n, d.toordinal())
558 fromord = self.theclass.fromordinal(n)
559 self.assertEqual(d, fromord)
560 if hasattr(fromord, "hour"):
561 # if we're checking something fancier than a date, verify
562 # the extra fields have been zeroed out
563 self.assertEqual(fromord.hour, 0)
564 self.assertEqual(fromord.minute, 0)
565 self.assertEqual(fromord.second, 0)
566 self.assertEqual(fromord.microsecond, 0)
568 # Check first and last days of year spottily across the whole
569 # range of years supported.
570 for year in xrange(MINYEAR, MAXYEAR+1, 7):
571 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
572 d = self.theclass(year, 1, 1)
573 n = d.toordinal()
574 d2 = self.theclass.fromordinal(n)
575 self.assertEqual(d, d2)
576 # Verify that moving back a day gets to the end of year-1.
577 if year > 1:
578 d = self.theclass.fromordinal(n-1)
579 d2 = self.theclass(year-1, 12, 31)
580 self.assertEqual(d, d2)
581 self.assertEqual(d2.toordinal(), n-1)
583 # Test every day in a leap-year and a non-leap year.
584 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
585 for year, isleap in (2000, True), (2002, False):
586 n = self.theclass(year, 1, 1).toordinal()
587 for month, maxday in zip(range(1, 13), dim):
588 if month == 2 and isleap:
589 maxday += 1
590 for day in range(1, maxday+1):
591 d = self.theclass(year, month, day)
592 self.assertEqual(d.toordinal(), n)
593 self.assertEqual(d, self.theclass.fromordinal(n))
594 n += 1
596 def test_extreme_ordinals(self):
597 a = self.theclass.min
598 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
599 aord = a.toordinal()
600 b = a.fromordinal(aord)
601 self.assertEqual(a, b)
603 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
605 b = a + timedelta(days=1)
606 self.assertEqual(b.toordinal(), aord + 1)
607 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
609 a = self.theclass.max
610 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
611 aord = a.toordinal()
612 b = a.fromordinal(aord)
613 self.assertEqual(a, b)
615 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
617 b = a - timedelta(days=1)
618 self.assertEqual(b.toordinal(), aord - 1)
619 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
621 def test_bad_constructor_arguments(self):
622 # bad years
623 self.theclass(MINYEAR, 1, 1) # no exception
624 self.theclass(MAXYEAR, 1, 1) # no exception
625 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
626 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
627 # bad months
628 self.theclass(2000, 1, 1) # no exception
629 self.theclass(2000, 12, 1) # no exception
630 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
631 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
632 # bad days
633 self.theclass(2000, 2, 29) # no exception
634 self.theclass(2004, 2, 29) # no exception
635 self.theclass(2400, 2, 29) # no exception
636 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
637 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
638 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
639 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
640 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
641 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
643 def test_hash_equality(self):
644 d = self.theclass(2000, 12, 31)
645 # same thing
646 e = self.theclass(2000, 12, 31)
647 self.assertEqual(d, e)
648 self.assertEqual(hash(d), hash(e))
650 dic = {d: 1}
651 dic[e] = 2
652 self.assertEqual(len(dic), 1)
653 self.assertEqual(dic[d], 2)
654 self.assertEqual(dic[e], 2)
656 d = self.theclass(2001, 1, 1)
657 # same thing
658 e = self.theclass(2001, 1, 1)
659 self.assertEqual(d, e)
660 self.assertEqual(hash(d), hash(e))
662 dic = {d: 1}
663 dic[e] = 2
664 self.assertEqual(len(dic), 1)
665 self.assertEqual(dic[d], 2)
666 self.assertEqual(dic[e], 2)
668 def test_computations(self):
669 a = self.theclass(2002, 1, 31)
670 b = self.theclass(1956, 1, 31)
672 diff = a-b
673 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
674 self.assertEqual(diff.seconds, 0)
675 self.assertEqual(diff.microseconds, 0)
677 day = timedelta(1)
678 week = timedelta(7)
679 a = self.theclass(2002, 3, 2)
680 self.assertEqual(a + day, self.theclass(2002, 3, 3))
681 self.assertEqual(day + a, self.theclass(2002, 3, 3))
682 self.assertEqual(a - day, self.theclass(2002, 3, 1))
683 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
684 self.assertEqual(a + week, self.theclass(2002, 3, 9))
685 self.assertEqual(a - week, self.theclass(2002, 2, 23))
686 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
687 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
688 self.assertEqual((a + week) - a, week)
689 self.assertEqual((a + day) - a, day)
690 self.assertEqual((a - week) - a, -week)
691 self.assertEqual((a - day) - a, -day)
692 self.assertEqual(a - (a + week), -week)
693 self.assertEqual(a - (a + day), -day)
694 self.assertEqual(a - (a - week), week)
695 self.assertEqual(a - (a - day), day)
697 # Add/sub ints, longs, floats should be illegal
698 for i in 1, 1L, 1.0:
699 self.assertRaises(TypeError, lambda: a+i)
700 self.assertRaises(TypeError, lambda: a-i)
701 self.assertRaises(TypeError, lambda: i+a)
702 self.assertRaises(TypeError, lambda: i-a)
704 # delta - date is senseless.
705 self.assertRaises(TypeError, lambda: day - a)
706 # mixing date and (delta or date) via * or // is senseless
707 self.assertRaises(TypeError, lambda: day * a)
708 self.assertRaises(TypeError, lambda: a * day)
709 self.assertRaises(TypeError, lambda: day // a)
710 self.assertRaises(TypeError, lambda: a // day)
711 self.assertRaises(TypeError, lambda: a * a)
712 self.assertRaises(TypeError, lambda: a // a)
713 # date + date is senseless
714 self.assertRaises(TypeError, lambda: a + a)
716 def test_overflow(self):
717 tiny = self.theclass.resolution
719 dt = self.theclass.min + tiny
720 dt -= tiny # no problem
721 self.assertRaises(OverflowError, dt.__sub__, tiny)
722 self.assertRaises(OverflowError, dt.__add__, -tiny)
724 dt = self.theclass.max - tiny
725 dt += tiny # no problem
726 self.assertRaises(OverflowError, dt.__add__, tiny)
727 self.assertRaises(OverflowError, dt.__sub__, -tiny)
729 def test_fromtimestamp(self):
730 import time
732 # Try an arbitrary fixed value.
733 year, month, day = 1999, 9, 19
734 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
735 d = self.theclass.fromtimestamp(ts)
736 self.assertEqual(d.year, year)
737 self.assertEqual(d.month, month)
738 self.assertEqual(d.day, day)
740 def test_insane_fromtimestamp(self):
741 # It's possible that some platform maps time_t to double,
742 # and that this test will fail there. This test should
743 # exempt such platforms (provided they return reasonable
744 # results!).
745 for insane in -1e200, 1e200:
746 self.assertRaises(ValueError, self.theclass.fromtimestamp,
747 insane)
749 def test_today(self):
750 import time
752 # We claim that today() is like fromtimestamp(time.time()), so
753 # prove it.
754 for dummy in range(3):
755 today = self.theclass.today()
756 ts = time.time()
757 todayagain = self.theclass.fromtimestamp(ts)
758 if today == todayagain:
759 break
760 # There are several legit reasons that could fail:
761 # 1. It recently became midnight, between the today() and the
762 # time() calls.
763 # 2. The platform time() has such fine resolution that we'll
764 # never get the same value twice.
765 # 3. The platform time() has poor resolution, and we just
766 # happened to call today() right before a resolution quantum
767 # boundary.
768 # 4. The system clock got fiddled between calls.
769 # In any case, wait a little while and try again.
770 time.sleep(0.1)
772 # It worked or it didn't. If it didn't, assume it's reason #2, and
773 # let the test pass if they're within half a second of each other.
774 self.assertTrue(today == todayagain or
775 abs(todayagain - today) < timedelta(seconds=0.5))
777 def test_weekday(self):
778 for i in range(7):
779 # March 4, 2002 is a Monday
780 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
781 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
782 # January 2, 1956 is a Monday
783 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
784 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
786 def test_isocalendar(self):
787 # Check examples from
788 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
789 for i in range(7):
790 d = self.theclass(2003, 12, 22+i)
791 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
792 d = self.theclass(2003, 12, 29) + timedelta(i)
793 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
794 d = self.theclass(2004, 1, 5+i)
795 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
796 d = self.theclass(2009, 12, 21+i)
797 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
798 d = self.theclass(2009, 12, 28) + timedelta(i)
799 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
800 d = self.theclass(2010, 1, 4+i)
801 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
803 def test_iso_long_years(self):
804 # Calculate long ISO years and compare to table from
805 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
806 ISO_LONG_YEARS_TABLE = """
807 4 32 60 88
808 9 37 65 93
809 15 43 71 99
810 20 48 76
811 26 54 82
813 105 133 161 189
814 111 139 167 195
815 116 144 172
816 122 150 178
817 128 156 184
819 201 229 257 285
820 207 235 263 291
821 212 240 268 296
822 218 246 274
823 224 252 280
825 303 331 359 387
826 308 336 364 392
827 314 342 370 398
828 320 348 376
829 325 353 381
831 iso_long_years = map(int, ISO_LONG_YEARS_TABLE.split())
832 iso_long_years.sort()
833 L = []
834 for i in range(400):
835 d = self.theclass(2000+i, 12, 31)
836 d1 = self.theclass(1600+i, 12, 31)
837 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
838 if d.isocalendar()[1] == 53:
839 L.append(i)
840 self.assertEqual(L, iso_long_years)
842 def test_isoformat(self):
843 t = self.theclass(2, 3, 2)
844 self.assertEqual(t.isoformat(), "0002-03-02")
846 def test_ctime(self):
847 t = self.theclass(2002, 3, 2)
848 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
850 def test_strftime(self):
851 t = self.theclass(2005, 3, 2)
852 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
853 self.assertEqual(t.strftime(""), "") # SF bug #761337
854 self.assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784
856 self.assertRaises(TypeError, t.strftime) # needs an arg
857 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
858 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
860 # test that unicode input is allowed (issue 2782)
861 self.assertEqual(t.strftime(u"%m"), "03")
863 # A naive object replaces %z and %Z w/ empty strings.
864 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
866 #make sure that invalid format specifiers are handled correctly
867 #self.assertRaises(ValueError, t.strftime, "%e")
868 #self.assertRaises(ValueError, t.strftime, "%")
869 #self.assertRaises(ValueError, t.strftime, "%#")
871 #oh well, some systems just ignore those invalid ones.
872 #at least, excercise them to make sure that no crashes
873 #are generated
874 for f in ["%e", "%", "%#"]:
875 try:
876 t.strftime(f)
877 except ValueError:
878 pass
880 #check that this standard extension works
881 t.strftime("%f")
884 def test_format(self):
885 dt = self.theclass(2007, 9, 10)
886 self.assertEqual(dt.__format__(''), str(dt))
888 # check that a derived class's __str__() gets called
889 class A(self.theclass):
890 def __str__(self):
891 return 'A'
892 a = A(2007, 9, 10)
893 self.assertEqual(a.__format__(''), 'A')
895 # check that a derived class's strftime gets called
896 class B(self.theclass):
897 def strftime(self, format_spec):
898 return 'B'
899 b = B(2007, 9, 10)
900 self.assertEqual(b.__format__(''), str(dt))
902 for fmt in ["m:%m d:%d y:%y",
903 "m:%m d:%d y:%y H:%H M:%M S:%S",
904 "%z %Z",
906 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
907 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
908 self.assertEqual(b.__format__(fmt), 'B')
910 def test_resolution_info(self):
911 self.assertTrue(isinstance(self.theclass.min, self.theclass))
912 self.assertTrue(isinstance(self.theclass.max, self.theclass))
913 self.assertTrue(isinstance(self.theclass.resolution, timedelta))
914 self.assertTrue(self.theclass.max > self.theclass.min)
916 def test_extreme_timedelta(self):
917 big = self.theclass.max - self.theclass.min
918 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
919 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
920 # n == 315537897599999999 ~= 2**58.13
921 justasbig = timedelta(0, 0, n)
922 self.assertEqual(big, justasbig)
923 self.assertEqual(self.theclass.min + big, self.theclass.max)
924 self.assertEqual(self.theclass.max - big, self.theclass.min)
926 def test_timetuple(self):
927 for i in range(7):
928 # January 2, 1956 is a Monday (0)
929 d = self.theclass(1956, 1, 2+i)
930 t = d.timetuple()
931 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
932 # February 1, 1956 is a Wednesday (2)
933 d = self.theclass(1956, 2, 1+i)
934 t = d.timetuple()
935 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
936 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
937 # of the year.
938 d = self.theclass(1956, 3, 1+i)
939 t = d.timetuple()
940 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
941 self.assertEqual(t.tm_year, 1956)
942 self.assertEqual(t.tm_mon, 3)
943 self.assertEqual(t.tm_mday, 1+i)
944 self.assertEqual(t.tm_hour, 0)
945 self.assertEqual(t.tm_min, 0)
946 self.assertEqual(t.tm_sec, 0)
947 self.assertEqual(t.tm_wday, (3+i)%7)
948 self.assertEqual(t.tm_yday, 61+i)
949 self.assertEqual(t.tm_isdst, -1)
951 def test_pickling(self):
952 args = 6, 7, 23
953 orig = self.theclass(*args)
954 for pickler, unpickler, proto in pickle_choices:
955 green = pickler.dumps(orig, proto)
956 derived = unpickler.loads(green)
957 self.assertEqual(orig, derived)
959 def test_compare(self):
960 t1 = self.theclass(2, 3, 4)
961 t2 = self.theclass(2, 3, 4)
962 self.assertTrue(t1 == t2)
963 self.assertTrue(t1 <= t2)
964 self.assertTrue(t1 >= t2)
965 self.assertTrue(not t1 != t2)
966 self.assertTrue(not t1 < t2)
967 self.assertTrue(not t1 > t2)
968 self.assertEqual(cmp(t1, t2), 0)
969 self.assertEqual(cmp(t2, t1), 0)
971 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
972 t2 = self.theclass(*args) # this is larger than t1
973 self.assertTrue(t1 < t2)
974 self.assertTrue(t2 > t1)
975 self.assertTrue(t1 <= t2)
976 self.assertTrue(t2 >= t1)
977 self.assertTrue(t1 != t2)
978 self.assertTrue(t2 != t1)
979 self.assertTrue(not t1 == t2)
980 self.assertTrue(not t2 == t1)
981 self.assertTrue(not t1 > t2)
982 self.assertTrue(not t2 < t1)
983 self.assertTrue(not t1 >= t2)
984 self.assertTrue(not t2 <= t1)
985 self.assertEqual(cmp(t1, t2), -1)
986 self.assertEqual(cmp(t2, t1), 1)
988 for badarg in OTHERSTUFF:
989 self.assertEqual(t1 == badarg, False)
990 self.assertEqual(t1 != badarg, True)
991 self.assertEqual(badarg == t1, False)
992 self.assertEqual(badarg != t1, True)
994 self.assertRaises(TypeError, lambda: t1 < badarg)
995 self.assertRaises(TypeError, lambda: t1 > badarg)
996 self.assertRaises(TypeError, lambda: t1 >= badarg)
997 self.assertRaises(TypeError, lambda: badarg <= t1)
998 self.assertRaises(TypeError, lambda: badarg < t1)
999 self.assertRaises(TypeError, lambda: badarg > t1)
1000 self.assertRaises(TypeError, lambda: badarg >= t1)
1002 def test_mixed_compare(self):
1003 our = self.theclass(2000, 4, 5)
1004 self.assertRaises(TypeError, cmp, our, 1)
1005 self.assertRaises(TypeError, cmp, 1, our)
1007 class AnotherDateTimeClass(object):
1008 def __cmp__(self, other):
1009 # Return "equal" so calling this can't be confused with
1010 # compare-by-address (which never says "equal" for distinct
1011 # objects).
1012 return 0
1013 __hash__ = None # Silence Py3k warning
1015 # This still errors, because date and datetime comparison raise
1016 # TypeError instead of NotImplemented when they don't know what to
1017 # do, in order to stop comparison from falling back to the default
1018 # compare-by-address.
1019 their = AnotherDateTimeClass()
1020 self.assertRaises(TypeError, cmp, our, their)
1021 # Oops: The next stab raises TypeError in the C implementation,
1022 # but not in the Python implementation of datetime. The difference
1023 # is due to that the Python implementation defines __cmp__ but
1024 # the C implementation defines tp_richcompare. This is more pain
1025 # to fix than it's worth, so commenting out the test.
1026 # self.assertEqual(cmp(their, our), 0)
1028 # But date and datetime comparison return NotImplemented instead if the
1029 # other object has a timetuple attr. This gives the other object a
1030 # chance to do the comparison.
1031 class Comparable(AnotherDateTimeClass):
1032 def timetuple(self):
1033 return ()
1035 their = Comparable()
1036 self.assertEqual(cmp(our, their), 0)
1037 self.assertEqual(cmp(their, our), 0)
1038 self.assertTrue(our == their)
1039 self.assertTrue(their == our)
1041 def test_bool(self):
1042 # All dates are considered true.
1043 self.assertTrue(self.theclass.min)
1044 self.assertTrue(self.theclass.max)
1046 def test_strftime_out_of_range(self):
1047 # For nasty technical reasons, we can't handle years before 1900.
1048 cls = self.theclass
1049 self.assertEqual(cls(1900, 1, 1).strftime("%Y"), "1900")
1050 for y in 1, 49, 51, 99, 100, 1000, 1899:
1051 self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y")
1053 def test_replace(self):
1054 cls = self.theclass
1055 args = [1, 2, 3]
1056 base = cls(*args)
1057 self.assertEqual(base, base.replace())
1059 i = 0
1060 for name, newval in (("year", 2),
1061 ("month", 3),
1062 ("day", 4)):
1063 newargs = args[:]
1064 newargs[i] = newval
1065 expected = cls(*newargs)
1066 got = base.replace(**{name: newval})
1067 self.assertEqual(expected, got)
1068 i += 1
1070 # Out of bounds.
1071 base = cls(2000, 2, 29)
1072 self.assertRaises(ValueError, base.replace, year=2001)
1074 def test_subclass_date(self):
1076 class C(self.theclass):
1077 theAnswer = 42
1079 def __new__(cls, *args, **kws):
1080 temp = kws.copy()
1081 extra = temp.pop('extra')
1082 result = self.theclass.__new__(cls, *args, **temp)
1083 result.extra = extra
1084 return result
1086 def newmeth(self, start):
1087 return start + self.year + self.month
1089 args = 2003, 4, 14
1091 dt1 = self.theclass(*args)
1092 dt2 = C(*args, **{'extra': 7})
1094 self.assertEqual(dt2.__class__, C)
1095 self.assertEqual(dt2.theAnswer, 42)
1096 self.assertEqual(dt2.extra, 7)
1097 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1098 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1100 def test_pickling_subclass_date(self):
1102 args = 6, 7, 23
1103 orig = SubclassDate(*args)
1104 for pickler, unpickler, proto in pickle_choices:
1105 green = pickler.dumps(orig, proto)
1106 derived = unpickler.loads(green)
1107 self.assertEqual(orig, derived)
1109 def test_backdoor_resistance(self):
1110 # For fast unpickling, the constructor accepts a pickle string.
1111 # This is a low-overhead backdoor. A user can (by intent or
1112 # mistake) pass a string directly, which (if it's the right length)
1113 # will get treated like a pickle, and bypass the normal sanity
1114 # checks in the constructor. This can create insane objects.
1115 # The constructor doesn't want to burn the time to validate all
1116 # fields, but does check the month field. This stops, e.g.,
1117 # datetime.datetime('1995-03-25') from yielding an insane object.
1118 base = '1995-03-25'
1119 if not issubclass(self.theclass, datetime):
1120 base = base[:4]
1121 for month_byte in '9', chr(0), chr(13), '\xff':
1122 self.assertRaises(TypeError, self.theclass,
1123 base[:2] + month_byte + base[3:])
1124 for ord_byte in range(1, 13):
1125 # This shouldn't blow up because of the month byte alone. If
1126 # the implementation changes to do more-careful checking, it may
1127 # blow up because other fields are insane.
1128 self.theclass(base[:2] + chr(ord_byte) + base[3:])
1130 #############################################################################
1131 # datetime tests
1133 class SubclassDatetime(datetime):
1134 sub_var = 1
1136 class TestDateTime(TestDate):
1138 theclass = datetime
1140 def test_basic_attributes(self):
1141 dt = self.theclass(2002, 3, 1, 12, 0)
1142 self.assertEqual(dt.year, 2002)
1143 self.assertEqual(dt.month, 3)
1144 self.assertEqual(dt.day, 1)
1145 self.assertEqual(dt.hour, 12)
1146 self.assertEqual(dt.minute, 0)
1147 self.assertEqual(dt.second, 0)
1148 self.assertEqual(dt.microsecond, 0)
1150 def test_basic_attributes_nonzero(self):
1151 # Make sure all attributes are non-zero so bugs in
1152 # bit-shifting access show up.
1153 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1154 self.assertEqual(dt.year, 2002)
1155 self.assertEqual(dt.month, 3)
1156 self.assertEqual(dt.day, 1)
1157 self.assertEqual(dt.hour, 12)
1158 self.assertEqual(dt.minute, 59)
1159 self.assertEqual(dt.second, 59)
1160 self.assertEqual(dt.microsecond, 8000)
1162 def test_roundtrip(self):
1163 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1164 self.theclass.now()):
1165 # Verify dt -> string -> datetime identity.
1166 s = repr(dt)
1167 self.assertTrue(s.startswith('datetime.'))
1168 s = s[9:]
1169 dt2 = eval(s)
1170 self.assertEqual(dt, dt2)
1172 # Verify identity via reconstructing from pieces.
1173 dt2 = self.theclass(dt.year, dt.month, dt.day,
1174 dt.hour, dt.minute, dt.second,
1175 dt.microsecond)
1176 self.assertEqual(dt, dt2)
1178 def test_isoformat(self):
1179 t = self.theclass(2, 3, 2, 4, 5, 1, 123)
1180 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123")
1181 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123")
1182 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123")
1183 # str is ISO format with the separator forced to a blank.
1184 self.assertEqual(str(t), "0002-03-02 04:05:01.000123")
1186 t = self.theclass(2, 3, 2)
1187 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1188 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1189 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1190 # str is ISO format with the separator forced to a blank.
1191 self.assertEqual(str(t), "0002-03-02 00:00:00")
1193 def test_format(self):
1194 dt = self.theclass(2007, 9, 10, 4, 5, 1, 123)
1195 self.assertEqual(dt.__format__(''), str(dt))
1197 # check that a derived class's __str__() gets called
1198 class A(self.theclass):
1199 def __str__(self):
1200 return 'A'
1201 a = A(2007, 9, 10, 4, 5, 1, 123)
1202 self.assertEqual(a.__format__(''), 'A')
1204 # check that a derived class's strftime gets called
1205 class B(self.theclass):
1206 def strftime(self, format_spec):
1207 return 'B'
1208 b = B(2007, 9, 10, 4, 5, 1, 123)
1209 self.assertEqual(b.__format__(''), str(dt))
1211 for fmt in ["m:%m d:%d y:%y",
1212 "m:%m d:%d y:%y H:%H M:%M S:%S",
1213 "%z %Z",
1215 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1216 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1217 self.assertEqual(b.__format__(fmt), 'B')
1219 def test_more_ctime(self):
1220 # Test fields that TestDate doesn't touch.
1221 import time
1223 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1224 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1225 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1226 # out. The difference is that t.ctime() produces " 2" for the day,
1227 # but platform ctime() produces "02" for the day. According to
1228 # C99, t.ctime() is correct here.
1229 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1231 # So test a case where that difference doesn't matter.
1232 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1233 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1235 def test_tz_independent_comparing(self):
1236 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1237 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1238 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1239 self.assertEqual(dt1, dt3)
1240 self.assertTrue(dt2 > dt3)
1242 # Make sure comparison doesn't forget microseconds, and isn't done
1243 # via comparing a float timestamp (an IEEE double doesn't have enough
1244 # precision to span microsecond resolution across years 1 thru 9999,
1245 # so comparing via timestamp necessarily calls some distinct values
1246 # equal).
1247 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1248 us = timedelta(microseconds=1)
1249 dt2 = dt1 + us
1250 self.assertEqual(dt2 - dt1, us)
1251 self.assertTrue(dt1 < dt2)
1253 def test_strftime_with_bad_tzname_replace(self):
1254 # verify ok if tzinfo.tzname().replace() returns a non-string
1255 class MyTzInfo(FixedOffset):
1256 def tzname(self, dt):
1257 class MyStr(str):
1258 def replace(self, *args):
1259 return None
1260 return MyStr('name')
1261 t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
1262 self.assertRaises(TypeError, t.strftime, '%Z')
1264 def test_bad_constructor_arguments(self):
1265 # bad years
1266 self.theclass(MINYEAR, 1, 1) # no exception
1267 self.theclass(MAXYEAR, 1, 1) # no exception
1268 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1269 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1270 # bad months
1271 self.theclass(2000, 1, 1) # no exception
1272 self.theclass(2000, 12, 1) # no exception
1273 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1274 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1275 # bad days
1276 self.theclass(2000, 2, 29) # no exception
1277 self.theclass(2004, 2, 29) # no exception
1278 self.theclass(2400, 2, 29) # no exception
1279 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1280 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1281 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1282 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1283 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1284 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1285 # bad hours
1286 self.theclass(2000, 1, 31, 0) # no exception
1287 self.theclass(2000, 1, 31, 23) # no exception
1288 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1289 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1290 # bad minutes
1291 self.theclass(2000, 1, 31, 23, 0) # no exception
1292 self.theclass(2000, 1, 31, 23, 59) # no exception
1293 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1294 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1295 # bad seconds
1296 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1297 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1298 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1299 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1300 # bad microseconds
1301 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1302 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1303 self.assertRaises(ValueError, self.theclass,
1304 2000, 1, 31, 23, 59, 59, -1)
1305 self.assertRaises(ValueError, self.theclass,
1306 2000, 1, 31, 23, 59, 59,
1307 1000000)
1309 def test_hash_equality(self):
1310 d = self.theclass(2000, 12, 31, 23, 30, 17)
1311 e = self.theclass(2000, 12, 31, 23, 30, 17)
1312 self.assertEqual(d, e)
1313 self.assertEqual(hash(d), hash(e))
1315 dic = {d: 1}
1316 dic[e] = 2
1317 self.assertEqual(len(dic), 1)
1318 self.assertEqual(dic[d], 2)
1319 self.assertEqual(dic[e], 2)
1321 d = self.theclass(2001, 1, 1, 0, 5, 17)
1322 e = self.theclass(2001, 1, 1, 0, 5, 17)
1323 self.assertEqual(d, e)
1324 self.assertEqual(hash(d), hash(e))
1326 dic = {d: 1}
1327 dic[e] = 2
1328 self.assertEqual(len(dic), 1)
1329 self.assertEqual(dic[d], 2)
1330 self.assertEqual(dic[e], 2)
1332 def test_computations(self):
1333 a = self.theclass(2002, 1, 31)
1334 b = self.theclass(1956, 1, 31)
1335 diff = a-b
1336 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1337 self.assertEqual(diff.seconds, 0)
1338 self.assertEqual(diff.microseconds, 0)
1339 a = self.theclass(2002, 3, 2, 17, 6)
1340 millisec = timedelta(0, 0, 1000)
1341 hour = timedelta(0, 3600)
1342 day = timedelta(1)
1343 week = timedelta(7)
1344 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1345 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1346 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1347 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1348 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1349 self.assertEqual(a - hour, a + -hour)
1350 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1351 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1352 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1353 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1354 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1355 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1356 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1357 self.assertEqual((a + week) - a, week)
1358 self.assertEqual((a + day) - a, day)
1359 self.assertEqual((a + hour) - a, hour)
1360 self.assertEqual((a + millisec) - a, millisec)
1361 self.assertEqual((a - week) - a, -week)
1362 self.assertEqual((a - day) - a, -day)
1363 self.assertEqual((a - hour) - a, -hour)
1364 self.assertEqual((a - millisec) - a, -millisec)
1365 self.assertEqual(a - (a + week), -week)
1366 self.assertEqual(a - (a + day), -day)
1367 self.assertEqual(a - (a + hour), -hour)
1368 self.assertEqual(a - (a + millisec), -millisec)
1369 self.assertEqual(a - (a - week), week)
1370 self.assertEqual(a - (a - day), day)
1371 self.assertEqual(a - (a - hour), hour)
1372 self.assertEqual(a - (a - millisec), millisec)
1373 self.assertEqual(a + (week + day + hour + millisec),
1374 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1375 self.assertEqual(a + (week + day + hour + millisec),
1376 (((a + week) + day) + hour) + millisec)
1377 self.assertEqual(a - (week + day + hour + millisec),
1378 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1379 self.assertEqual(a - (week + day + hour + millisec),
1380 (((a - week) - day) - hour) - millisec)
1381 # Add/sub ints, longs, floats should be illegal
1382 for i in 1, 1L, 1.0:
1383 self.assertRaises(TypeError, lambda: a+i)
1384 self.assertRaises(TypeError, lambda: a-i)
1385 self.assertRaises(TypeError, lambda: i+a)
1386 self.assertRaises(TypeError, lambda: i-a)
1388 # delta - datetime is senseless.
1389 self.assertRaises(TypeError, lambda: day - a)
1390 # mixing datetime and (delta or datetime) via * or // is senseless
1391 self.assertRaises(TypeError, lambda: day * a)
1392 self.assertRaises(TypeError, lambda: a * day)
1393 self.assertRaises(TypeError, lambda: day // a)
1394 self.assertRaises(TypeError, lambda: a // day)
1395 self.assertRaises(TypeError, lambda: a * a)
1396 self.assertRaises(TypeError, lambda: a // a)
1397 # datetime + datetime is senseless
1398 self.assertRaises(TypeError, lambda: a + a)
1400 def test_pickling(self):
1401 args = 6, 7, 23, 20, 59, 1, 64**2
1402 orig = self.theclass(*args)
1403 for pickler, unpickler, proto in pickle_choices:
1404 green = pickler.dumps(orig, proto)
1405 derived = unpickler.loads(green)
1406 self.assertEqual(orig, derived)
1408 def test_more_pickling(self):
1409 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
1410 s = pickle.dumps(a)
1411 b = pickle.loads(s)
1412 self.assertEqual(b.year, 2003)
1413 self.assertEqual(b.month, 2)
1414 self.assertEqual(b.day, 7)
1416 def test_pickling_subclass_datetime(self):
1417 args = 6, 7, 23, 20, 59, 1, 64**2
1418 orig = SubclassDatetime(*args)
1419 for pickler, unpickler, proto in pickle_choices:
1420 green = pickler.dumps(orig, proto)
1421 derived = unpickler.loads(green)
1422 self.assertEqual(orig, derived)
1424 def test_more_compare(self):
1425 # The test_compare() inherited from TestDate covers the error cases.
1426 # We just want to test lexicographic ordering on the members datetime
1427 # has that date lacks.
1428 args = [2000, 11, 29, 20, 58, 16, 999998]
1429 t1 = self.theclass(*args)
1430 t2 = self.theclass(*args)
1431 self.assertTrue(t1 == t2)
1432 self.assertTrue(t1 <= t2)
1433 self.assertTrue(t1 >= t2)
1434 self.assertTrue(not t1 != t2)
1435 self.assertTrue(not t1 < t2)
1436 self.assertTrue(not t1 > t2)
1437 self.assertEqual(cmp(t1, t2), 0)
1438 self.assertEqual(cmp(t2, t1), 0)
1440 for i in range(len(args)):
1441 newargs = args[:]
1442 newargs[i] = args[i] + 1
1443 t2 = self.theclass(*newargs) # this is larger than t1
1444 self.assertTrue(t1 < t2)
1445 self.assertTrue(t2 > t1)
1446 self.assertTrue(t1 <= t2)
1447 self.assertTrue(t2 >= t1)
1448 self.assertTrue(t1 != t2)
1449 self.assertTrue(t2 != t1)
1450 self.assertTrue(not t1 == t2)
1451 self.assertTrue(not t2 == t1)
1452 self.assertTrue(not t1 > t2)
1453 self.assertTrue(not t2 < t1)
1454 self.assertTrue(not t1 >= t2)
1455 self.assertTrue(not t2 <= t1)
1456 self.assertEqual(cmp(t1, t2), -1)
1457 self.assertEqual(cmp(t2, t1), 1)
1460 # A helper for timestamp constructor tests.
1461 def verify_field_equality(self, expected, got):
1462 self.assertEqual(expected.tm_year, got.year)
1463 self.assertEqual(expected.tm_mon, got.month)
1464 self.assertEqual(expected.tm_mday, got.day)
1465 self.assertEqual(expected.tm_hour, got.hour)
1466 self.assertEqual(expected.tm_min, got.minute)
1467 self.assertEqual(expected.tm_sec, got.second)
1469 def test_fromtimestamp(self):
1470 import time
1472 ts = time.time()
1473 expected = time.localtime(ts)
1474 got = self.theclass.fromtimestamp(ts)
1475 self.verify_field_equality(expected, got)
1477 def test_utcfromtimestamp(self):
1478 import time
1480 ts = time.time()
1481 expected = time.gmtime(ts)
1482 got = self.theclass.utcfromtimestamp(ts)
1483 self.verify_field_equality(expected, got)
1485 def test_microsecond_rounding(self):
1486 # Test whether fromtimestamp "rounds up" floats that are less
1487 # than one microsecond smaller than an integer.
1488 self.assertEquals(self.theclass.fromtimestamp(0.9999999),
1489 self.theclass.fromtimestamp(1))
1491 def test_insane_fromtimestamp(self):
1492 # It's possible that some platform maps time_t to double,
1493 # and that this test will fail there. This test should
1494 # exempt such platforms (provided they return reasonable
1495 # results!).
1496 for insane in -1e200, 1e200:
1497 self.assertRaises(ValueError, self.theclass.fromtimestamp,
1498 insane)
1500 def test_insane_utcfromtimestamp(self):
1501 # It's possible that some platform maps time_t to double,
1502 # and that this test will fail there. This test should
1503 # exempt such platforms (provided they return reasonable
1504 # results!).
1505 for insane in -1e200, 1e200:
1506 self.assertRaises(ValueError, self.theclass.utcfromtimestamp,
1507 insane)
1509 def test_negative_float_fromtimestamp(self):
1510 # Windows doesn't accept negative timestamps
1511 if os.name == "nt":
1512 return
1513 # The result is tz-dependent; at least test that this doesn't
1514 # fail (like it did before bug 1646728 was fixed).
1515 self.theclass.fromtimestamp(-1.05)
1517 def test_negative_float_utcfromtimestamp(self):
1518 # Windows doesn't accept negative timestamps
1519 if os.name == "nt":
1520 return
1521 d = self.theclass.utcfromtimestamp(-1.05)
1522 self.assertEquals(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
1524 def test_utcnow(self):
1525 import time
1527 # Call it a success if utcnow() and utcfromtimestamp() are within
1528 # a second of each other.
1529 tolerance = timedelta(seconds=1)
1530 for dummy in range(3):
1531 from_now = self.theclass.utcnow()
1532 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1533 if abs(from_timestamp - from_now) <= tolerance:
1534 break
1535 # Else try again a few times.
1536 self.assertTrue(abs(from_timestamp - from_now) <= tolerance)
1538 def test_strptime(self):
1539 import _strptime
1541 string = '2004-12-01 13:02:47.197'
1542 format = '%Y-%m-%d %H:%M:%S.%f'
1543 result, frac = _strptime._strptime(string, format)
1544 expected = self.theclass(*(result[0:6]+(frac,)))
1545 got = self.theclass.strptime(string, format)
1546 self.assertEqual(expected, got)
1548 def test_more_timetuple(self):
1549 # This tests fields beyond those tested by the TestDate.test_timetuple.
1550 t = self.theclass(2004, 12, 31, 6, 22, 33)
1551 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
1552 self.assertEqual(t.timetuple(),
1553 (t.year, t.month, t.day,
1554 t.hour, t.minute, t.second,
1555 t.weekday(),
1556 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
1557 -1))
1558 tt = t.timetuple()
1559 self.assertEqual(tt.tm_year, t.year)
1560 self.assertEqual(tt.tm_mon, t.month)
1561 self.assertEqual(tt.tm_mday, t.day)
1562 self.assertEqual(tt.tm_hour, t.hour)
1563 self.assertEqual(tt.tm_min, t.minute)
1564 self.assertEqual(tt.tm_sec, t.second)
1565 self.assertEqual(tt.tm_wday, t.weekday())
1566 self.assertEqual(tt.tm_yday, t.toordinal() -
1567 date(t.year, 1, 1).toordinal() + 1)
1568 self.assertEqual(tt.tm_isdst, -1)
1570 def test_more_strftime(self):
1571 # This tests fields beyond those tested by the TestDate.test_strftime.
1572 t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
1573 self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
1574 "12 31 04 000047 33 22 06 366")
1576 def test_extract(self):
1577 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1578 self.assertEqual(dt.date(), date(2002, 3, 4))
1579 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
1581 def test_combine(self):
1582 d = date(2002, 3, 4)
1583 t = time(18, 45, 3, 1234)
1584 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1585 combine = self.theclass.combine
1586 dt = combine(d, t)
1587 self.assertEqual(dt, expected)
1589 dt = combine(time=t, date=d)
1590 self.assertEqual(dt, expected)
1592 self.assertEqual(d, dt.date())
1593 self.assertEqual(t, dt.time())
1594 self.assertEqual(dt, combine(dt.date(), dt.time()))
1596 self.assertRaises(TypeError, combine) # need an arg
1597 self.assertRaises(TypeError, combine, d) # need two args
1598 self.assertRaises(TypeError, combine, t, d) # args reversed
1599 self.assertRaises(TypeError, combine, d, t, 1) # too many args
1600 self.assertRaises(TypeError, combine, "date", "time") # wrong types
1602 def test_replace(self):
1603 cls = self.theclass
1604 args = [1, 2, 3, 4, 5, 6, 7]
1605 base = cls(*args)
1606 self.assertEqual(base, base.replace())
1608 i = 0
1609 for name, newval in (("year", 2),
1610 ("month", 3),
1611 ("day", 4),
1612 ("hour", 5),
1613 ("minute", 6),
1614 ("second", 7),
1615 ("microsecond", 8)):
1616 newargs = args[:]
1617 newargs[i] = newval
1618 expected = cls(*newargs)
1619 got = base.replace(**{name: newval})
1620 self.assertEqual(expected, got)
1621 i += 1
1623 # Out of bounds.
1624 base = cls(2000, 2, 29)
1625 self.assertRaises(ValueError, base.replace, year=2001)
1627 def test_astimezone(self):
1628 # Pretty boring! The TZ test is more interesting here. astimezone()
1629 # simply can't be applied to a naive object.
1630 dt = self.theclass.now()
1631 f = FixedOffset(44, "")
1632 self.assertRaises(TypeError, dt.astimezone) # not enough args
1633 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
1634 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
1635 self.assertRaises(ValueError, dt.astimezone, f) # naive
1636 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
1638 class Bogus(tzinfo):
1639 def utcoffset(self, dt): return None
1640 def dst(self, dt): return timedelta(0)
1641 bog = Bogus()
1642 self.assertRaises(ValueError, dt.astimezone, bog) # naive
1644 class AlsoBogus(tzinfo):
1645 def utcoffset(self, dt): return timedelta(0)
1646 def dst(self, dt): return None
1647 alsobog = AlsoBogus()
1648 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
1650 def test_subclass_datetime(self):
1652 class C(self.theclass):
1653 theAnswer = 42
1655 def __new__(cls, *args, **kws):
1656 temp = kws.copy()
1657 extra = temp.pop('extra')
1658 result = self.theclass.__new__(cls, *args, **temp)
1659 result.extra = extra
1660 return result
1662 def newmeth(self, start):
1663 return start + self.year + self.month + self.second
1665 args = 2003, 4, 14, 12, 13, 41
1667 dt1 = self.theclass(*args)
1668 dt2 = C(*args, **{'extra': 7})
1670 self.assertEqual(dt2.__class__, C)
1671 self.assertEqual(dt2.theAnswer, 42)
1672 self.assertEqual(dt2.extra, 7)
1673 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1674 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
1675 dt1.second - 7)
1677 class SubclassTime(time):
1678 sub_var = 1
1680 class TestTime(HarmlessMixedComparison, unittest.TestCase):
1682 theclass = time
1684 def test_basic_attributes(self):
1685 t = self.theclass(12, 0)
1686 self.assertEqual(t.hour, 12)
1687 self.assertEqual(t.minute, 0)
1688 self.assertEqual(t.second, 0)
1689 self.assertEqual(t.microsecond, 0)
1691 def test_basic_attributes_nonzero(self):
1692 # Make sure all attributes are non-zero so bugs in
1693 # bit-shifting access show up.
1694 t = self.theclass(12, 59, 59, 8000)
1695 self.assertEqual(t.hour, 12)
1696 self.assertEqual(t.minute, 59)
1697 self.assertEqual(t.second, 59)
1698 self.assertEqual(t.microsecond, 8000)
1700 def test_roundtrip(self):
1701 t = self.theclass(1, 2, 3, 4)
1703 # Verify t -> string -> time identity.
1704 s = repr(t)
1705 self.assertTrue(s.startswith('datetime.'))
1706 s = s[9:]
1707 t2 = eval(s)
1708 self.assertEqual(t, t2)
1710 # Verify identity via reconstructing from pieces.
1711 t2 = self.theclass(t.hour, t.minute, t.second,
1712 t.microsecond)
1713 self.assertEqual(t, t2)
1715 def test_comparing(self):
1716 args = [1, 2, 3, 4]
1717 t1 = self.theclass(*args)
1718 t2 = self.theclass(*args)
1719 self.assertTrue(t1 == t2)
1720 self.assertTrue(t1 <= t2)
1721 self.assertTrue(t1 >= t2)
1722 self.assertTrue(not t1 != t2)
1723 self.assertTrue(not t1 < t2)
1724 self.assertTrue(not t1 > t2)
1725 self.assertEqual(cmp(t1, t2), 0)
1726 self.assertEqual(cmp(t2, t1), 0)
1728 for i in range(len(args)):
1729 newargs = args[:]
1730 newargs[i] = args[i] + 1
1731 t2 = self.theclass(*newargs) # this is larger than t1
1732 self.assertTrue(t1 < t2)
1733 self.assertTrue(t2 > t1)
1734 self.assertTrue(t1 <= t2)
1735 self.assertTrue(t2 >= t1)
1736 self.assertTrue(t1 != t2)
1737 self.assertTrue(t2 != t1)
1738 self.assertTrue(not t1 == t2)
1739 self.assertTrue(not t2 == t1)
1740 self.assertTrue(not t1 > t2)
1741 self.assertTrue(not t2 < t1)
1742 self.assertTrue(not t1 >= t2)
1743 self.assertTrue(not t2 <= t1)
1744 self.assertEqual(cmp(t1, t2), -1)
1745 self.assertEqual(cmp(t2, t1), 1)
1747 for badarg in OTHERSTUFF:
1748 self.assertEqual(t1 == badarg, False)
1749 self.assertEqual(t1 != badarg, True)
1750 self.assertEqual(badarg == t1, False)
1751 self.assertEqual(badarg != t1, True)
1753 self.assertRaises(TypeError, lambda: t1 <= badarg)
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: badarg <= t1)
1758 self.assertRaises(TypeError, lambda: badarg < t1)
1759 self.assertRaises(TypeError, lambda: badarg > t1)
1760 self.assertRaises(TypeError, lambda: badarg >= t1)
1762 def test_bad_constructor_arguments(self):
1763 # bad hours
1764 self.theclass(0, 0) # no exception
1765 self.theclass(23, 0) # no exception
1766 self.assertRaises(ValueError, self.theclass, -1, 0)
1767 self.assertRaises(ValueError, self.theclass, 24, 0)
1768 # bad minutes
1769 self.theclass(23, 0) # no exception
1770 self.theclass(23, 59) # no exception
1771 self.assertRaises(ValueError, self.theclass, 23, -1)
1772 self.assertRaises(ValueError, self.theclass, 23, 60)
1773 # bad seconds
1774 self.theclass(23, 59, 0) # no exception
1775 self.theclass(23, 59, 59) # no exception
1776 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
1777 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
1778 # bad microseconds
1779 self.theclass(23, 59, 59, 0) # no exception
1780 self.theclass(23, 59, 59, 999999) # no exception
1781 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
1782 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
1784 def test_hash_equality(self):
1785 d = self.theclass(23, 30, 17)
1786 e = self.theclass(23, 30, 17)
1787 self.assertEqual(d, e)
1788 self.assertEqual(hash(d), hash(e))
1790 dic = {d: 1}
1791 dic[e] = 2
1792 self.assertEqual(len(dic), 1)
1793 self.assertEqual(dic[d], 2)
1794 self.assertEqual(dic[e], 2)
1796 d = self.theclass(0, 5, 17)
1797 e = self.theclass(0, 5, 17)
1798 self.assertEqual(d, e)
1799 self.assertEqual(hash(d), hash(e))
1801 dic = {d: 1}
1802 dic[e] = 2
1803 self.assertEqual(len(dic), 1)
1804 self.assertEqual(dic[d], 2)
1805 self.assertEqual(dic[e], 2)
1807 def test_isoformat(self):
1808 t = self.theclass(4, 5, 1, 123)
1809 self.assertEqual(t.isoformat(), "04:05:01.000123")
1810 self.assertEqual(t.isoformat(), str(t))
1812 t = self.theclass()
1813 self.assertEqual(t.isoformat(), "00:00:00")
1814 self.assertEqual(t.isoformat(), str(t))
1816 t = self.theclass(microsecond=1)
1817 self.assertEqual(t.isoformat(), "00:00:00.000001")
1818 self.assertEqual(t.isoformat(), str(t))
1820 t = self.theclass(microsecond=10)
1821 self.assertEqual(t.isoformat(), "00:00:00.000010")
1822 self.assertEqual(t.isoformat(), str(t))
1824 t = self.theclass(microsecond=100)
1825 self.assertEqual(t.isoformat(), "00:00:00.000100")
1826 self.assertEqual(t.isoformat(), str(t))
1828 t = self.theclass(microsecond=1000)
1829 self.assertEqual(t.isoformat(), "00:00:00.001000")
1830 self.assertEqual(t.isoformat(), str(t))
1832 t = self.theclass(microsecond=10000)
1833 self.assertEqual(t.isoformat(), "00:00:00.010000")
1834 self.assertEqual(t.isoformat(), str(t))
1836 t = self.theclass(microsecond=100000)
1837 self.assertEqual(t.isoformat(), "00:00:00.100000")
1838 self.assertEqual(t.isoformat(), str(t))
1840 def test_1653736(self):
1841 # verify it doesn't accept extra keyword arguments
1842 t = self.theclass(second=1)
1843 self.assertRaises(TypeError, t.isoformat, foo=3)
1845 def test_strftime(self):
1846 t = self.theclass(1, 2, 3, 4)
1847 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
1848 # A naive object replaces %z and %Z with empty strings.
1849 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1851 def test_format(self):
1852 t = self.theclass(1, 2, 3, 4)
1853 self.assertEqual(t.__format__(''), str(t))
1855 # check that a derived class's __str__() gets called
1856 class A(self.theclass):
1857 def __str__(self):
1858 return 'A'
1859 a = A(1, 2, 3, 4)
1860 self.assertEqual(a.__format__(''), 'A')
1862 # check that a derived class's strftime gets called
1863 class B(self.theclass):
1864 def strftime(self, format_spec):
1865 return 'B'
1866 b = B(1, 2, 3, 4)
1867 self.assertEqual(b.__format__(''), str(t))
1869 for fmt in ['%H %M %S',
1871 self.assertEqual(t.__format__(fmt), t.strftime(fmt))
1872 self.assertEqual(a.__format__(fmt), t.strftime(fmt))
1873 self.assertEqual(b.__format__(fmt), 'B')
1875 def test_str(self):
1876 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
1877 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
1878 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
1879 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
1880 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
1882 def test_repr(self):
1883 name = 'datetime.' + self.theclass.__name__
1884 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
1885 "%s(1, 2, 3, 4)" % name)
1886 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
1887 "%s(10, 2, 3, 4000)" % name)
1888 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
1889 "%s(0, 2, 3, 400000)" % name)
1890 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
1891 "%s(12, 2, 3)" % name)
1892 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
1893 "%s(23, 15)" % name)
1895 def test_resolution_info(self):
1896 self.assertTrue(isinstance(self.theclass.min, self.theclass))
1897 self.assertTrue(isinstance(self.theclass.max, self.theclass))
1898 self.assertTrue(isinstance(self.theclass.resolution, timedelta))
1899 self.assertTrue(self.theclass.max > self.theclass.min)
1901 def test_pickling(self):
1902 args = 20, 59, 16, 64**2
1903 orig = self.theclass(*args)
1904 for pickler, unpickler, proto in pickle_choices:
1905 green = pickler.dumps(orig, proto)
1906 derived = unpickler.loads(green)
1907 self.assertEqual(orig, derived)
1909 def test_pickling_subclass_time(self):
1910 args = 20, 59, 16, 64**2
1911 orig = SubclassTime(*args)
1912 for pickler, unpickler, proto in pickle_choices:
1913 green = pickler.dumps(orig, proto)
1914 derived = unpickler.loads(green)
1915 self.assertEqual(orig, derived)
1917 def test_bool(self):
1918 cls = self.theclass
1919 self.assertTrue(cls(1))
1920 self.assertTrue(cls(0, 1))
1921 self.assertTrue(cls(0, 0, 1))
1922 self.assertTrue(cls(0, 0, 0, 1))
1923 self.assertTrue(not cls(0))
1924 self.assertTrue(not cls())
1926 def test_replace(self):
1927 cls = self.theclass
1928 args = [1, 2, 3, 4]
1929 base = cls(*args)
1930 self.assertEqual(base, base.replace())
1932 i = 0
1933 for name, newval in (("hour", 5),
1934 ("minute", 6),
1935 ("second", 7),
1936 ("microsecond", 8)):
1937 newargs = args[:]
1938 newargs[i] = newval
1939 expected = cls(*newargs)
1940 got = base.replace(**{name: newval})
1941 self.assertEqual(expected, got)
1942 i += 1
1944 # Out of bounds.
1945 base = cls(1)
1946 self.assertRaises(ValueError, base.replace, hour=24)
1947 self.assertRaises(ValueError, base.replace, minute=-1)
1948 self.assertRaises(ValueError, base.replace, second=100)
1949 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1951 def test_subclass_time(self):
1953 class C(self.theclass):
1954 theAnswer = 42
1956 def __new__(cls, *args, **kws):
1957 temp = kws.copy()
1958 extra = temp.pop('extra')
1959 result = self.theclass.__new__(cls, *args, **temp)
1960 result.extra = extra
1961 return result
1963 def newmeth(self, start):
1964 return start + self.hour + self.second
1966 args = 4, 5, 6
1968 dt1 = self.theclass(*args)
1969 dt2 = C(*args, **{'extra': 7})
1971 self.assertEqual(dt2.__class__, C)
1972 self.assertEqual(dt2.theAnswer, 42)
1973 self.assertEqual(dt2.extra, 7)
1974 self.assertEqual(dt1.isoformat(), dt2.isoformat())
1975 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
1977 def test_backdoor_resistance(self):
1978 # see TestDate.test_backdoor_resistance().
1979 base = '2:59.0'
1980 for hour_byte in ' ', '9', chr(24), '\xff':
1981 self.assertRaises(TypeError, self.theclass,
1982 hour_byte + base[1:])
1984 # A mixin for classes with a tzinfo= argument. Subclasses must define
1985 # theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
1986 # must be legit (which is true for time and datetime).
1987 class TZInfoBase:
1989 def test_argument_passing(self):
1990 cls = self.theclass
1991 # A datetime passes itself on, a time passes None.
1992 class introspective(tzinfo):
1993 def tzname(self, dt): return dt and "real" or "none"
1994 def utcoffset(self, dt):
1995 return timedelta(minutes = dt and 42 or -42)
1996 dst = utcoffset
1998 obj = cls(1, 2, 3, tzinfo=introspective())
2000 expected = cls is time and "none" or "real"
2001 self.assertEqual(obj.tzname(), expected)
2003 expected = timedelta(minutes=(cls is time and -42 or 42))
2004 self.assertEqual(obj.utcoffset(), expected)
2005 self.assertEqual(obj.dst(), expected)
2007 def test_bad_tzinfo_classes(self):
2008 cls = self.theclass
2009 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
2011 class NiceTry(object):
2012 def __init__(self): pass
2013 def utcoffset(self, dt): pass
2014 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
2016 class BetterTry(tzinfo):
2017 def __init__(self): pass
2018 def utcoffset(self, dt): pass
2019 b = BetterTry()
2020 t = cls(1, 1, 1, tzinfo=b)
2021 self.assertTrue(t.tzinfo is b)
2023 def test_utc_offset_out_of_bounds(self):
2024 class Edgy(tzinfo):
2025 def __init__(self, offset):
2026 self.offset = timedelta(minutes=offset)
2027 def utcoffset(self, dt):
2028 return self.offset
2030 cls = self.theclass
2031 for offset, legit in ((-1440, False),
2032 (-1439, True),
2033 (1439, True),
2034 (1440, False)):
2035 if cls is time:
2036 t = cls(1, 2, 3, tzinfo=Edgy(offset))
2037 elif cls is datetime:
2038 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
2039 else:
2040 assert 0, "impossible"
2041 if legit:
2042 aofs = abs(offset)
2043 h, m = divmod(aofs, 60)
2044 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
2045 if isinstance(t, datetime):
2046 t = t.timetz()
2047 self.assertEqual(str(t), "01:02:03" + tag)
2048 else:
2049 self.assertRaises(ValueError, str, t)
2051 def test_tzinfo_classes(self):
2052 cls = self.theclass
2053 class C1(tzinfo):
2054 def utcoffset(self, dt): return None
2055 def dst(self, dt): return None
2056 def tzname(self, dt): return None
2057 for t in (cls(1, 1, 1),
2058 cls(1, 1, 1, tzinfo=None),
2059 cls(1, 1, 1, tzinfo=C1())):
2060 self.assertTrue(t.utcoffset() is None)
2061 self.assertTrue(t.dst() is None)
2062 self.assertTrue(t.tzname() is None)
2064 class C3(tzinfo):
2065 def utcoffset(self, dt): return timedelta(minutes=-1439)
2066 def dst(self, dt): return timedelta(minutes=1439)
2067 def tzname(self, dt): return "aname"
2068 t = cls(1, 1, 1, tzinfo=C3())
2069 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
2070 self.assertEqual(t.dst(), timedelta(minutes=1439))
2071 self.assertEqual(t.tzname(), "aname")
2073 # Wrong types.
2074 class C4(tzinfo):
2075 def utcoffset(self, dt): return "aname"
2076 def dst(self, dt): return 7
2077 def tzname(self, dt): return 0
2078 t = cls(1, 1, 1, tzinfo=C4())
2079 self.assertRaises(TypeError, t.utcoffset)
2080 self.assertRaises(TypeError, t.dst)
2081 self.assertRaises(TypeError, t.tzname)
2083 # Offset out of range.
2084 class C6(tzinfo):
2085 def utcoffset(self, dt): return timedelta(hours=-24)
2086 def dst(self, dt): return timedelta(hours=24)
2087 t = cls(1, 1, 1, tzinfo=C6())
2088 self.assertRaises(ValueError, t.utcoffset)
2089 self.assertRaises(ValueError, t.dst)
2091 # Not a whole number of minutes.
2092 class C7(tzinfo):
2093 def utcoffset(self, dt): return timedelta(seconds=61)
2094 def dst(self, dt): return timedelta(microseconds=-81)
2095 t = cls(1, 1, 1, tzinfo=C7())
2096 self.assertRaises(ValueError, t.utcoffset)
2097 self.assertRaises(ValueError, t.dst)
2099 def test_aware_compare(self):
2100 cls = self.theclass
2102 # Ensure that utcoffset() gets ignored if the comparands have
2103 # the same tzinfo member.
2104 class OperandDependentOffset(tzinfo):
2105 def utcoffset(self, t):
2106 if t.minute < 10:
2107 # d0 and d1 equal after adjustment
2108 return timedelta(minutes=t.minute)
2109 else:
2110 # d2 off in the weeds
2111 return timedelta(minutes=59)
2113 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
2114 d0 = base.replace(minute=3)
2115 d1 = base.replace(minute=9)
2116 d2 = base.replace(minute=11)
2117 for x in d0, d1, d2:
2118 for y in d0, d1, d2:
2119 got = cmp(x, y)
2120 expected = cmp(x.minute, y.minute)
2121 self.assertEqual(got, expected)
2123 # However, if they're different members, uctoffset is not ignored.
2124 # Note that a time can't actually have an operand-depedent offset,
2125 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
2126 # so skip this test for time.
2127 if cls is not time:
2128 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2129 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2130 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2131 for x in d0, d1, d2:
2132 for y in d0, d1, d2:
2133 got = cmp(x, y)
2134 if (x is d0 or x is d1) and (y is d0 or y is d1):
2135 expected = 0
2136 elif x is y is d2:
2137 expected = 0
2138 elif x is d2:
2139 expected = -1
2140 else:
2141 assert y is d2
2142 expected = 1
2143 self.assertEqual(got, expected)
2146 # Testing time objects with a non-None tzinfo.
2147 class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
2148 theclass = time
2150 def test_empty(self):
2151 t = self.theclass()
2152 self.assertEqual(t.hour, 0)
2153 self.assertEqual(t.minute, 0)
2154 self.assertEqual(t.second, 0)
2155 self.assertEqual(t.microsecond, 0)
2156 self.assertTrue(t.tzinfo is None)
2158 def test_zones(self):
2159 est = FixedOffset(-300, "EST", 1)
2160 utc = FixedOffset(0, "UTC", -2)
2161 met = FixedOffset(60, "MET", 3)
2162 t1 = time( 7, 47, tzinfo=est)
2163 t2 = time(12, 47, tzinfo=utc)
2164 t3 = time(13, 47, tzinfo=met)
2165 t4 = time(microsecond=40)
2166 t5 = time(microsecond=40, tzinfo=utc)
2168 self.assertEqual(t1.tzinfo, est)
2169 self.assertEqual(t2.tzinfo, utc)
2170 self.assertEqual(t3.tzinfo, met)
2171 self.assertTrue(t4.tzinfo is None)
2172 self.assertEqual(t5.tzinfo, utc)
2174 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2175 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2176 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
2177 self.assertTrue(t4.utcoffset() is None)
2178 self.assertRaises(TypeError, t1.utcoffset, "no args")
2180 self.assertEqual(t1.tzname(), "EST")
2181 self.assertEqual(t2.tzname(), "UTC")
2182 self.assertEqual(t3.tzname(), "MET")
2183 self.assertTrue(t4.tzname() is None)
2184 self.assertRaises(TypeError, t1.tzname, "no args")
2186 self.assertEqual(t1.dst(), timedelta(minutes=1))
2187 self.assertEqual(t2.dst(), timedelta(minutes=-2))
2188 self.assertEqual(t3.dst(), timedelta(minutes=3))
2189 self.assertTrue(t4.dst() is None)
2190 self.assertRaises(TypeError, t1.dst, "no args")
2192 self.assertEqual(hash(t1), hash(t2))
2193 self.assertEqual(hash(t1), hash(t3))
2194 self.assertEqual(hash(t2), hash(t3))
2196 self.assertEqual(t1, t2)
2197 self.assertEqual(t1, t3)
2198 self.assertEqual(t2, t3)
2199 self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
2200 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
2201 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
2203 self.assertEqual(str(t1), "07:47:00-05:00")
2204 self.assertEqual(str(t2), "12:47:00+00:00")
2205 self.assertEqual(str(t3), "13:47:00+01:00")
2206 self.assertEqual(str(t4), "00:00:00.000040")
2207 self.assertEqual(str(t5), "00:00:00.000040+00:00")
2209 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
2210 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
2211 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
2212 self.assertEqual(t4.isoformat(), "00:00:00.000040")
2213 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
2215 d = 'datetime.time'
2216 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
2217 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
2218 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
2219 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
2220 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
2222 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
2223 "07:47:00 %Z=EST %z=-0500")
2224 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
2225 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
2227 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
2228 t1 = time(23, 59, tzinfo=yuck)
2229 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
2230 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
2232 # Check that an invalid tzname result raises an exception.
2233 class Badtzname(tzinfo):
2234 def tzname(self, dt): return 42
2235 t = time(2, 3, 4, tzinfo=Badtzname())
2236 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
2237 self.assertRaises(TypeError, t.strftime, "%Z")
2239 def test_hash_edge_cases(self):
2240 # Offsets that overflow a basic time.
2241 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2242 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2243 self.assertEqual(hash(t1), hash(t2))
2245 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2246 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2247 self.assertEqual(hash(t1), hash(t2))
2249 def test_pickling(self):
2250 # Try one without a tzinfo.
2251 args = 20, 59, 16, 64**2
2252 orig = self.theclass(*args)
2253 for pickler, unpickler, proto in pickle_choices:
2254 green = pickler.dumps(orig, proto)
2255 derived = unpickler.loads(green)
2256 self.assertEqual(orig, derived)
2258 # Try one with a tzinfo.
2259 tinfo = PicklableFixedOffset(-300, 'cookie')
2260 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
2261 for pickler, unpickler, proto in pickle_choices:
2262 green = pickler.dumps(orig, proto)
2263 derived = unpickler.loads(green)
2264 self.assertEqual(orig, derived)
2265 self.assertTrue(isinstance(derived.tzinfo, PicklableFixedOffset))
2266 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2267 self.assertEqual(derived.tzname(), 'cookie')
2269 def test_more_bool(self):
2270 # Test cases with non-None tzinfo.
2271 cls = self.theclass
2273 t = cls(0, tzinfo=FixedOffset(-300, ""))
2274 self.assertTrue(t)
2276 t = cls(5, tzinfo=FixedOffset(-300, ""))
2277 self.assertTrue(t)
2279 t = cls(5, tzinfo=FixedOffset(300, ""))
2280 self.assertTrue(not t)
2282 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2283 self.assertTrue(not t)
2285 # Mostly ensuring this doesn't overflow internally.
2286 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
2287 self.assertTrue(t)
2289 # But this should yield a value error -- the utcoffset is bogus.
2290 t = cls(0, tzinfo=FixedOffset(24*60, ""))
2291 self.assertRaises(ValueError, lambda: bool(t))
2293 # Likewise.
2294 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
2295 self.assertRaises(ValueError, lambda: bool(t))
2297 def test_replace(self):
2298 cls = self.theclass
2299 z100 = FixedOffset(100, "+100")
2300 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2301 args = [1, 2, 3, 4, z100]
2302 base = cls(*args)
2303 self.assertEqual(base, base.replace())
2305 i = 0
2306 for name, newval in (("hour", 5),
2307 ("minute", 6),
2308 ("second", 7),
2309 ("microsecond", 8),
2310 ("tzinfo", zm200)):
2311 newargs = args[:]
2312 newargs[i] = newval
2313 expected = cls(*newargs)
2314 got = base.replace(**{name: newval})
2315 self.assertEqual(expected, got)
2316 i += 1
2318 # Ensure we can get rid of a tzinfo.
2319 self.assertEqual(base.tzname(), "+100")
2320 base2 = base.replace(tzinfo=None)
2321 self.assertTrue(base2.tzinfo is None)
2322 self.assertTrue(base2.tzname() is None)
2324 # Ensure we can add one.
2325 base3 = base2.replace(tzinfo=z100)
2326 self.assertEqual(base, base3)
2327 self.assertTrue(base.tzinfo is base3.tzinfo)
2329 # Out of bounds.
2330 base = cls(1)
2331 self.assertRaises(ValueError, base.replace, hour=24)
2332 self.assertRaises(ValueError, base.replace, minute=-1)
2333 self.assertRaises(ValueError, base.replace, second=100)
2334 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2336 def test_mixed_compare(self):
2337 t1 = time(1, 2, 3)
2338 t2 = time(1, 2, 3)
2339 self.assertEqual(t1, t2)
2340 t2 = t2.replace(tzinfo=None)
2341 self.assertEqual(t1, t2)
2342 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2343 self.assertEqual(t1, t2)
2344 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2345 self.assertRaises(TypeError, lambda: t1 == t2)
2347 # In time w/ identical tzinfo objects, utcoffset is ignored.
2348 class Varies(tzinfo):
2349 def __init__(self):
2350 self.offset = timedelta(minutes=22)
2351 def utcoffset(self, t):
2352 self.offset += timedelta(minutes=1)
2353 return self.offset
2355 v = Varies()
2356 t1 = t2.replace(tzinfo=v)
2357 t2 = t2.replace(tzinfo=v)
2358 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2359 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2360 self.assertEqual(t1, t2)
2362 # But if they're not identical, it isn't ignored.
2363 t2 = t2.replace(tzinfo=Varies())
2364 self.assertTrue(t1 < t2) # t1's offset counter still going up
2366 def test_subclass_timetz(self):
2368 class C(self.theclass):
2369 theAnswer = 42
2371 def __new__(cls, *args, **kws):
2372 temp = kws.copy()
2373 extra = temp.pop('extra')
2374 result = self.theclass.__new__(cls, *args, **temp)
2375 result.extra = extra
2376 return result
2378 def newmeth(self, start):
2379 return start + self.hour + self.second
2381 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2383 dt1 = self.theclass(*args)
2384 dt2 = C(*args, **{'extra': 7})
2386 self.assertEqual(dt2.__class__, C)
2387 self.assertEqual(dt2.theAnswer, 42)
2388 self.assertEqual(dt2.extra, 7)
2389 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2390 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2393 # Testing datetime objects with a non-None tzinfo.
2395 class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
2396 theclass = datetime
2398 def test_trivial(self):
2399 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2400 self.assertEqual(dt.year, 1)
2401 self.assertEqual(dt.month, 2)
2402 self.assertEqual(dt.day, 3)
2403 self.assertEqual(dt.hour, 4)
2404 self.assertEqual(dt.minute, 5)
2405 self.assertEqual(dt.second, 6)
2406 self.assertEqual(dt.microsecond, 7)
2407 self.assertEqual(dt.tzinfo, None)
2409 def test_even_more_compare(self):
2410 # The test_compare() and test_more_compare() inherited from TestDate
2411 # and TestDateTime covered non-tzinfo cases.
2413 # Smallest possible after UTC adjustment.
2414 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2415 # Largest possible after UTC adjustment.
2416 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2417 tzinfo=FixedOffset(-1439, ""))
2419 # Make sure those compare correctly, and w/o overflow.
2420 self.assertTrue(t1 < t2)
2421 self.assertTrue(t1 != t2)
2422 self.assertTrue(t2 > t1)
2424 self.assertTrue(t1 == t1)
2425 self.assertTrue(t2 == t2)
2427 # Equal afer adjustment.
2428 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2429 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2430 self.assertEqual(t1, t2)
2432 # Change t1 not to subtract a minute, and t1 should be larger.
2433 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2434 self.assertTrue(t1 > t2)
2436 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2437 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2438 self.assertTrue(t1 < t2)
2440 # Back to the original t1, but make seconds resolve it.
2441 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2442 second=1)
2443 self.assertTrue(t1 > t2)
2445 # Likewise, but make microseconds resolve it.
2446 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2447 microsecond=1)
2448 self.assertTrue(t1 > t2)
2450 # Make t2 naive and it should fail.
2451 t2 = self.theclass.min
2452 self.assertRaises(TypeError, lambda: t1 == t2)
2453 self.assertEqual(t2, t2)
2455 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2456 class Naive(tzinfo):
2457 def utcoffset(self, dt): return None
2458 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
2459 self.assertRaises(TypeError, lambda: t1 == t2)
2460 self.assertEqual(t2, t2)
2462 # OTOH, it's OK to compare two of these mixing the two ways of being
2463 # naive.
2464 t1 = self.theclass(5, 6, 7)
2465 self.assertEqual(t1, t2)
2467 # Try a bogus uctoffset.
2468 class Bogus(tzinfo):
2469 def utcoffset(self, dt):
2470 return timedelta(minutes=1440) # out of bounds
2471 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2472 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
2473 self.assertRaises(ValueError, lambda: t1 == t2)
2475 def test_pickling(self):
2476 # Try one without a tzinfo.
2477 args = 6, 7, 23, 20, 59, 1, 64**2
2478 orig = self.theclass(*args)
2479 for pickler, unpickler, proto in pickle_choices:
2480 green = pickler.dumps(orig, proto)
2481 derived = unpickler.loads(green)
2482 self.assertEqual(orig, derived)
2484 # Try one with a tzinfo.
2485 tinfo = PicklableFixedOffset(-300, 'cookie')
2486 orig = self.theclass(*args, **{'tzinfo': tinfo})
2487 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
2488 for pickler, unpickler, proto in pickle_choices:
2489 green = pickler.dumps(orig, proto)
2490 derived = unpickler.loads(green)
2491 self.assertEqual(orig, derived)
2492 self.assertTrue(isinstance(derived.tzinfo,
2493 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()