blob: 49140a55264361dd0fec2e893d7f2320018284b7 [file] [log] [blame]
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001"""Test date/time type.
2
3See http://www.zope.org/Members/fdrake/DateTimeWiki/TestCases
4"""
5
6import sys
7import pickle
8import unittest
9
10from operator import lt, le, gt, ge, eq, ne, truediv, floordiv, mod
11
12from test import support
13
14import datetime as datetime_module
15from datetime import MINYEAR, MAXYEAR
16from datetime import timedelta
17from datetime import tzinfo
18from datetime import time
19from datetime import timezone
20from datetime import date, datetime
21import time as _time
22
23# Needed by test_datetime
24import _strptime
25#
26
27
28pickle_choices = [(pickle, pickle, proto)
29 for proto in range(pickle.HIGHEST_PROTOCOL + 1)]
30assert len(pickle_choices) == pickle.HIGHEST_PROTOCOL + 1
31
32# An arbitrary collection of objects of non-datetime types, for testing
33# mixed-type comparisons.
34OTHERSTUFF = (10, 34.5, "abc", {}, [], ())
35
36
37# XXX Copied from test_float.
38INF = float("inf")
39NAN = float("nan")
40
Alexander Belopolskycf86e362010-07-23 19:25:47 +000041
42#############################################################################
43# module tests
44
45class TestModule(unittest.TestCase):
46
47 def test_constants(self):
48 datetime = datetime_module
49 self.assertEqual(datetime.MINYEAR, 1)
50 self.assertEqual(datetime.MAXYEAR, 9999)
51
52#############################################################################
53# tzinfo tests
54
55class FixedOffset(tzinfo):
56
57 def __init__(self, offset, name, dstoffset=42):
58 if isinstance(offset, int):
59 offset = timedelta(minutes=offset)
60 if isinstance(dstoffset, int):
61 dstoffset = timedelta(minutes=dstoffset)
62 self.__offset = offset
63 self.__name = name
64 self.__dstoffset = dstoffset
65 def __repr__(self):
66 return self.__name.lower()
67 def utcoffset(self, dt):
68 return self.__offset
69 def tzname(self, dt):
70 return self.__name
71 def dst(self, dt):
72 return self.__dstoffset
73
74class PicklableFixedOffset(FixedOffset):
75
76 def __init__(self, offset=None, name=None, dstoffset=None):
77 FixedOffset.__init__(self, offset, name, dstoffset)
78
79class TestTZInfo(unittest.TestCase):
80
81 def test_non_abstractness(self):
82 # In order to allow subclasses to get pickled, the C implementation
83 # wasn't able to get away with having __init__ raise
84 # NotImplementedError.
85 useless = tzinfo()
86 dt = datetime.max
87 self.assertRaises(NotImplementedError, useless.tzname, dt)
88 self.assertRaises(NotImplementedError, useless.utcoffset, dt)
89 self.assertRaises(NotImplementedError, useless.dst, dt)
90
91 def test_subclass_must_override(self):
92 class NotEnough(tzinfo):
93 def __init__(self, offset, name):
94 self.__offset = offset
95 self.__name = name
96 self.assertTrue(issubclass(NotEnough, tzinfo))
97 ne = NotEnough(3, "NotByALongShot")
98 self.assertIsInstance(ne, tzinfo)
99
100 dt = datetime.now()
101 self.assertRaises(NotImplementedError, ne.tzname, dt)
102 self.assertRaises(NotImplementedError, ne.utcoffset, dt)
103 self.assertRaises(NotImplementedError, ne.dst, dt)
104
105 def test_normal(self):
106 fo = FixedOffset(3, "Three")
107 self.assertIsInstance(fo, tzinfo)
108 for dt in datetime.now(), None:
109 self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3))
110 self.assertEqual(fo.tzname(dt), "Three")
111 self.assertEqual(fo.dst(dt), timedelta(minutes=42))
112
113 def test_pickling_base(self):
114 # There's no point to pickling tzinfo objects on their own (they
115 # carry no data), but they need to be picklable anyway else
116 # concrete subclasses can't be pickled.
117 orig = tzinfo.__new__(tzinfo)
118 self.assertTrue(type(orig) is tzinfo)
119 for pickler, unpickler, proto in pickle_choices:
120 green = pickler.dumps(orig, proto)
121 derived = unpickler.loads(green)
122 self.assertTrue(type(derived) is tzinfo)
123
124 def test_pickling_subclass(self):
125 # Make sure we can pickle/unpickle an instance of a subclass.
126 offset = timedelta(minutes=-300)
127 for otype, args in [
128 (PicklableFixedOffset, (offset, 'cookie')),
129 (timezone, (offset,)),
130 (timezone, (offset, "EST"))]:
131 orig = otype(*args)
132 oname = orig.tzname(None)
133 self.assertIsInstance(orig, tzinfo)
134 self.assertIs(type(orig), otype)
135 self.assertEqual(orig.utcoffset(None), offset)
136 self.assertEqual(orig.tzname(None), oname)
137 for pickler, unpickler, proto in pickle_choices:
138 green = pickler.dumps(orig, proto)
139 derived = unpickler.loads(green)
140 self.assertIsInstance(derived, tzinfo)
141 self.assertIs(type(derived), otype)
142 self.assertEqual(derived.utcoffset(None), offset)
143 self.assertEqual(derived.tzname(None), oname)
144
145class TestTimeZone(unittest.TestCase):
146
147 def setUp(self):
148 self.ACDT = timezone(timedelta(hours=9.5), 'ACDT')
149 self.EST = timezone(-timedelta(hours=5), 'EST')
150 self.DT = datetime(2010, 1, 1)
151
152 def test_str(self):
153 for tz in [self.ACDT, self.EST, timezone.utc,
154 timezone.min, timezone.max]:
155 self.assertEqual(str(tz), tz.tzname(None))
156
157 def test_repr(self):
158 datetime = datetime_module
159 for tz in [self.ACDT, self.EST, timezone.utc,
160 timezone.min, timezone.max]:
161 # test round-trip
162 tzrep = repr(tz)
163 self.assertEqual(tz, eval(tzrep))
164
165
166 def test_class_members(self):
167 limit = timedelta(hours=23, minutes=59)
168 self.assertEqual(timezone.utc.utcoffset(None), ZERO)
169 self.assertEqual(timezone.min.utcoffset(None), -limit)
170 self.assertEqual(timezone.max.utcoffset(None), limit)
171
172
173 def test_constructor(self):
Alexander Belopolsky1bcbaab2010-10-14 17:03:51 +0000174 self.assertIs(timezone.utc, timezone(timedelta(0)))
175 self.assertIsNot(timezone.utc, timezone(timedelta(0), 'UTC'))
176 self.assertEqual(timezone.utc, timezone(timedelta(0), 'UTC'))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000177 # invalid offsets
178 for invalid in [timedelta(microseconds=1), timedelta(1, 1),
179 timedelta(seconds=1), timedelta(1), -timedelta(1)]:
180 self.assertRaises(ValueError, timezone, invalid)
181 self.assertRaises(ValueError, timezone, -invalid)
182
183 with self.assertRaises(TypeError): timezone(None)
184 with self.assertRaises(TypeError): timezone(42)
185 with self.assertRaises(TypeError): timezone(ZERO, None)
186 with self.assertRaises(TypeError): timezone(ZERO, 42)
187 with self.assertRaises(TypeError): timezone(ZERO, 'ABC', 'extra')
188
189 def test_inheritance(self):
190 self.assertIsInstance(timezone.utc, tzinfo)
191 self.assertIsInstance(self.EST, tzinfo)
192
193 def test_utcoffset(self):
194 dummy = self.DT
195 for h in [0, 1.5, 12]:
196 offset = h * HOUR
197 self.assertEqual(offset, timezone(offset).utcoffset(dummy))
198 self.assertEqual(-offset, timezone(-offset).utcoffset(dummy))
199
200 with self.assertRaises(TypeError): self.EST.utcoffset('')
201 with self.assertRaises(TypeError): self.EST.utcoffset(5)
202
203
204 def test_dst(self):
Raymond Hettinger7beae8a2011-01-06 05:34:17 +0000205 self.assertIsNone(timezone.utc.dst(self.DT))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000206
207 with self.assertRaises(TypeError): self.EST.dst('')
208 with self.assertRaises(TypeError): self.EST.dst(5)
209
210 def test_tzname(self):
211 self.assertEqual('UTC+00:00', timezone(ZERO).tzname(None))
212 self.assertEqual('UTC-05:00', timezone(-5 * HOUR).tzname(None))
213 self.assertEqual('UTC+09:30', timezone(9.5 * HOUR).tzname(None))
214 self.assertEqual('UTC-00:01', timezone(timedelta(minutes=-1)).tzname(None))
215 self.assertEqual('XYZ', timezone(-5 * HOUR, 'XYZ').tzname(None))
216
217 with self.assertRaises(TypeError): self.EST.tzname('')
218 with self.assertRaises(TypeError): self.EST.tzname(5)
219
220 def test_fromutc(self):
221 with self.assertRaises(ValueError):
222 timezone.utc.fromutc(self.DT)
223 with self.assertRaises(TypeError):
224 timezone.utc.fromutc('not datetime')
225 for tz in [self.EST, self.ACDT, Eastern]:
226 utctime = self.DT.replace(tzinfo=tz)
227 local = tz.fromutc(utctime)
228 self.assertEqual(local - utctime, tz.utcoffset(local))
229 self.assertEqual(local,
230 self.DT.replace(tzinfo=timezone.utc))
231
232 def test_comparison(self):
233 self.assertNotEqual(timezone(ZERO), timezone(HOUR))
234 self.assertEqual(timezone(HOUR), timezone(HOUR))
235 self.assertEqual(timezone(-5 * HOUR), timezone(-5 * HOUR, 'EST'))
236 with self.assertRaises(TypeError): timezone(ZERO) < timezone(ZERO)
237 self.assertIn(timezone(ZERO), {timezone(ZERO)})
Georg Brandl0085a242012-09-22 09:23:12 +0200238 self.assertTrue(timezone(ZERO) != None)
239 self.assertFalse(timezone(ZERO) == None)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000240
241 def test_aware_datetime(self):
242 # test that timezone instances can be used by datetime
243 t = datetime(1, 1, 1)
244 for tz in [timezone.min, timezone.max, timezone.utc]:
245 self.assertEqual(tz.tzname(t),
246 t.replace(tzinfo=tz).tzname())
247 self.assertEqual(tz.utcoffset(t),
248 t.replace(tzinfo=tz).utcoffset())
249 self.assertEqual(tz.dst(t),
250 t.replace(tzinfo=tz).dst())
251
252#############################################################################
Ezio Melotti85a86292013-08-17 16:57:41 +0300253# Base class for testing a particular aspect of timedelta, time, date and
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000254# datetime comparisons.
255
256class HarmlessMixedComparison:
257 # Test that __eq__ and __ne__ don't complain for mixed-type comparisons.
258
259 # Subclasses must define 'theclass', and theclass(1, 1, 1) must be a
260 # legit constructor.
261
262 def test_harmless_mixed_comparison(self):
263 me = self.theclass(1, 1, 1)
264
265 self.assertFalse(me == ())
266 self.assertTrue(me != ())
267 self.assertFalse(() == me)
268 self.assertTrue(() != me)
269
270 self.assertIn(me, [1, 20, [], me])
271 self.assertIn([], [me, 1, 20, []])
272
273 def test_harmful_mixed_comparison(self):
274 me = self.theclass(1, 1, 1)
275
276 self.assertRaises(TypeError, lambda: me < ())
277 self.assertRaises(TypeError, lambda: me <= ())
278 self.assertRaises(TypeError, lambda: me > ())
279 self.assertRaises(TypeError, lambda: me >= ())
280
281 self.assertRaises(TypeError, lambda: () < me)
282 self.assertRaises(TypeError, lambda: () <= me)
283 self.assertRaises(TypeError, lambda: () > me)
284 self.assertRaises(TypeError, lambda: () >= me)
285
286#############################################################################
287# timedelta tests
288
289class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
290
291 theclass = timedelta
292
293 def test_constructor(self):
294 eq = self.assertEqual
295 td = timedelta
296
297 # Check keyword args to constructor
298 eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0,
299 milliseconds=0, microseconds=0))
300 eq(td(1), td(days=1))
301 eq(td(0, 1), td(seconds=1))
302 eq(td(0, 0, 1), td(microseconds=1))
303 eq(td(weeks=1), td(days=7))
304 eq(td(days=1), td(hours=24))
305 eq(td(hours=1), td(minutes=60))
306 eq(td(minutes=1), td(seconds=60))
307 eq(td(seconds=1), td(milliseconds=1000))
308 eq(td(milliseconds=1), td(microseconds=1000))
309
310 # Check float args to constructor
311 eq(td(weeks=1.0/7), td(days=1))
312 eq(td(days=1.0/24), td(hours=1))
313 eq(td(hours=1.0/60), td(minutes=1))
314 eq(td(minutes=1.0/60), td(seconds=1))
315 eq(td(seconds=0.001), td(milliseconds=1))
316 eq(td(milliseconds=0.001), td(microseconds=1))
317
318 def test_computations(self):
319 eq = self.assertEqual
320 td = timedelta
321
322 a = td(7) # One week
323 b = td(0, 60) # One minute
324 c = td(0, 0, 1000) # One millisecond
325 eq(a+b+c, td(7, 60, 1000))
326 eq(a-b, td(6, 24*3600 - 60))
327 eq(b.__rsub__(a), td(6, 24*3600 - 60))
328 eq(-a, td(-7))
329 eq(+a, td(7))
330 eq(-b, td(-1, 24*3600 - 60))
331 eq(-c, td(-1, 24*3600 - 1, 999000))
332 eq(abs(a), a)
333 eq(abs(-a), a)
334 eq(td(6, 24*3600), a)
335 eq(td(0, 0, 60*1000000), b)
336 eq(a*10, td(70))
337 eq(a*10, 10*a)
338 eq(a*10, 10*a)
339 eq(b*10, td(0, 600))
340 eq(10*b, td(0, 600))
341 eq(b*10, td(0, 600))
342 eq(c*10, td(0, 0, 10000))
343 eq(10*c, td(0, 0, 10000))
344 eq(c*10, td(0, 0, 10000))
345 eq(a*-1, -a)
346 eq(b*-2, -b-b)
347 eq(c*-2, -c+-c)
348 eq(b*(60*24), (b*60)*24)
349 eq(b*(60*24), (60*b)*24)
350 eq(c*1000, td(0, 1))
351 eq(1000*c, td(0, 1))
352 eq(a//7, td(1))
353 eq(b//10, td(0, 6))
354 eq(c//1000, td(0, 0, 1))
355 eq(a//10, td(0, 7*24*360))
356 eq(a//3600000, td(0, 0, 7*24*1000))
357 eq(a/0.5, td(14))
358 eq(b/0.5, td(0, 120))
359 eq(a/7, td(1))
360 eq(b/10, td(0, 6))
361 eq(c/1000, td(0, 0, 1))
362 eq(a/10, td(0, 7*24*360))
363 eq(a/3600000, td(0, 0, 7*24*1000))
364
365 # Multiplication by float
366 us = td(microseconds=1)
367 eq((3*us) * 0.5, 2*us)
368 eq((5*us) * 0.5, 2*us)
369 eq(0.5 * (3*us), 2*us)
370 eq(0.5 * (5*us), 2*us)
371 eq((-3*us) * 0.5, -2*us)
372 eq((-5*us) * 0.5, -2*us)
373
374 # Division by int and float
375 eq((3*us) / 2, 2*us)
376 eq((5*us) / 2, 2*us)
377 eq((-3*us) / 2.0, -2*us)
378 eq((-5*us) / 2.0, -2*us)
379 eq((3*us) / -2, -2*us)
380 eq((5*us) / -2, -2*us)
381 eq((3*us) / -2.0, -2*us)
382 eq((5*us) / -2.0, -2*us)
383 for i in range(-10, 10):
384 eq((i*us/3)//us, round(i/3))
385 for i in range(-10, 10):
386 eq((i*us/-3)//us, round(i/-3))
387
Alexander Belopolskyb6f5ec72011-04-05 20:07:38 -0400388 # Issue #11576
389 eq(td(999999999, 86399, 999999) - td(999999999, 86399, 999998),
390 td(0, 0, 1))
391 eq(td(999999999, 1, 1) - td(999999999, 1, 0),
392 td(0, 0, 1))
393
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000394 def test_disallowed_computations(self):
395 a = timedelta(42)
396
397 # Add/sub ints or floats should be illegal
398 for i in 1, 1.0:
399 self.assertRaises(TypeError, lambda: a+i)
400 self.assertRaises(TypeError, lambda: a-i)
401 self.assertRaises(TypeError, lambda: i+a)
402 self.assertRaises(TypeError, lambda: i-a)
403
404 # Division of int by timedelta doesn't make sense.
405 # Division by zero doesn't make sense.
406 zero = 0
407 self.assertRaises(TypeError, lambda: zero // a)
408 self.assertRaises(ZeroDivisionError, lambda: a // zero)
409 self.assertRaises(ZeroDivisionError, lambda: a / zero)
410 self.assertRaises(ZeroDivisionError, lambda: a / 0.0)
411 self.assertRaises(TypeError, lambda: a / '')
412
Eric Smith3ab08ca2010-12-04 15:17:38 +0000413 @support.requires_IEEE_754
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000414 def test_disallowed_special(self):
415 a = timedelta(42)
416 self.assertRaises(ValueError, a.__mul__, NAN)
417 self.assertRaises(ValueError, a.__truediv__, NAN)
418
419 def test_basic_attributes(self):
420 days, seconds, us = 1, 7, 31
421 td = timedelta(days, seconds, us)
422 self.assertEqual(td.days, days)
423 self.assertEqual(td.seconds, seconds)
424 self.assertEqual(td.microseconds, us)
425
426 def test_total_seconds(self):
427 td = timedelta(days=365)
428 self.assertEqual(td.total_seconds(), 31536000.0)
429 for total_seconds in [123456.789012, -123456.789012, 0.123456, 0, 1e6]:
430 td = timedelta(seconds=total_seconds)
431 self.assertEqual(td.total_seconds(), total_seconds)
432 # Issue8644: Test that td.total_seconds() has the same
433 # accuracy as td / timedelta(seconds=1).
434 for ms in [-1, -2, -123]:
435 td = timedelta(microseconds=ms)
436 self.assertEqual(td.total_seconds(), td / timedelta(seconds=1))
437
438 def test_carries(self):
439 t1 = timedelta(days=100,
440 weeks=-7,
441 hours=-24*(100-49),
442 minutes=-3,
443 seconds=12,
444 microseconds=(3*60 - 12) * 1e6 + 1)
445 t2 = timedelta(microseconds=1)
446 self.assertEqual(t1, t2)
447
448 def test_hash_equality(self):
449 t1 = timedelta(days=100,
450 weeks=-7,
451 hours=-24*(100-49),
452 minutes=-3,
453 seconds=12,
454 microseconds=(3*60 - 12) * 1000000)
455 t2 = timedelta()
456 self.assertEqual(hash(t1), hash(t2))
457
458 t1 += timedelta(weeks=7)
459 t2 += timedelta(days=7*7)
460 self.assertEqual(t1, t2)
461 self.assertEqual(hash(t1), hash(t2))
462
463 d = {t1: 1}
464 d[t2] = 2
465 self.assertEqual(len(d), 1)
466 self.assertEqual(d[t1], 2)
467
468 def test_pickling(self):
469 args = 12, 34, 56
470 orig = timedelta(*args)
471 for pickler, unpickler, proto in pickle_choices:
472 green = pickler.dumps(orig, proto)
473 derived = unpickler.loads(green)
474 self.assertEqual(orig, derived)
475
476 def test_compare(self):
477 t1 = timedelta(2, 3, 4)
478 t2 = timedelta(2, 3, 4)
479 self.assertEqual(t1, t2)
480 self.assertTrue(t1 <= t2)
481 self.assertTrue(t1 >= t2)
482 self.assertTrue(not t1 != t2)
483 self.assertTrue(not t1 < t2)
484 self.assertTrue(not t1 > t2)
485
486 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
487 t2 = timedelta(*args) # this is larger than t1
488 self.assertTrue(t1 < t2)
489 self.assertTrue(t2 > t1)
490 self.assertTrue(t1 <= t2)
491 self.assertTrue(t2 >= t1)
492 self.assertTrue(t1 != t2)
493 self.assertTrue(t2 != t1)
494 self.assertTrue(not t1 == t2)
495 self.assertTrue(not t2 == t1)
496 self.assertTrue(not t1 > t2)
497 self.assertTrue(not t2 < t1)
498 self.assertTrue(not t1 >= t2)
499 self.assertTrue(not t2 <= t1)
500
501 for badarg in OTHERSTUFF:
502 self.assertEqual(t1 == badarg, False)
503 self.assertEqual(t1 != badarg, True)
504 self.assertEqual(badarg == t1, False)
505 self.assertEqual(badarg != t1, True)
506
507 self.assertRaises(TypeError, lambda: t1 <= badarg)
508 self.assertRaises(TypeError, lambda: t1 < badarg)
509 self.assertRaises(TypeError, lambda: t1 > badarg)
510 self.assertRaises(TypeError, lambda: t1 >= badarg)
511 self.assertRaises(TypeError, lambda: badarg <= t1)
512 self.assertRaises(TypeError, lambda: badarg < t1)
513 self.assertRaises(TypeError, lambda: badarg > t1)
514 self.assertRaises(TypeError, lambda: badarg >= t1)
515
516 def test_str(self):
517 td = timedelta
518 eq = self.assertEqual
519
520 eq(str(td(1)), "1 day, 0:00:00")
521 eq(str(td(-1)), "-1 day, 0:00:00")
522 eq(str(td(2)), "2 days, 0:00:00")
523 eq(str(td(-2)), "-2 days, 0:00:00")
524
525 eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
526 eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
527 eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
528 "-210 days, 23:12:34")
529
530 eq(str(td(milliseconds=1)), "0:00:00.001000")
531 eq(str(td(microseconds=3)), "0:00:00.000003")
532
533 eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
534 microseconds=999999)),
535 "999999999 days, 23:59:59.999999")
536
537 def test_repr(self):
538 name = 'datetime.' + self.theclass.__name__
539 self.assertEqual(repr(self.theclass(1)),
540 "%s(1)" % name)
541 self.assertEqual(repr(self.theclass(10, 2)),
542 "%s(10, 2)" % name)
543 self.assertEqual(repr(self.theclass(-10, 2, 400000)),
544 "%s(-10, 2, 400000)" % name)
545
546 def test_roundtrip(self):
547 for td in (timedelta(days=999999999, hours=23, minutes=59,
548 seconds=59, microseconds=999999),
549 timedelta(days=-999999999),
550 timedelta(days=-999999999, seconds=1),
551 timedelta(days=1, seconds=2, microseconds=3)):
552
553 # Verify td -> string -> td identity.
554 s = repr(td)
555 self.assertTrue(s.startswith('datetime.'))
556 s = s[9:]
557 td2 = eval(s)
558 self.assertEqual(td, td2)
559
560 # Verify identity via reconstructing from pieces.
561 td2 = timedelta(td.days, td.seconds, td.microseconds)
562 self.assertEqual(td, td2)
563
564 def test_resolution_info(self):
565 self.assertIsInstance(timedelta.min, timedelta)
566 self.assertIsInstance(timedelta.max, timedelta)
567 self.assertIsInstance(timedelta.resolution, timedelta)
568 self.assertTrue(timedelta.max > timedelta.min)
569 self.assertEqual(timedelta.min, timedelta(-999999999))
570 self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
571 self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
572
573 def test_overflow(self):
574 tiny = timedelta.resolution
575
576 td = timedelta.min + tiny
577 td -= tiny # no problem
578 self.assertRaises(OverflowError, td.__sub__, tiny)
579 self.assertRaises(OverflowError, td.__add__, -tiny)
580
581 td = timedelta.max - tiny
582 td += tiny # no problem
583 self.assertRaises(OverflowError, td.__add__, tiny)
584 self.assertRaises(OverflowError, td.__sub__, -tiny)
585
586 self.assertRaises(OverflowError, lambda: -timedelta.max)
587
588 day = timedelta(1)
589 self.assertRaises(OverflowError, day.__mul__, 10**9)
590 self.assertRaises(OverflowError, day.__mul__, 1e9)
591 self.assertRaises(OverflowError, day.__truediv__, 1e-20)
592 self.assertRaises(OverflowError, day.__truediv__, 1e-10)
593 self.assertRaises(OverflowError, day.__truediv__, 9e-10)
594
Eric Smith3ab08ca2010-12-04 15:17:38 +0000595 @support.requires_IEEE_754
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000596 def _test_overflow_special(self):
597 day = timedelta(1)
598 self.assertRaises(OverflowError, day.__mul__, INF)
599 self.assertRaises(OverflowError, day.__mul__, -INF)
600
601 def test_microsecond_rounding(self):
602 td = timedelta
603 eq = self.assertEqual
604
605 # Single-field rounding.
606 eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
607 eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
608 eq(td(milliseconds=0.6/1000), td(microseconds=1))
609 eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
610
611 # Rounding due to contributions from more than one field.
612 us_per_hour = 3600e6
613 us_per_day = us_per_hour * 24
614 eq(td(days=.4/us_per_day), td(0))
615 eq(td(hours=.2/us_per_hour), td(0))
616 eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
617
618 eq(td(days=-.4/us_per_day), td(0))
619 eq(td(hours=-.2/us_per_hour), td(0))
620 eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
621
Alexander Belopolsky790d2692013-08-04 14:51:35 -0400622 # Test for a patch in Issue 8860
623 eq(td(microseconds=0.5), 0.5*td(microseconds=1.0))
624 eq(td(microseconds=0.5)//td.resolution, 0.5*td.resolution//td.resolution)
625
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000626 def test_massive_normalization(self):
627 td = timedelta(microseconds=-1)
628 self.assertEqual((td.days, td.seconds, td.microseconds),
629 (-1, 24*3600-1, 999999))
630
631 def test_bool(self):
632 self.assertTrue(timedelta(1))
633 self.assertTrue(timedelta(0, 1))
634 self.assertTrue(timedelta(0, 0, 1))
635 self.assertTrue(timedelta(microseconds=1))
636 self.assertTrue(not timedelta(0))
637
638 def test_subclass_timedelta(self):
639
640 class T(timedelta):
641 @staticmethod
642 def from_td(td):
643 return T(td.days, td.seconds, td.microseconds)
644
645 def as_hours(self):
646 sum = (self.days * 24 +
647 self.seconds / 3600.0 +
648 self.microseconds / 3600e6)
649 return round(sum)
650
651 t1 = T(days=1)
652 self.assertTrue(type(t1) is T)
653 self.assertEqual(t1.as_hours(), 24)
654
655 t2 = T(days=-1, seconds=-3600)
656 self.assertTrue(type(t2) is T)
657 self.assertEqual(t2.as_hours(), -25)
658
659 t3 = t1 + t2
660 self.assertTrue(type(t3) is timedelta)
661 t4 = T.from_td(t3)
662 self.assertTrue(type(t4) is T)
663 self.assertEqual(t3.days, t4.days)
664 self.assertEqual(t3.seconds, t4.seconds)
665 self.assertEqual(t3.microseconds, t4.microseconds)
666 self.assertEqual(str(t3), str(t4))
667 self.assertEqual(t4.as_hours(), -1)
668
669 def test_division(self):
670 t = timedelta(hours=1, minutes=24, seconds=19)
671 second = timedelta(seconds=1)
672 self.assertEqual(t / second, 5059.0)
673 self.assertEqual(t // second, 5059)
674
675 t = timedelta(minutes=2, seconds=30)
676 minute = timedelta(minutes=1)
677 self.assertEqual(t / minute, 2.5)
678 self.assertEqual(t // minute, 2)
679
680 zerotd = timedelta(0)
681 self.assertRaises(ZeroDivisionError, truediv, t, zerotd)
682 self.assertRaises(ZeroDivisionError, floordiv, t, zerotd)
683
684 # self.assertRaises(TypeError, truediv, t, 2)
685 # note: floor division of a timedelta by an integer *is*
686 # currently permitted.
687
688 def test_remainder(self):
689 t = timedelta(minutes=2, seconds=30)
690 minute = timedelta(minutes=1)
691 r = t % minute
692 self.assertEqual(r, timedelta(seconds=30))
693
694 t = timedelta(minutes=-2, seconds=30)
695 r = t % minute
696 self.assertEqual(r, timedelta(seconds=30))
697
698 zerotd = timedelta(0)
699 self.assertRaises(ZeroDivisionError, mod, t, zerotd)
700
701 self.assertRaises(TypeError, mod, t, 10)
702
703 def test_divmod(self):
704 t = timedelta(minutes=2, seconds=30)
705 minute = timedelta(minutes=1)
706 q, r = divmod(t, minute)
707 self.assertEqual(q, 2)
708 self.assertEqual(r, timedelta(seconds=30))
709
710 t = timedelta(minutes=-2, seconds=30)
711 q, r = divmod(t, minute)
712 self.assertEqual(q, -2)
713 self.assertEqual(r, timedelta(seconds=30))
714
715 zerotd = timedelta(0)
716 self.assertRaises(ZeroDivisionError, divmod, t, zerotd)
717
718 self.assertRaises(TypeError, divmod, t, 10)
719
720
721#############################################################################
722# date tests
723
724class TestDateOnly(unittest.TestCase):
725 # Tests here won't pass if also run on datetime objects, so don't
726 # subclass this to test datetimes too.
727
728 def test_delta_non_days_ignored(self):
729 dt = date(2000, 1, 2)
730 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
731 microseconds=5)
732 days = timedelta(delta.days)
733 self.assertEqual(days, timedelta(1))
734
735 dt2 = dt + delta
736 self.assertEqual(dt2, dt + days)
737
738 dt2 = delta + dt
739 self.assertEqual(dt2, dt + days)
740
741 dt2 = dt - delta
742 self.assertEqual(dt2, dt - days)
743
744 delta = -delta
745 days = timedelta(delta.days)
746 self.assertEqual(days, timedelta(-2))
747
748 dt2 = dt + delta
749 self.assertEqual(dt2, dt + days)
750
751 dt2 = delta + dt
752 self.assertEqual(dt2, dt + days)
753
754 dt2 = dt - delta
755 self.assertEqual(dt2, dt - days)
756
757class SubclassDate(date):
758 sub_var = 1
759
760class TestDate(HarmlessMixedComparison, unittest.TestCase):
761 # Tests here should pass for both dates and datetimes, except for a
762 # few tests that TestDateTime overrides.
763
764 theclass = date
765
766 def test_basic_attributes(self):
767 dt = self.theclass(2002, 3, 1)
768 self.assertEqual(dt.year, 2002)
769 self.assertEqual(dt.month, 3)
770 self.assertEqual(dt.day, 1)
771
772 def test_roundtrip(self):
773 for dt in (self.theclass(1, 2, 3),
774 self.theclass.today()):
775 # Verify dt -> string -> date identity.
776 s = repr(dt)
777 self.assertTrue(s.startswith('datetime.'))
778 s = s[9:]
779 dt2 = eval(s)
780 self.assertEqual(dt, dt2)
781
782 # Verify identity via reconstructing from pieces.
783 dt2 = self.theclass(dt.year, dt.month, dt.day)
784 self.assertEqual(dt, dt2)
785
786 def test_ordinal_conversions(self):
787 # Check some fixed values.
788 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
789 (1, 12, 31, 365),
790 (2, 1, 1, 366),
791 # first example from "Calendrical Calculations"
792 (1945, 11, 12, 710347)]:
793 d = self.theclass(y, m, d)
794 self.assertEqual(n, d.toordinal())
795 fromord = self.theclass.fromordinal(n)
796 self.assertEqual(d, fromord)
797 if hasattr(fromord, "hour"):
798 # if we're checking something fancier than a date, verify
799 # the extra fields have been zeroed out
800 self.assertEqual(fromord.hour, 0)
801 self.assertEqual(fromord.minute, 0)
802 self.assertEqual(fromord.second, 0)
803 self.assertEqual(fromord.microsecond, 0)
804
805 # Check first and last days of year spottily across the whole
806 # range of years supported.
807 for year in range(MINYEAR, MAXYEAR+1, 7):
808 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
809 d = self.theclass(year, 1, 1)
810 n = d.toordinal()
811 d2 = self.theclass.fromordinal(n)
812 self.assertEqual(d, d2)
813 # Verify that moving back a day gets to the end of year-1.
814 if year > 1:
815 d = self.theclass.fromordinal(n-1)
816 d2 = self.theclass(year-1, 12, 31)
817 self.assertEqual(d, d2)
818 self.assertEqual(d2.toordinal(), n-1)
819
820 # Test every day in a leap-year and a non-leap year.
821 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
822 for year, isleap in (2000, True), (2002, False):
823 n = self.theclass(year, 1, 1).toordinal()
824 for month, maxday in zip(range(1, 13), dim):
825 if month == 2 and isleap:
826 maxday += 1
827 for day in range(1, maxday+1):
828 d = self.theclass(year, month, day)
829 self.assertEqual(d.toordinal(), n)
830 self.assertEqual(d, self.theclass.fromordinal(n))
831 n += 1
832
833 def test_extreme_ordinals(self):
834 a = self.theclass.min
835 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
836 aord = a.toordinal()
837 b = a.fromordinal(aord)
838 self.assertEqual(a, b)
839
840 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
841
842 b = a + timedelta(days=1)
843 self.assertEqual(b.toordinal(), aord + 1)
844 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
845
846 a = self.theclass.max
847 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
848 aord = a.toordinal()
849 b = a.fromordinal(aord)
850 self.assertEqual(a, b)
851
852 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
853
854 b = a - timedelta(days=1)
855 self.assertEqual(b.toordinal(), aord - 1)
856 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
857
858 def test_bad_constructor_arguments(self):
859 # bad years
860 self.theclass(MINYEAR, 1, 1) # no exception
861 self.theclass(MAXYEAR, 1, 1) # no exception
862 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
863 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
864 # bad months
865 self.theclass(2000, 1, 1) # no exception
866 self.theclass(2000, 12, 1) # no exception
867 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
868 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
869 # bad days
870 self.theclass(2000, 2, 29) # no exception
871 self.theclass(2004, 2, 29) # no exception
872 self.theclass(2400, 2, 29) # no exception
873 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
874 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
875 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
876 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
877 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
878 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
879
880 def test_hash_equality(self):
881 d = self.theclass(2000, 12, 31)
882 # same thing
883 e = self.theclass(2000, 12, 31)
884 self.assertEqual(d, e)
885 self.assertEqual(hash(d), hash(e))
886
887 dic = {d: 1}
888 dic[e] = 2
889 self.assertEqual(len(dic), 1)
890 self.assertEqual(dic[d], 2)
891 self.assertEqual(dic[e], 2)
892
893 d = self.theclass(2001, 1, 1)
894 # same thing
895 e = self.theclass(2001, 1, 1)
896 self.assertEqual(d, e)
897 self.assertEqual(hash(d), hash(e))
898
899 dic = {d: 1}
900 dic[e] = 2
901 self.assertEqual(len(dic), 1)
902 self.assertEqual(dic[d], 2)
903 self.assertEqual(dic[e], 2)
904
905 def test_computations(self):
906 a = self.theclass(2002, 1, 31)
907 b = self.theclass(1956, 1, 31)
908 c = self.theclass(2001,2,1)
909
910 diff = a-b
911 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
912 self.assertEqual(diff.seconds, 0)
913 self.assertEqual(diff.microseconds, 0)
914
915 day = timedelta(1)
916 week = timedelta(7)
917 a = self.theclass(2002, 3, 2)
918 self.assertEqual(a + day, self.theclass(2002, 3, 3))
919 self.assertEqual(day + a, self.theclass(2002, 3, 3))
920 self.assertEqual(a - day, self.theclass(2002, 3, 1))
921 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
922 self.assertEqual(a + week, self.theclass(2002, 3, 9))
923 self.assertEqual(a - week, self.theclass(2002, 2, 23))
924 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
925 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
926 self.assertEqual((a + week) - a, week)
927 self.assertEqual((a + day) - a, day)
928 self.assertEqual((a - week) - a, -week)
929 self.assertEqual((a - day) - a, -day)
930 self.assertEqual(a - (a + week), -week)
931 self.assertEqual(a - (a + day), -day)
932 self.assertEqual(a - (a - week), week)
933 self.assertEqual(a - (a - day), day)
934 self.assertEqual(c - (c - day), day)
935
936 # Add/sub ints or floats should be illegal
937 for i in 1, 1.0:
938 self.assertRaises(TypeError, lambda: a+i)
939 self.assertRaises(TypeError, lambda: a-i)
940 self.assertRaises(TypeError, lambda: i+a)
941 self.assertRaises(TypeError, lambda: i-a)
942
943 # delta - date is senseless.
944 self.assertRaises(TypeError, lambda: day - a)
945 # mixing date and (delta or date) via * or // is senseless
946 self.assertRaises(TypeError, lambda: day * a)
947 self.assertRaises(TypeError, lambda: a * day)
948 self.assertRaises(TypeError, lambda: day // a)
949 self.assertRaises(TypeError, lambda: a // day)
950 self.assertRaises(TypeError, lambda: a * a)
951 self.assertRaises(TypeError, lambda: a // a)
952 # date + date is senseless
953 self.assertRaises(TypeError, lambda: a + a)
954
955 def test_overflow(self):
956 tiny = self.theclass.resolution
957
958 for delta in [tiny, timedelta(1), timedelta(2)]:
959 dt = self.theclass.min + delta
960 dt -= delta # no problem
961 self.assertRaises(OverflowError, dt.__sub__, delta)
962 self.assertRaises(OverflowError, dt.__add__, -delta)
963
964 dt = self.theclass.max - delta
965 dt += delta # no problem
966 self.assertRaises(OverflowError, dt.__add__, delta)
967 self.assertRaises(OverflowError, dt.__sub__, -delta)
968
969 def test_fromtimestamp(self):
970 import time
971
972 # Try an arbitrary fixed value.
973 year, month, day = 1999, 9, 19
974 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
975 d = self.theclass.fromtimestamp(ts)
976 self.assertEqual(d.year, year)
977 self.assertEqual(d.month, month)
978 self.assertEqual(d.day, day)
979
980 def test_insane_fromtimestamp(self):
981 # It's possible that some platform maps time_t to double,
982 # and that this test will fail there. This test should
983 # exempt such platforms (provided they return reasonable
984 # results!).
985 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +0100986 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000987 insane)
988
989 def test_today(self):
990 import time
991
992 # We claim that today() is like fromtimestamp(time.time()), so
993 # prove it.
994 for dummy in range(3):
995 today = self.theclass.today()
996 ts = time.time()
997 todayagain = self.theclass.fromtimestamp(ts)
998 if today == todayagain:
999 break
1000 # There are several legit reasons that could fail:
1001 # 1. It recently became midnight, between the today() and the
1002 # time() calls.
1003 # 2. The platform time() has such fine resolution that we'll
1004 # never get the same value twice.
1005 # 3. The platform time() has poor resolution, and we just
1006 # happened to call today() right before a resolution quantum
1007 # boundary.
1008 # 4. The system clock got fiddled between calls.
1009 # In any case, wait a little while and try again.
1010 time.sleep(0.1)
1011
1012 # It worked or it didn't. If it didn't, assume it's reason #2, and
1013 # let the test pass if they're within half a second of each other.
1014 self.assertTrue(today == todayagain or
1015 abs(todayagain - today) < timedelta(seconds=0.5))
1016
1017 def test_weekday(self):
1018 for i in range(7):
1019 # March 4, 2002 is a Monday
1020 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
1021 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
1022 # January 2, 1956 is a Monday
1023 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
1024 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
1025
1026 def test_isocalendar(self):
1027 # Check examples from
1028 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1029 for i in range(7):
1030 d = self.theclass(2003, 12, 22+i)
1031 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
1032 d = self.theclass(2003, 12, 29) + timedelta(i)
1033 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
1034 d = self.theclass(2004, 1, 5+i)
1035 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
1036 d = self.theclass(2009, 12, 21+i)
1037 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
1038 d = self.theclass(2009, 12, 28) + timedelta(i)
1039 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
1040 d = self.theclass(2010, 1, 4+i)
1041 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
1042
1043 def test_iso_long_years(self):
1044 # Calculate long ISO years and compare to table from
1045 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1046 ISO_LONG_YEARS_TABLE = """
1047 4 32 60 88
1048 9 37 65 93
1049 15 43 71 99
1050 20 48 76
1051 26 54 82
1052
1053 105 133 161 189
1054 111 139 167 195
1055 116 144 172
1056 122 150 178
1057 128 156 184
1058
1059 201 229 257 285
1060 207 235 263 291
1061 212 240 268 296
1062 218 246 274
1063 224 252 280
1064
1065 303 331 359 387
1066 308 336 364 392
1067 314 342 370 398
1068 320 348 376
1069 325 353 381
1070 """
1071 iso_long_years = sorted(map(int, ISO_LONG_YEARS_TABLE.split()))
1072 L = []
1073 for i in range(400):
1074 d = self.theclass(2000+i, 12, 31)
1075 d1 = self.theclass(1600+i, 12, 31)
1076 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
1077 if d.isocalendar()[1] == 53:
1078 L.append(i)
1079 self.assertEqual(L, iso_long_years)
1080
1081 def test_isoformat(self):
1082 t = self.theclass(2, 3, 2)
1083 self.assertEqual(t.isoformat(), "0002-03-02")
1084
1085 def test_ctime(self):
1086 t = self.theclass(2002, 3, 2)
1087 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
1088
1089 def test_strftime(self):
1090 t = self.theclass(2005, 3, 2)
1091 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
1092 self.assertEqual(t.strftime(""), "") # SF bug #761337
1093 self.assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784
1094
1095 self.assertRaises(TypeError, t.strftime) # needs an arg
1096 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
1097 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
1098
1099 # test that unicode input is allowed (issue 2782)
1100 self.assertEqual(t.strftime("%m"), "03")
1101
1102 # A naive object replaces %z and %Z w/ empty strings.
1103 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1104
1105 #make sure that invalid format specifiers are handled correctly
1106 #self.assertRaises(ValueError, t.strftime, "%e")
1107 #self.assertRaises(ValueError, t.strftime, "%")
1108 #self.assertRaises(ValueError, t.strftime, "%#")
1109
1110 #oh well, some systems just ignore those invalid ones.
1111 #at least, excercise them to make sure that no crashes
1112 #are generated
1113 for f in ["%e", "%", "%#"]:
1114 try:
1115 t.strftime(f)
1116 except ValueError:
1117 pass
1118
1119 #check that this standard extension works
1120 t.strftime("%f")
1121
1122
1123 def test_format(self):
1124 dt = self.theclass(2007, 9, 10)
1125 self.assertEqual(dt.__format__(''), str(dt))
1126
1127 # check that a derived class's __str__() gets called
1128 class A(self.theclass):
1129 def __str__(self):
1130 return 'A'
1131 a = A(2007, 9, 10)
1132 self.assertEqual(a.__format__(''), 'A')
1133
1134 # check that a derived class's strftime gets called
1135 class B(self.theclass):
1136 def strftime(self, format_spec):
1137 return 'B'
1138 b = B(2007, 9, 10)
1139 self.assertEqual(b.__format__(''), str(dt))
1140
1141 for fmt in ["m:%m d:%d y:%y",
1142 "m:%m d:%d y:%y H:%H M:%M S:%S",
1143 "%z %Z",
1144 ]:
1145 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1146 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1147 self.assertEqual(b.__format__(fmt), 'B')
1148
1149 def test_resolution_info(self):
1150 # XXX: Should min and max respect subclassing?
1151 if issubclass(self.theclass, datetime):
1152 expected_class = datetime
1153 else:
1154 expected_class = date
1155 self.assertIsInstance(self.theclass.min, expected_class)
1156 self.assertIsInstance(self.theclass.max, expected_class)
1157 self.assertIsInstance(self.theclass.resolution, timedelta)
1158 self.assertTrue(self.theclass.max > self.theclass.min)
1159
1160 def test_extreme_timedelta(self):
1161 big = self.theclass.max - self.theclass.min
1162 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
1163 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
1164 # n == 315537897599999999 ~= 2**58.13
1165 justasbig = timedelta(0, 0, n)
1166 self.assertEqual(big, justasbig)
1167 self.assertEqual(self.theclass.min + big, self.theclass.max)
1168 self.assertEqual(self.theclass.max - big, self.theclass.min)
1169
1170 def test_timetuple(self):
1171 for i in range(7):
1172 # January 2, 1956 is a Monday (0)
1173 d = self.theclass(1956, 1, 2+i)
1174 t = d.timetuple()
1175 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
1176 # February 1, 1956 is a Wednesday (2)
1177 d = self.theclass(1956, 2, 1+i)
1178 t = d.timetuple()
1179 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
1180 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
1181 # of the year.
1182 d = self.theclass(1956, 3, 1+i)
1183 t = d.timetuple()
1184 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
1185 self.assertEqual(t.tm_year, 1956)
1186 self.assertEqual(t.tm_mon, 3)
1187 self.assertEqual(t.tm_mday, 1+i)
1188 self.assertEqual(t.tm_hour, 0)
1189 self.assertEqual(t.tm_min, 0)
1190 self.assertEqual(t.tm_sec, 0)
1191 self.assertEqual(t.tm_wday, (3+i)%7)
1192 self.assertEqual(t.tm_yday, 61+i)
1193 self.assertEqual(t.tm_isdst, -1)
1194
1195 def test_pickling(self):
1196 args = 6, 7, 23
1197 orig = self.theclass(*args)
1198 for pickler, unpickler, proto in pickle_choices:
1199 green = pickler.dumps(orig, proto)
1200 derived = unpickler.loads(green)
1201 self.assertEqual(orig, derived)
1202
1203 def test_compare(self):
1204 t1 = self.theclass(2, 3, 4)
1205 t2 = self.theclass(2, 3, 4)
1206 self.assertEqual(t1, t2)
1207 self.assertTrue(t1 <= t2)
1208 self.assertTrue(t1 >= t2)
1209 self.assertTrue(not t1 != t2)
1210 self.assertTrue(not t1 < t2)
1211 self.assertTrue(not t1 > t2)
1212
1213 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
1214 t2 = self.theclass(*args) # this is larger than t1
1215 self.assertTrue(t1 < t2)
1216 self.assertTrue(t2 > t1)
1217 self.assertTrue(t1 <= t2)
1218 self.assertTrue(t2 >= t1)
1219 self.assertTrue(t1 != t2)
1220 self.assertTrue(t2 != t1)
1221 self.assertTrue(not t1 == t2)
1222 self.assertTrue(not t2 == t1)
1223 self.assertTrue(not t1 > t2)
1224 self.assertTrue(not t2 < t1)
1225 self.assertTrue(not t1 >= t2)
1226 self.assertTrue(not t2 <= t1)
1227
1228 for badarg in OTHERSTUFF:
1229 self.assertEqual(t1 == badarg, False)
1230 self.assertEqual(t1 != badarg, True)
1231 self.assertEqual(badarg == t1, False)
1232 self.assertEqual(badarg != t1, True)
1233
1234 self.assertRaises(TypeError, lambda: t1 < badarg)
1235 self.assertRaises(TypeError, lambda: t1 > badarg)
1236 self.assertRaises(TypeError, lambda: t1 >= badarg)
1237 self.assertRaises(TypeError, lambda: badarg <= t1)
1238 self.assertRaises(TypeError, lambda: badarg < t1)
1239 self.assertRaises(TypeError, lambda: badarg > t1)
1240 self.assertRaises(TypeError, lambda: badarg >= t1)
1241
1242 def test_mixed_compare(self):
1243 our = self.theclass(2000, 4, 5)
1244
1245 # Our class can be compared for equality to other classes
1246 self.assertEqual(our == 1, False)
1247 self.assertEqual(1 == our, False)
1248 self.assertEqual(our != 1, True)
1249 self.assertEqual(1 != our, True)
1250
1251 # But the ordering is undefined
1252 self.assertRaises(TypeError, lambda: our < 1)
1253 self.assertRaises(TypeError, lambda: 1 < our)
1254
1255 # Repeat those tests with a different class
1256
1257 class SomeClass:
1258 pass
1259
1260 their = SomeClass()
1261 self.assertEqual(our == their, False)
1262 self.assertEqual(their == our, False)
1263 self.assertEqual(our != their, True)
1264 self.assertEqual(their != our, True)
1265 self.assertRaises(TypeError, lambda: our < their)
1266 self.assertRaises(TypeError, lambda: their < our)
1267
1268 # However, if the other class explicitly defines ordering
1269 # relative to our class, it is allowed to do so
1270
1271 class LargerThanAnything:
1272 def __lt__(self, other):
1273 return False
1274 def __le__(self, other):
1275 return isinstance(other, LargerThanAnything)
1276 def __eq__(self, other):
1277 return isinstance(other, LargerThanAnything)
1278 def __ne__(self, other):
1279 return not isinstance(other, LargerThanAnything)
1280 def __gt__(self, other):
1281 return not isinstance(other, LargerThanAnything)
1282 def __ge__(self, other):
1283 return True
1284
1285 their = LargerThanAnything()
1286 self.assertEqual(our == their, False)
1287 self.assertEqual(their == our, False)
1288 self.assertEqual(our != their, True)
1289 self.assertEqual(their != our, True)
1290 self.assertEqual(our < their, True)
1291 self.assertEqual(their < our, False)
1292
1293 def test_bool(self):
1294 # All dates are considered true.
1295 self.assertTrue(self.theclass.min)
1296 self.assertTrue(self.theclass.max)
1297
Alexander Belopolsky89da3492011-05-02 13:14:24 -04001298 def test_strftime_y2k(self):
1299 for y in (1, 49, 70, 99, 100, 999, 1000, 1970):
Florent Xicluna49ce0682011-11-01 12:56:14 +01001300 d = self.theclass(y, 1, 1)
1301 # Issue 13305: For years < 1000, the value is not always
1302 # padded to 4 digits across platforms. The C standard
1303 # assumes year >= 1900, so it does not specify the number
1304 # of digits.
1305 if d.strftime("%Y") != '%04d' % y:
1306 # Year 42 returns '42', not padded
1307 self.assertEqual(d.strftime("%Y"), '%d' % y)
1308 # '0042' is obtained anyway
1309 self.assertEqual(d.strftime("%4Y"), '%04d' % y)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001310
1311 def test_replace(self):
1312 cls = self.theclass
1313 args = [1, 2, 3]
1314 base = cls(*args)
1315 self.assertEqual(base, base.replace())
1316
1317 i = 0
1318 for name, newval in (("year", 2),
1319 ("month", 3),
1320 ("day", 4)):
1321 newargs = args[:]
1322 newargs[i] = newval
1323 expected = cls(*newargs)
1324 got = base.replace(**{name: newval})
1325 self.assertEqual(expected, got)
1326 i += 1
1327
1328 # Out of bounds.
1329 base = cls(2000, 2, 29)
1330 self.assertRaises(ValueError, base.replace, year=2001)
1331
1332 def test_subclass_date(self):
1333
1334 class C(self.theclass):
1335 theAnswer = 42
1336
1337 def __new__(cls, *args, **kws):
1338 temp = kws.copy()
1339 extra = temp.pop('extra')
1340 result = self.theclass.__new__(cls, *args, **temp)
1341 result.extra = extra
1342 return result
1343
1344 def newmeth(self, start):
1345 return start + self.year + self.month
1346
1347 args = 2003, 4, 14
1348
1349 dt1 = self.theclass(*args)
1350 dt2 = C(*args, **{'extra': 7})
1351
1352 self.assertEqual(dt2.__class__, C)
1353 self.assertEqual(dt2.theAnswer, 42)
1354 self.assertEqual(dt2.extra, 7)
1355 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1356 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1357
1358 def test_pickling_subclass_date(self):
1359
1360 args = 6, 7, 23
1361 orig = SubclassDate(*args)
1362 for pickler, unpickler, proto in pickle_choices:
1363 green = pickler.dumps(orig, proto)
1364 derived = unpickler.loads(green)
1365 self.assertEqual(orig, derived)
1366
1367 def test_backdoor_resistance(self):
1368 # For fast unpickling, the constructor accepts a pickle byte string.
1369 # This is a low-overhead backdoor. A user can (by intent or
1370 # mistake) pass a string directly, which (if it's the right length)
1371 # will get treated like a pickle, and bypass the normal sanity
1372 # checks in the constructor. This can create insane objects.
1373 # The constructor doesn't want to burn the time to validate all
1374 # fields, but does check the month field. This stops, e.g.,
1375 # datetime.datetime('1995-03-25') from yielding an insane object.
1376 base = b'1995-03-25'
1377 if not issubclass(self.theclass, datetime):
1378 base = base[:4]
1379 for month_byte in b'9', b'\0', b'\r', b'\xff':
1380 self.assertRaises(TypeError, self.theclass,
1381 base[:2] + month_byte + base[3:])
1382 # Good bytes, but bad tzinfo:
1383 self.assertRaises(TypeError, self.theclass,
1384 bytes([1] * len(base)), 'EST')
1385
1386 for ord_byte in range(1, 13):
1387 # This shouldn't blow up because of the month byte alone. If
1388 # the implementation changes to do more-careful checking, it may
1389 # blow up because other fields are insane.
1390 self.theclass(base[:2] + bytes([ord_byte]) + base[3:])
1391
1392#############################################################################
1393# datetime tests
1394
1395class SubclassDatetime(datetime):
1396 sub_var = 1
1397
1398class TestDateTime(TestDate):
1399
1400 theclass = datetime
1401
1402 def test_basic_attributes(self):
1403 dt = self.theclass(2002, 3, 1, 12, 0)
1404 self.assertEqual(dt.year, 2002)
1405 self.assertEqual(dt.month, 3)
1406 self.assertEqual(dt.day, 1)
1407 self.assertEqual(dt.hour, 12)
1408 self.assertEqual(dt.minute, 0)
1409 self.assertEqual(dt.second, 0)
1410 self.assertEqual(dt.microsecond, 0)
1411
1412 def test_basic_attributes_nonzero(self):
1413 # Make sure all attributes are non-zero so bugs in
1414 # bit-shifting access show up.
1415 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1416 self.assertEqual(dt.year, 2002)
1417 self.assertEqual(dt.month, 3)
1418 self.assertEqual(dt.day, 1)
1419 self.assertEqual(dt.hour, 12)
1420 self.assertEqual(dt.minute, 59)
1421 self.assertEqual(dt.second, 59)
1422 self.assertEqual(dt.microsecond, 8000)
1423
1424 def test_roundtrip(self):
1425 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1426 self.theclass.now()):
1427 # Verify dt -> string -> datetime identity.
1428 s = repr(dt)
1429 self.assertTrue(s.startswith('datetime.'))
1430 s = s[9:]
1431 dt2 = eval(s)
1432 self.assertEqual(dt, dt2)
1433
1434 # Verify identity via reconstructing from pieces.
1435 dt2 = self.theclass(dt.year, dt.month, dt.day,
1436 dt.hour, dt.minute, dt.second,
1437 dt.microsecond)
1438 self.assertEqual(dt, dt2)
1439
1440 def test_isoformat(self):
1441 t = self.theclass(2, 3, 2, 4, 5, 1, 123)
1442 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123")
1443 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123")
1444 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123")
1445 self.assertEqual(t.isoformat('\x00'), "0002-03-02\x0004:05:01.000123")
1446 # str is ISO format with the separator forced to a blank.
1447 self.assertEqual(str(t), "0002-03-02 04:05:01.000123")
1448
1449 t = self.theclass(2, 3, 2)
1450 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1451 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1452 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1453 # str is ISO format with the separator forced to a blank.
1454 self.assertEqual(str(t), "0002-03-02 00:00:00")
1455
1456 def test_format(self):
1457 dt = self.theclass(2007, 9, 10, 4, 5, 1, 123)
1458 self.assertEqual(dt.__format__(''), str(dt))
1459
1460 # check that a derived class's __str__() gets called
1461 class A(self.theclass):
1462 def __str__(self):
1463 return 'A'
1464 a = A(2007, 9, 10, 4, 5, 1, 123)
1465 self.assertEqual(a.__format__(''), 'A')
1466
1467 # check that a derived class's strftime gets called
1468 class B(self.theclass):
1469 def strftime(self, format_spec):
1470 return 'B'
1471 b = B(2007, 9, 10, 4, 5, 1, 123)
1472 self.assertEqual(b.__format__(''), str(dt))
1473
1474 for fmt in ["m:%m d:%d y:%y",
1475 "m:%m d:%d y:%y H:%H M:%M S:%S",
1476 "%z %Z",
1477 ]:
1478 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1479 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1480 self.assertEqual(b.__format__(fmt), 'B')
1481
1482 def test_more_ctime(self):
1483 # Test fields that TestDate doesn't touch.
1484 import time
1485
1486 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1487 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1488 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1489 # out. The difference is that t.ctime() produces " 2" for the day,
1490 # but platform ctime() produces "02" for the day. According to
1491 # C99, t.ctime() is correct here.
1492 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1493
1494 # So test a case where that difference doesn't matter.
1495 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1496 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1497
1498 def test_tz_independent_comparing(self):
1499 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1500 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1501 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1502 self.assertEqual(dt1, dt3)
1503 self.assertTrue(dt2 > dt3)
1504
1505 # Make sure comparison doesn't forget microseconds, and isn't done
1506 # via comparing a float timestamp (an IEEE double doesn't have enough
1507 # precision to span microsecond resolution across years 1 thru 9999,
1508 # so comparing via timestamp necessarily calls some distinct values
1509 # equal).
1510 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1511 us = timedelta(microseconds=1)
1512 dt2 = dt1 + us
1513 self.assertEqual(dt2 - dt1, us)
1514 self.assertTrue(dt1 < dt2)
1515
1516 def test_strftime_with_bad_tzname_replace(self):
1517 # verify ok if tzinfo.tzname().replace() returns a non-string
1518 class MyTzInfo(FixedOffset):
1519 def tzname(self, dt):
1520 class MyStr(str):
1521 def replace(self, *args):
1522 return None
1523 return MyStr('name')
1524 t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
1525 self.assertRaises(TypeError, t.strftime, '%Z')
1526
1527 def test_bad_constructor_arguments(self):
1528 # bad years
1529 self.theclass(MINYEAR, 1, 1) # no exception
1530 self.theclass(MAXYEAR, 1, 1) # no exception
1531 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1532 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1533 # bad months
1534 self.theclass(2000, 1, 1) # no exception
1535 self.theclass(2000, 12, 1) # no exception
1536 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1537 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1538 # bad days
1539 self.theclass(2000, 2, 29) # no exception
1540 self.theclass(2004, 2, 29) # no exception
1541 self.theclass(2400, 2, 29) # no exception
1542 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1543 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1544 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1545 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1546 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1547 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1548 # bad hours
1549 self.theclass(2000, 1, 31, 0) # no exception
1550 self.theclass(2000, 1, 31, 23) # no exception
1551 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1552 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1553 # bad minutes
1554 self.theclass(2000, 1, 31, 23, 0) # no exception
1555 self.theclass(2000, 1, 31, 23, 59) # no exception
1556 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1557 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1558 # bad seconds
1559 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1560 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1561 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1562 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1563 # bad microseconds
1564 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1565 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1566 self.assertRaises(ValueError, self.theclass,
1567 2000, 1, 31, 23, 59, 59, -1)
1568 self.assertRaises(ValueError, self.theclass,
1569 2000, 1, 31, 23, 59, 59,
1570 1000000)
1571
1572 def test_hash_equality(self):
1573 d = self.theclass(2000, 12, 31, 23, 30, 17)
1574 e = self.theclass(2000, 12, 31, 23, 30, 17)
1575 self.assertEqual(d, e)
1576 self.assertEqual(hash(d), hash(e))
1577
1578 dic = {d: 1}
1579 dic[e] = 2
1580 self.assertEqual(len(dic), 1)
1581 self.assertEqual(dic[d], 2)
1582 self.assertEqual(dic[e], 2)
1583
1584 d = self.theclass(2001, 1, 1, 0, 5, 17)
1585 e = self.theclass(2001, 1, 1, 0, 5, 17)
1586 self.assertEqual(d, e)
1587 self.assertEqual(hash(d), hash(e))
1588
1589 dic = {d: 1}
1590 dic[e] = 2
1591 self.assertEqual(len(dic), 1)
1592 self.assertEqual(dic[d], 2)
1593 self.assertEqual(dic[e], 2)
1594
1595 def test_computations(self):
1596 a = self.theclass(2002, 1, 31)
1597 b = self.theclass(1956, 1, 31)
1598 diff = a-b
1599 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1600 self.assertEqual(diff.seconds, 0)
1601 self.assertEqual(diff.microseconds, 0)
1602 a = self.theclass(2002, 3, 2, 17, 6)
1603 millisec = timedelta(0, 0, 1000)
1604 hour = timedelta(0, 3600)
1605 day = timedelta(1)
1606 week = timedelta(7)
1607 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1608 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1609 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1610 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1611 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1612 self.assertEqual(a - hour, a + -hour)
1613 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1614 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1615 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1616 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1617 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1618 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1619 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1620 self.assertEqual((a + week) - a, week)
1621 self.assertEqual((a + day) - a, day)
1622 self.assertEqual((a + hour) - a, hour)
1623 self.assertEqual((a + millisec) - a, millisec)
1624 self.assertEqual((a - week) - a, -week)
1625 self.assertEqual((a - day) - a, -day)
1626 self.assertEqual((a - hour) - a, -hour)
1627 self.assertEqual((a - millisec) - a, -millisec)
1628 self.assertEqual(a - (a + week), -week)
1629 self.assertEqual(a - (a + day), -day)
1630 self.assertEqual(a - (a + hour), -hour)
1631 self.assertEqual(a - (a + millisec), -millisec)
1632 self.assertEqual(a - (a - week), week)
1633 self.assertEqual(a - (a - day), day)
1634 self.assertEqual(a - (a - hour), hour)
1635 self.assertEqual(a - (a - millisec), millisec)
1636 self.assertEqual(a + (week + day + hour + millisec),
1637 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1638 self.assertEqual(a + (week + day + hour + millisec),
1639 (((a + week) + day) + hour) + millisec)
1640 self.assertEqual(a - (week + day + hour + millisec),
1641 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1642 self.assertEqual(a - (week + day + hour + millisec),
1643 (((a - week) - day) - hour) - millisec)
1644 # Add/sub ints or floats should be illegal
1645 for i in 1, 1.0:
1646 self.assertRaises(TypeError, lambda: a+i)
1647 self.assertRaises(TypeError, lambda: a-i)
1648 self.assertRaises(TypeError, lambda: i+a)
1649 self.assertRaises(TypeError, lambda: i-a)
1650
1651 # delta - datetime is senseless.
1652 self.assertRaises(TypeError, lambda: day - a)
1653 # mixing datetime and (delta or datetime) via * or // is senseless
1654 self.assertRaises(TypeError, lambda: day * a)
1655 self.assertRaises(TypeError, lambda: a * day)
1656 self.assertRaises(TypeError, lambda: day // a)
1657 self.assertRaises(TypeError, lambda: a // day)
1658 self.assertRaises(TypeError, lambda: a * a)
1659 self.assertRaises(TypeError, lambda: a // a)
1660 # datetime + datetime is senseless
1661 self.assertRaises(TypeError, lambda: a + a)
1662
1663 def test_pickling(self):
1664 args = 6, 7, 23, 20, 59, 1, 64**2
1665 orig = self.theclass(*args)
1666 for pickler, unpickler, proto in pickle_choices:
1667 green = pickler.dumps(orig, proto)
1668 derived = unpickler.loads(green)
1669 self.assertEqual(orig, derived)
1670
1671 def test_more_pickling(self):
1672 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
1673 s = pickle.dumps(a)
1674 b = pickle.loads(s)
1675 self.assertEqual(b.year, 2003)
1676 self.assertEqual(b.month, 2)
1677 self.assertEqual(b.day, 7)
1678
1679 def test_pickling_subclass_datetime(self):
1680 args = 6, 7, 23, 20, 59, 1, 64**2
1681 orig = SubclassDatetime(*args)
1682 for pickler, unpickler, proto in pickle_choices:
1683 green = pickler.dumps(orig, proto)
1684 derived = unpickler.loads(green)
1685 self.assertEqual(orig, derived)
1686
1687 def test_more_compare(self):
1688 # The test_compare() inherited from TestDate covers the error cases.
1689 # We just want to test lexicographic ordering on the members datetime
1690 # has that date lacks.
1691 args = [2000, 11, 29, 20, 58, 16, 999998]
1692 t1 = self.theclass(*args)
1693 t2 = self.theclass(*args)
1694 self.assertEqual(t1, t2)
1695 self.assertTrue(t1 <= t2)
1696 self.assertTrue(t1 >= t2)
1697 self.assertTrue(not t1 != t2)
1698 self.assertTrue(not t1 < t2)
1699 self.assertTrue(not t1 > t2)
1700
1701 for i in range(len(args)):
1702 newargs = args[:]
1703 newargs[i] = args[i] + 1
1704 t2 = self.theclass(*newargs) # this is larger than t1
1705 self.assertTrue(t1 < t2)
1706 self.assertTrue(t2 > t1)
1707 self.assertTrue(t1 <= t2)
1708 self.assertTrue(t2 >= t1)
1709 self.assertTrue(t1 != t2)
1710 self.assertTrue(t2 != t1)
1711 self.assertTrue(not t1 == t2)
1712 self.assertTrue(not t2 == t1)
1713 self.assertTrue(not t1 > t2)
1714 self.assertTrue(not t2 < t1)
1715 self.assertTrue(not t1 >= t2)
1716 self.assertTrue(not t2 <= t1)
1717
1718
1719 # A helper for timestamp constructor tests.
1720 def verify_field_equality(self, expected, got):
1721 self.assertEqual(expected.tm_year, got.year)
1722 self.assertEqual(expected.tm_mon, got.month)
1723 self.assertEqual(expected.tm_mday, got.day)
1724 self.assertEqual(expected.tm_hour, got.hour)
1725 self.assertEqual(expected.tm_min, got.minute)
1726 self.assertEqual(expected.tm_sec, got.second)
1727
1728 def test_fromtimestamp(self):
1729 import time
1730
1731 ts = time.time()
1732 expected = time.localtime(ts)
1733 got = self.theclass.fromtimestamp(ts)
1734 self.verify_field_equality(expected, got)
1735
1736 def test_utcfromtimestamp(self):
1737 import time
1738
1739 ts = time.time()
1740 expected = time.gmtime(ts)
1741 got = self.theclass.utcfromtimestamp(ts)
1742 self.verify_field_equality(expected, got)
1743
Alexander Belopolskya4415142012-06-08 12:33:09 -04001744 # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in
1745 # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0).
1746 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
1747 def test_timestamp_naive(self):
1748 t = self.theclass(1970, 1, 1)
1749 self.assertEqual(t.timestamp(), 18000.0)
1750 t = self.theclass(1970, 1, 1, 1, 2, 3, 4)
1751 self.assertEqual(t.timestamp(),
1752 18000.0 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky0c687e52012-06-08 12:58:31 -04001753 # Missing hour may produce platform-dependent result
Alexander Belopolskya4415142012-06-08 12:33:09 -04001754 t = self.theclass(2012, 3, 11, 2, 30)
Alexander Belopolsky0c687e52012-06-08 12:58:31 -04001755 self.assertIn(self.theclass.fromtimestamp(t.timestamp()),
Alexander Belopolskyf6f56182012-06-08 13:00:27 -04001756 [t - timedelta(hours=1), t + timedelta(hours=1)])
Alexander Belopolskya4415142012-06-08 12:33:09 -04001757 # Ambiguous hour defaults to DST
1758 t = self.theclass(2012, 11, 4, 1, 30)
1759 self.assertEqual(self.theclass.fromtimestamp(t.timestamp()), t)
1760
1761 # Timestamp may raise an overflow error on some platforms
1762 for t in [self.theclass(1,1,1), self.theclass(9999,12,12)]:
1763 try:
1764 s = t.timestamp()
1765 except OverflowError:
1766 pass
1767 else:
1768 self.assertEqual(self.theclass.fromtimestamp(s), t)
1769
1770 def test_timestamp_aware(self):
1771 t = self.theclass(1970, 1, 1, tzinfo=timezone.utc)
1772 self.assertEqual(t.timestamp(), 0.0)
1773 t = self.theclass(1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone.utc)
1774 self.assertEqual(t.timestamp(),
1775 3600 + 2*60 + 3 + 4*1e-6)
1776 t = self.theclass(1970, 1, 1, 1, 2, 3, 4,
1777 tzinfo=timezone(timedelta(hours=-5), 'EST'))
1778 self.assertEqual(t.timestamp(),
1779 18000 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001780 def test_microsecond_rounding(self):
Alexander Belopolsky3e62f782010-09-21 16:30:56 +00001781 for fts in [self.theclass.fromtimestamp,
1782 self.theclass.utcfromtimestamp]:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001783 zero = fts(0)
1784 self.assertEqual(zero.second, 0)
1785 self.assertEqual(zero.microsecond, 0)
Victor Stinner8050ca92012-03-14 00:17:05 +01001786 try:
1787 minus_one = fts(-1e-6)
1788 except OSError:
1789 # localtime(-1) and gmtime(-1) is not supported on Windows
1790 pass
1791 else:
1792 self.assertEqual(minus_one.second, 59)
1793 self.assertEqual(minus_one.microsecond, 999999)
Victor Stinner5d272cc2012-03-13 13:35:55 +01001794
Victor Stinner8050ca92012-03-14 00:17:05 +01001795 t = fts(-1e-8)
1796 self.assertEqual(t, minus_one)
1797 t = fts(-9e-7)
1798 self.assertEqual(t, minus_one)
1799 t = fts(-1e-7)
1800 self.assertEqual(t, minus_one)
Victor Stinner5d272cc2012-03-13 13:35:55 +01001801
1802 t = fts(1e-7)
1803 self.assertEqual(t, zero)
1804 t = fts(9e-7)
1805 self.assertEqual(t, zero)
1806 t = fts(0.99999949)
1807 self.assertEqual(t.second, 0)
1808 self.assertEqual(t.microsecond, 999999)
1809 t = fts(0.9999999)
1810 self.assertEqual(t.second, 0)
1811 self.assertEqual(t.microsecond, 999999)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001812
1813 def test_insane_fromtimestamp(self):
1814 # It's possible that some platform maps time_t to double,
1815 # and that this test will fail there. This test should
1816 # exempt such platforms (provided they return reasonable
1817 # results!).
1818 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001819 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001820 insane)
1821
1822 def test_insane_utcfromtimestamp(self):
1823 # It's possible that some platform maps time_t to double,
1824 # and that this test will fail there. This test should
1825 # exempt such platforms (provided they return reasonable
1826 # results!).
1827 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001828 self.assertRaises(OverflowError, self.theclass.utcfromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001829 insane)
1830 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
1831 def test_negative_float_fromtimestamp(self):
1832 # The result is tz-dependent; at least test that this doesn't
1833 # fail (like it did before bug 1646728 was fixed).
1834 self.theclass.fromtimestamp(-1.05)
1835
1836 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
1837 def test_negative_float_utcfromtimestamp(self):
1838 d = self.theclass.utcfromtimestamp(-1.05)
1839 self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
1840
1841 def test_utcnow(self):
1842 import time
1843
1844 # Call it a success if utcnow() and utcfromtimestamp() are within
1845 # a second of each other.
1846 tolerance = timedelta(seconds=1)
1847 for dummy in range(3):
1848 from_now = self.theclass.utcnow()
1849 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1850 if abs(from_timestamp - from_now) <= tolerance:
1851 break
1852 # Else try again a few times.
1853 self.assertTrue(abs(from_timestamp - from_now) <= tolerance)
1854
1855 def test_strptime(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001856 string = '2004-12-01 13:02:47.197'
1857 format = '%Y-%m-%d %H:%M:%S.%f'
1858 expected = _strptime._strptime_datetime(self.theclass, string, format)
1859 got = self.theclass.strptime(string, format)
1860 self.assertEqual(expected, got)
1861 self.assertIs(type(expected), self.theclass)
1862 self.assertIs(type(got), self.theclass)
1863
1864 strptime = self.theclass.strptime
1865 self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
1866 self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
1867 # Only local timezone and UTC are supported
1868 for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
1869 (-_time.timezone, _time.tzname[0])):
1870 if tzseconds < 0:
1871 sign = '-'
1872 seconds = -tzseconds
1873 else:
1874 sign ='+'
1875 seconds = tzseconds
1876 hours, minutes = divmod(seconds//60, 60)
1877 dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
1878 dt = strptime(dtstr, "%z %Z")
1879 self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
1880 self.assertEqual(dt.tzname(), tzname)
1881 # Can produce inconsistent datetime
1882 dtstr, fmt = "+1234 UTC", "%z %Z"
1883 dt = strptime(dtstr, fmt)
1884 self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE)
1885 self.assertEqual(dt.tzname(), 'UTC')
1886 # yet will roundtrip
1887 self.assertEqual(dt.strftime(fmt), dtstr)
1888
1889 # Produce naive datetime if no %z is provided
1890 self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
1891
1892 with self.assertRaises(ValueError): strptime("-2400", "%z")
1893 with self.assertRaises(ValueError): strptime("-000", "%z")
1894
1895 def test_more_timetuple(self):
1896 # This tests fields beyond those tested by the TestDate.test_timetuple.
1897 t = self.theclass(2004, 12, 31, 6, 22, 33)
1898 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
1899 self.assertEqual(t.timetuple(),
1900 (t.year, t.month, t.day,
1901 t.hour, t.minute, t.second,
1902 t.weekday(),
1903 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
1904 -1))
1905 tt = t.timetuple()
1906 self.assertEqual(tt.tm_year, t.year)
1907 self.assertEqual(tt.tm_mon, t.month)
1908 self.assertEqual(tt.tm_mday, t.day)
1909 self.assertEqual(tt.tm_hour, t.hour)
1910 self.assertEqual(tt.tm_min, t.minute)
1911 self.assertEqual(tt.tm_sec, t.second)
1912 self.assertEqual(tt.tm_wday, t.weekday())
1913 self.assertEqual(tt.tm_yday, t.toordinal() -
1914 date(t.year, 1, 1).toordinal() + 1)
1915 self.assertEqual(tt.tm_isdst, -1)
1916
1917 def test_more_strftime(self):
1918 # This tests fields beyond those tested by the TestDate.test_strftime.
1919 t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
1920 self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
1921 "12 31 04 000047 33 22 06 366")
1922
1923 def test_extract(self):
1924 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1925 self.assertEqual(dt.date(), date(2002, 3, 4))
1926 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
1927
1928 def test_combine(self):
1929 d = date(2002, 3, 4)
1930 t = time(18, 45, 3, 1234)
1931 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1932 combine = self.theclass.combine
1933 dt = combine(d, t)
1934 self.assertEqual(dt, expected)
1935
1936 dt = combine(time=t, date=d)
1937 self.assertEqual(dt, expected)
1938
1939 self.assertEqual(d, dt.date())
1940 self.assertEqual(t, dt.time())
1941 self.assertEqual(dt, combine(dt.date(), dt.time()))
1942
1943 self.assertRaises(TypeError, combine) # need an arg
1944 self.assertRaises(TypeError, combine, d) # need two args
1945 self.assertRaises(TypeError, combine, t, d) # args reversed
1946 self.assertRaises(TypeError, combine, d, t, 1) # too many args
1947 self.assertRaises(TypeError, combine, "date", "time") # wrong types
1948 self.assertRaises(TypeError, combine, d, "time") # wrong type
1949 self.assertRaises(TypeError, combine, "date", t) # wrong type
1950
1951 def test_replace(self):
1952 cls = self.theclass
1953 args = [1, 2, 3, 4, 5, 6, 7]
1954 base = cls(*args)
1955 self.assertEqual(base, base.replace())
1956
1957 i = 0
1958 for name, newval in (("year", 2),
1959 ("month", 3),
1960 ("day", 4),
1961 ("hour", 5),
1962 ("minute", 6),
1963 ("second", 7),
1964 ("microsecond", 8)):
1965 newargs = args[:]
1966 newargs[i] = newval
1967 expected = cls(*newargs)
1968 got = base.replace(**{name: newval})
1969 self.assertEqual(expected, got)
1970 i += 1
1971
1972 # Out of bounds.
1973 base = cls(2000, 2, 29)
1974 self.assertRaises(ValueError, base.replace, year=2001)
1975
1976 def test_astimezone(self):
1977 # Pretty boring! The TZ test is more interesting here. astimezone()
1978 # simply can't be applied to a naive object.
1979 dt = self.theclass.now()
1980 f = FixedOffset(44, "")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04001981 self.assertRaises(ValueError, dt.astimezone) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001982 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
1983 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
1984 self.assertRaises(ValueError, dt.astimezone, f) # naive
1985 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
1986
1987 class Bogus(tzinfo):
1988 def utcoffset(self, dt): return None
1989 def dst(self, dt): return timedelta(0)
1990 bog = Bogus()
1991 self.assertRaises(ValueError, dt.astimezone, bog) # naive
1992 self.assertRaises(ValueError,
1993 dt.replace(tzinfo=bog).astimezone, f)
1994
1995 class AlsoBogus(tzinfo):
1996 def utcoffset(self, dt): return timedelta(0)
1997 def dst(self, dt): return None
1998 alsobog = AlsoBogus()
1999 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
2000
2001 def test_subclass_datetime(self):
2002
2003 class C(self.theclass):
2004 theAnswer = 42
2005
2006 def __new__(cls, *args, **kws):
2007 temp = kws.copy()
2008 extra = temp.pop('extra')
2009 result = self.theclass.__new__(cls, *args, **temp)
2010 result.extra = extra
2011 return result
2012
2013 def newmeth(self, start):
2014 return start + self.year + self.month + self.second
2015
2016 args = 2003, 4, 14, 12, 13, 41
2017
2018 dt1 = self.theclass(*args)
2019 dt2 = C(*args, **{'extra': 7})
2020
2021 self.assertEqual(dt2.__class__, C)
2022 self.assertEqual(dt2.theAnswer, 42)
2023 self.assertEqual(dt2.extra, 7)
2024 self.assertEqual(dt1.toordinal(), dt2.toordinal())
2025 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
2026 dt1.second - 7)
2027
2028class TestSubclassDateTime(TestDateTime):
2029 theclass = SubclassDatetime
2030 # Override tests not designed for subclass
2031 def test_roundtrip(self):
2032 pass
2033
2034class SubclassTime(time):
2035 sub_var = 1
2036
2037class TestTime(HarmlessMixedComparison, unittest.TestCase):
2038
2039 theclass = time
2040
2041 def test_basic_attributes(self):
2042 t = self.theclass(12, 0)
2043 self.assertEqual(t.hour, 12)
2044 self.assertEqual(t.minute, 0)
2045 self.assertEqual(t.second, 0)
2046 self.assertEqual(t.microsecond, 0)
2047
2048 def test_basic_attributes_nonzero(self):
2049 # Make sure all attributes are non-zero so bugs in
2050 # bit-shifting access show up.
2051 t = self.theclass(12, 59, 59, 8000)
2052 self.assertEqual(t.hour, 12)
2053 self.assertEqual(t.minute, 59)
2054 self.assertEqual(t.second, 59)
2055 self.assertEqual(t.microsecond, 8000)
2056
2057 def test_roundtrip(self):
2058 t = self.theclass(1, 2, 3, 4)
2059
2060 # Verify t -> string -> time identity.
2061 s = repr(t)
2062 self.assertTrue(s.startswith('datetime.'))
2063 s = s[9:]
2064 t2 = eval(s)
2065 self.assertEqual(t, t2)
2066
2067 # Verify identity via reconstructing from pieces.
2068 t2 = self.theclass(t.hour, t.minute, t.second,
2069 t.microsecond)
2070 self.assertEqual(t, t2)
2071
2072 def test_comparing(self):
2073 args = [1, 2, 3, 4]
2074 t1 = self.theclass(*args)
2075 t2 = self.theclass(*args)
2076 self.assertEqual(t1, t2)
2077 self.assertTrue(t1 <= t2)
2078 self.assertTrue(t1 >= t2)
2079 self.assertTrue(not t1 != t2)
2080 self.assertTrue(not t1 < t2)
2081 self.assertTrue(not t1 > t2)
2082
2083 for i in range(len(args)):
2084 newargs = args[:]
2085 newargs[i] = args[i] + 1
2086 t2 = self.theclass(*newargs) # this is larger than t1
2087 self.assertTrue(t1 < t2)
2088 self.assertTrue(t2 > t1)
2089 self.assertTrue(t1 <= t2)
2090 self.assertTrue(t2 >= t1)
2091 self.assertTrue(t1 != t2)
2092 self.assertTrue(t2 != t1)
2093 self.assertTrue(not t1 == t2)
2094 self.assertTrue(not t2 == t1)
2095 self.assertTrue(not t1 > t2)
2096 self.assertTrue(not t2 < t1)
2097 self.assertTrue(not t1 >= t2)
2098 self.assertTrue(not t2 <= t1)
2099
2100 for badarg in OTHERSTUFF:
2101 self.assertEqual(t1 == badarg, False)
2102 self.assertEqual(t1 != badarg, True)
2103 self.assertEqual(badarg == t1, False)
2104 self.assertEqual(badarg != t1, True)
2105
2106 self.assertRaises(TypeError, lambda: t1 <= badarg)
2107 self.assertRaises(TypeError, lambda: t1 < badarg)
2108 self.assertRaises(TypeError, lambda: t1 > badarg)
2109 self.assertRaises(TypeError, lambda: t1 >= badarg)
2110 self.assertRaises(TypeError, lambda: badarg <= t1)
2111 self.assertRaises(TypeError, lambda: badarg < t1)
2112 self.assertRaises(TypeError, lambda: badarg > t1)
2113 self.assertRaises(TypeError, lambda: badarg >= t1)
2114
2115 def test_bad_constructor_arguments(self):
2116 # bad hours
2117 self.theclass(0, 0) # no exception
2118 self.theclass(23, 0) # no exception
2119 self.assertRaises(ValueError, self.theclass, -1, 0)
2120 self.assertRaises(ValueError, self.theclass, 24, 0)
2121 # bad minutes
2122 self.theclass(23, 0) # no exception
2123 self.theclass(23, 59) # no exception
2124 self.assertRaises(ValueError, self.theclass, 23, -1)
2125 self.assertRaises(ValueError, self.theclass, 23, 60)
2126 # bad seconds
2127 self.theclass(23, 59, 0) # no exception
2128 self.theclass(23, 59, 59) # no exception
2129 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
2130 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
2131 # bad microseconds
2132 self.theclass(23, 59, 59, 0) # no exception
2133 self.theclass(23, 59, 59, 999999) # no exception
2134 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
2135 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
2136
2137 def test_hash_equality(self):
2138 d = self.theclass(23, 30, 17)
2139 e = self.theclass(23, 30, 17)
2140 self.assertEqual(d, e)
2141 self.assertEqual(hash(d), hash(e))
2142
2143 dic = {d: 1}
2144 dic[e] = 2
2145 self.assertEqual(len(dic), 1)
2146 self.assertEqual(dic[d], 2)
2147 self.assertEqual(dic[e], 2)
2148
2149 d = self.theclass(0, 5, 17)
2150 e = self.theclass(0, 5, 17)
2151 self.assertEqual(d, e)
2152 self.assertEqual(hash(d), hash(e))
2153
2154 dic = {d: 1}
2155 dic[e] = 2
2156 self.assertEqual(len(dic), 1)
2157 self.assertEqual(dic[d], 2)
2158 self.assertEqual(dic[e], 2)
2159
2160 def test_isoformat(self):
2161 t = self.theclass(4, 5, 1, 123)
2162 self.assertEqual(t.isoformat(), "04:05:01.000123")
2163 self.assertEqual(t.isoformat(), str(t))
2164
2165 t = self.theclass()
2166 self.assertEqual(t.isoformat(), "00:00:00")
2167 self.assertEqual(t.isoformat(), str(t))
2168
2169 t = self.theclass(microsecond=1)
2170 self.assertEqual(t.isoformat(), "00:00:00.000001")
2171 self.assertEqual(t.isoformat(), str(t))
2172
2173 t = self.theclass(microsecond=10)
2174 self.assertEqual(t.isoformat(), "00:00:00.000010")
2175 self.assertEqual(t.isoformat(), str(t))
2176
2177 t = self.theclass(microsecond=100)
2178 self.assertEqual(t.isoformat(), "00:00:00.000100")
2179 self.assertEqual(t.isoformat(), str(t))
2180
2181 t = self.theclass(microsecond=1000)
2182 self.assertEqual(t.isoformat(), "00:00:00.001000")
2183 self.assertEqual(t.isoformat(), str(t))
2184
2185 t = self.theclass(microsecond=10000)
2186 self.assertEqual(t.isoformat(), "00:00:00.010000")
2187 self.assertEqual(t.isoformat(), str(t))
2188
2189 t = self.theclass(microsecond=100000)
2190 self.assertEqual(t.isoformat(), "00:00:00.100000")
2191 self.assertEqual(t.isoformat(), str(t))
2192
2193 def test_1653736(self):
2194 # verify it doesn't accept extra keyword arguments
2195 t = self.theclass(second=1)
2196 self.assertRaises(TypeError, t.isoformat, foo=3)
2197
2198 def test_strftime(self):
2199 t = self.theclass(1, 2, 3, 4)
2200 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
2201 # A naive object replaces %z and %Z with empty strings.
2202 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
2203
2204 def test_format(self):
2205 t = self.theclass(1, 2, 3, 4)
2206 self.assertEqual(t.__format__(''), str(t))
2207
2208 # check that a derived class's __str__() gets called
2209 class A(self.theclass):
2210 def __str__(self):
2211 return 'A'
2212 a = A(1, 2, 3, 4)
2213 self.assertEqual(a.__format__(''), 'A')
2214
2215 # check that a derived class's strftime gets called
2216 class B(self.theclass):
2217 def strftime(self, format_spec):
2218 return 'B'
2219 b = B(1, 2, 3, 4)
2220 self.assertEqual(b.__format__(''), str(t))
2221
2222 for fmt in ['%H %M %S',
2223 ]:
2224 self.assertEqual(t.__format__(fmt), t.strftime(fmt))
2225 self.assertEqual(a.__format__(fmt), t.strftime(fmt))
2226 self.assertEqual(b.__format__(fmt), 'B')
2227
2228 def test_str(self):
2229 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
2230 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
2231 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
2232 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
2233 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
2234
2235 def test_repr(self):
2236 name = 'datetime.' + self.theclass.__name__
2237 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
2238 "%s(1, 2, 3, 4)" % name)
2239 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
2240 "%s(10, 2, 3, 4000)" % name)
2241 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
2242 "%s(0, 2, 3, 400000)" % name)
2243 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
2244 "%s(12, 2, 3)" % name)
2245 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
2246 "%s(23, 15)" % name)
2247
2248 def test_resolution_info(self):
2249 self.assertIsInstance(self.theclass.min, self.theclass)
2250 self.assertIsInstance(self.theclass.max, self.theclass)
2251 self.assertIsInstance(self.theclass.resolution, timedelta)
2252 self.assertTrue(self.theclass.max > self.theclass.min)
2253
2254 def test_pickling(self):
2255 args = 20, 59, 16, 64**2
2256 orig = self.theclass(*args)
2257 for pickler, unpickler, proto in pickle_choices:
2258 green = pickler.dumps(orig, proto)
2259 derived = unpickler.loads(green)
2260 self.assertEqual(orig, derived)
2261
2262 def test_pickling_subclass_time(self):
2263 args = 20, 59, 16, 64**2
2264 orig = SubclassTime(*args)
2265 for pickler, unpickler, proto in pickle_choices:
2266 green = pickler.dumps(orig, proto)
2267 derived = unpickler.loads(green)
2268 self.assertEqual(orig, derived)
2269
2270 def test_bool(self):
2271 cls = self.theclass
2272 self.assertTrue(cls(1))
2273 self.assertTrue(cls(0, 1))
2274 self.assertTrue(cls(0, 0, 1))
2275 self.assertTrue(cls(0, 0, 0, 1))
2276 self.assertTrue(not cls(0))
2277 self.assertTrue(not cls())
2278
2279 def test_replace(self):
2280 cls = self.theclass
2281 args = [1, 2, 3, 4]
2282 base = cls(*args)
2283 self.assertEqual(base, base.replace())
2284
2285 i = 0
2286 for name, newval in (("hour", 5),
2287 ("minute", 6),
2288 ("second", 7),
2289 ("microsecond", 8)):
2290 newargs = args[:]
2291 newargs[i] = newval
2292 expected = cls(*newargs)
2293 got = base.replace(**{name: newval})
2294 self.assertEqual(expected, got)
2295 i += 1
2296
2297 # Out of bounds.
2298 base = cls(1)
2299 self.assertRaises(ValueError, base.replace, hour=24)
2300 self.assertRaises(ValueError, base.replace, minute=-1)
2301 self.assertRaises(ValueError, base.replace, second=100)
2302 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2303
2304 def test_subclass_time(self):
2305
2306 class C(self.theclass):
2307 theAnswer = 42
2308
2309 def __new__(cls, *args, **kws):
2310 temp = kws.copy()
2311 extra = temp.pop('extra')
2312 result = self.theclass.__new__(cls, *args, **temp)
2313 result.extra = extra
2314 return result
2315
2316 def newmeth(self, start):
2317 return start + self.hour + self.second
2318
2319 args = 4, 5, 6
2320
2321 dt1 = self.theclass(*args)
2322 dt2 = C(*args, **{'extra': 7})
2323
2324 self.assertEqual(dt2.__class__, C)
2325 self.assertEqual(dt2.theAnswer, 42)
2326 self.assertEqual(dt2.extra, 7)
2327 self.assertEqual(dt1.isoformat(), dt2.isoformat())
2328 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2329
2330 def test_backdoor_resistance(self):
2331 # see TestDate.test_backdoor_resistance().
2332 base = '2:59.0'
2333 for hour_byte in ' ', '9', chr(24), '\xff':
2334 self.assertRaises(TypeError, self.theclass,
2335 hour_byte + base[1:])
2336
2337# A mixin for classes with a tzinfo= argument. Subclasses must define
2338# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
2339# must be legit (which is true for time and datetime).
2340class TZInfoBase:
2341
2342 def test_argument_passing(self):
2343 cls = self.theclass
2344 # A datetime passes itself on, a time passes None.
2345 class introspective(tzinfo):
2346 def tzname(self, dt): return dt and "real" or "none"
2347 def utcoffset(self, dt):
2348 return timedelta(minutes = dt and 42 or -42)
2349 dst = utcoffset
2350
2351 obj = cls(1, 2, 3, tzinfo=introspective())
2352
2353 expected = cls is time and "none" or "real"
2354 self.assertEqual(obj.tzname(), expected)
2355
2356 expected = timedelta(minutes=(cls is time and -42 or 42))
2357 self.assertEqual(obj.utcoffset(), expected)
2358 self.assertEqual(obj.dst(), expected)
2359
2360 def test_bad_tzinfo_classes(self):
2361 cls = self.theclass
2362 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
2363
2364 class NiceTry(object):
2365 def __init__(self): pass
2366 def utcoffset(self, dt): pass
2367 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
2368
2369 class BetterTry(tzinfo):
2370 def __init__(self): pass
2371 def utcoffset(self, dt): pass
2372 b = BetterTry()
2373 t = cls(1, 1, 1, tzinfo=b)
2374 self.assertTrue(t.tzinfo is b)
2375
2376 def test_utc_offset_out_of_bounds(self):
2377 class Edgy(tzinfo):
2378 def __init__(self, offset):
2379 self.offset = timedelta(minutes=offset)
2380 def utcoffset(self, dt):
2381 return self.offset
2382
2383 cls = self.theclass
2384 for offset, legit in ((-1440, False),
2385 (-1439, True),
2386 (1439, True),
2387 (1440, False)):
2388 if cls is time:
2389 t = cls(1, 2, 3, tzinfo=Edgy(offset))
2390 elif cls is datetime:
2391 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
2392 else:
2393 assert 0, "impossible"
2394 if legit:
2395 aofs = abs(offset)
2396 h, m = divmod(aofs, 60)
2397 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
2398 if isinstance(t, datetime):
2399 t = t.timetz()
2400 self.assertEqual(str(t), "01:02:03" + tag)
2401 else:
2402 self.assertRaises(ValueError, str, t)
2403
2404 def test_tzinfo_classes(self):
2405 cls = self.theclass
2406 class C1(tzinfo):
2407 def utcoffset(self, dt): return None
2408 def dst(self, dt): return None
2409 def tzname(self, dt): return None
2410 for t in (cls(1, 1, 1),
2411 cls(1, 1, 1, tzinfo=None),
2412 cls(1, 1, 1, tzinfo=C1())):
2413 self.assertTrue(t.utcoffset() is None)
2414 self.assertTrue(t.dst() is None)
2415 self.assertTrue(t.tzname() is None)
2416
2417 class C3(tzinfo):
2418 def utcoffset(self, dt): return timedelta(minutes=-1439)
2419 def dst(self, dt): return timedelta(minutes=1439)
2420 def tzname(self, dt): return "aname"
2421 t = cls(1, 1, 1, tzinfo=C3())
2422 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
2423 self.assertEqual(t.dst(), timedelta(minutes=1439))
2424 self.assertEqual(t.tzname(), "aname")
2425
2426 # Wrong types.
2427 class C4(tzinfo):
2428 def utcoffset(self, dt): return "aname"
2429 def dst(self, dt): return 7
2430 def tzname(self, dt): return 0
2431 t = cls(1, 1, 1, tzinfo=C4())
2432 self.assertRaises(TypeError, t.utcoffset)
2433 self.assertRaises(TypeError, t.dst)
2434 self.assertRaises(TypeError, t.tzname)
2435
2436 # Offset out of range.
2437 class C6(tzinfo):
2438 def utcoffset(self, dt): return timedelta(hours=-24)
2439 def dst(self, dt): return timedelta(hours=24)
2440 t = cls(1, 1, 1, tzinfo=C6())
2441 self.assertRaises(ValueError, t.utcoffset)
2442 self.assertRaises(ValueError, t.dst)
2443
2444 # Not a whole number of minutes.
2445 class C7(tzinfo):
2446 def utcoffset(self, dt): return timedelta(seconds=61)
2447 def dst(self, dt): return timedelta(microseconds=-81)
2448 t = cls(1, 1, 1, tzinfo=C7())
2449 self.assertRaises(ValueError, t.utcoffset)
2450 self.assertRaises(ValueError, t.dst)
2451
2452 def test_aware_compare(self):
2453 cls = self.theclass
2454
2455 # Ensure that utcoffset() gets ignored if the comparands have
2456 # the same tzinfo member.
2457 class OperandDependentOffset(tzinfo):
2458 def utcoffset(self, t):
2459 if t.minute < 10:
2460 # d0 and d1 equal after adjustment
2461 return timedelta(minutes=t.minute)
2462 else:
2463 # d2 off in the weeds
2464 return timedelta(minutes=59)
2465
2466 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
2467 d0 = base.replace(minute=3)
2468 d1 = base.replace(minute=9)
2469 d2 = base.replace(minute=11)
2470 for x in d0, d1, d2:
2471 for y in d0, d1, d2:
2472 for op in lt, le, gt, ge, eq, ne:
2473 got = op(x, y)
2474 expected = op(x.minute, y.minute)
2475 self.assertEqual(got, expected)
2476
2477 # However, if they're different members, uctoffset is not ignored.
2478 # Note that a time can't actually have an operand-depedent offset,
2479 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
2480 # so skip this test for time.
2481 if cls is not time:
2482 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2483 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2484 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2485 for x in d0, d1, d2:
2486 for y in d0, d1, d2:
2487 got = (x > y) - (x < y)
2488 if (x is d0 or x is d1) and (y is d0 or y is d1):
2489 expected = 0
2490 elif x is y is d2:
2491 expected = 0
2492 elif x is d2:
2493 expected = -1
2494 else:
2495 assert y is d2
2496 expected = 1
2497 self.assertEqual(got, expected)
2498
2499
2500# Testing time objects with a non-None tzinfo.
2501class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
2502 theclass = time
2503
2504 def test_empty(self):
2505 t = self.theclass()
2506 self.assertEqual(t.hour, 0)
2507 self.assertEqual(t.minute, 0)
2508 self.assertEqual(t.second, 0)
2509 self.assertEqual(t.microsecond, 0)
2510 self.assertTrue(t.tzinfo is None)
2511
2512 def test_zones(self):
2513 est = FixedOffset(-300, "EST", 1)
2514 utc = FixedOffset(0, "UTC", -2)
2515 met = FixedOffset(60, "MET", 3)
2516 t1 = time( 7, 47, tzinfo=est)
2517 t2 = time(12, 47, tzinfo=utc)
2518 t3 = time(13, 47, tzinfo=met)
2519 t4 = time(microsecond=40)
2520 t5 = time(microsecond=40, tzinfo=utc)
2521
2522 self.assertEqual(t1.tzinfo, est)
2523 self.assertEqual(t2.tzinfo, utc)
2524 self.assertEqual(t3.tzinfo, met)
2525 self.assertTrue(t4.tzinfo is None)
2526 self.assertEqual(t5.tzinfo, utc)
2527
2528 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2529 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2530 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
2531 self.assertTrue(t4.utcoffset() is None)
2532 self.assertRaises(TypeError, t1.utcoffset, "no args")
2533
2534 self.assertEqual(t1.tzname(), "EST")
2535 self.assertEqual(t2.tzname(), "UTC")
2536 self.assertEqual(t3.tzname(), "MET")
2537 self.assertTrue(t4.tzname() is None)
2538 self.assertRaises(TypeError, t1.tzname, "no args")
2539
2540 self.assertEqual(t1.dst(), timedelta(minutes=1))
2541 self.assertEqual(t2.dst(), timedelta(minutes=-2))
2542 self.assertEqual(t3.dst(), timedelta(minutes=3))
2543 self.assertTrue(t4.dst() is None)
2544 self.assertRaises(TypeError, t1.dst, "no args")
2545
2546 self.assertEqual(hash(t1), hash(t2))
2547 self.assertEqual(hash(t1), hash(t3))
2548 self.assertEqual(hash(t2), hash(t3))
2549
2550 self.assertEqual(t1, t2)
2551 self.assertEqual(t1, t3)
2552 self.assertEqual(t2, t3)
Alexander Belopolsky08313822012-06-15 20:19:47 -04002553 self.assertNotEqual(t4, t5) # mixed tz-aware & naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002554 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
2555 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
2556
2557 self.assertEqual(str(t1), "07:47:00-05:00")
2558 self.assertEqual(str(t2), "12:47:00+00:00")
2559 self.assertEqual(str(t3), "13:47:00+01:00")
2560 self.assertEqual(str(t4), "00:00:00.000040")
2561 self.assertEqual(str(t5), "00:00:00.000040+00:00")
2562
2563 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
2564 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
2565 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
2566 self.assertEqual(t4.isoformat(), "00:00:00.000040")
2567 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
2568
2569 d = 'datetime.time'
2570 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
2571 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
2572 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
2573 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
2574 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
2575
2576 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
2577 "07:47:00 %Z=EST %z=-0500")
2578 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
2579 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
2580
2581 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
2582 t1 = time(23, 59, tzinfo=yuck)
2583 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
2584 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
2585
2586 # Check that an invalid tzname result raises an exception.
2587 class Badtzname(tzinfo):
Alexander Belopolskye239d232010-12-08 23:31:48 +00002588 tz = 42
2589 def tzname(self, dt): return self.tz
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002590 t = time(2, 3, 4, tzinfo=Badtzname())
2591 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
2592 self.assertRaises(TypeError, t.strftime, "%Z")
2593
Alexander Belopolskye239d232010-12-08 23:31:48 +00002594 # Issue #6697:
2595 if '_Fast' in str(type(self)):
2596 Badtzname.tz = '\ud800'
2597 self.assertRaises(ValueError, t.strftime, "%Z")
2598
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002599 def test_hash_edge_cases(self):
2600 # Offsets that overflow a basic time.
2601 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2602 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2603 self.assertEqual(hash(t1), hash(t2))
2604
2605 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2606 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2607 self.assertEqual(hash(t1), hash(t2))
2608
2609 def test_pickling(self):
2610 # Try one without a tzinfo.
2611 args = 20, 59, 16, 64**2
2612 orig = self.theclass(*args)
2613 for pickler, unpickler, proto in pickle_choices:
2614 green = pickler.dumps(orig, proto)
2615 derived = unpickler.loads(green)
2616 self.assertEqual(orig, derived)
2617
2618 # Try one with a tzinfo.
2619 tinfo = PicklableFixedOffset(-300, 'cookie')
2620 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
2621 for pickler, unpickler, proto in pickle_choices:
2622 green = pickler.dumps(orig, proto)
2623 derived = unpickler.loads(green)
2624 self.assertEqual(orig, derived)
2625 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
2626 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2627 self.assertEqual(derived.tzname(), 'cookie')
2628
2629 def test_more_bool(self):
2630 # Test cases with non-None tzinfo.
2631 cls = self.theclass
2632
2633 t = cls(0, tzinfo=FixedOffset(-300, ""))
2634 self.assertTrue(t)
2635
2636 t = cls(5, tzinfo=FixedOffset(-300, ""))
2637 self.assertTrue(t)
2638
2639 t = cls(5, tzinfo=FixedOffset(300, ""))
2640 self.assertTrue(not t)
2641
2642 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2643 self.assertTrue(not t)
2644
2645 # Mostly ensuring this doesn't overflow internally.
2646 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
2647 self.assertTrue(t)
2648
2649 # But this should yield a value error -- the utcoffset is bogus.
2650 t = cls(0, tzinfo=FixedOffset(24*60, ""))
2651 self.assertRaises(ValueError, lambda: bool(t))
2652
2653 # Likewise.
2654 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
2655 self.assertRaises(ValueError, lambda: bool(t))
2656
2657 def test_replace(self):
2658 cls = self.theclass
2659 z100 = FixedOffset(100, "+100")
2660 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2661 args = [1, 2, 3, 4, z100]
2662 base = cls(*args)
2663 self.assertEqual(base, base.replace())
2664
2665 i = 0
2666 for name, newval in (("hour", 5),
2667 ("minute", 6),
2668 ("second", 7),
2669 ("microsecond", 8),
2670 ("tzinfo", zm200)):
2671 newargs = args[:]
2672 newargs[i] = newval
2673 expected = cls(*newargs)
2674 got = base.replace(**{name: newval})
2675 self.assertEqual(expected, got)
2676 i += 1
2677
2678 # Ensure we can get rid of a tzinfo.
2679 self.assertEqual(base.tzname(), "+100")
2680 base2 = base.replace(tzinfo=None)
2681 self.assertTrue(base2.tzinfo is None)
2682 self.assertTrue(base2.tzname() is None)
2683
2684 # Ensure we can add one.
2685 base3 = base2.replace(tzinfo=z100)
2686 self.assertEqual(base, base3)
2687 self.assertTrue(base.tzinfo is base3.tzinfo)
2688
2689 # Out of bounds.
2690 base = cls(1)
2691 self.assertRaises(ValueError, base.replace, hour=24)
2692 self.assertRaises(ValueError, base.replace, minute=-1)
2693 self.assertRaises(ValueError, base.replace, second=100)
2694 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2695
2696 def test_mixed_compare(self):
2697 t1 = time(1, 2, 3)
2698 t2 = time(1, 2, 3)
2699 self.assertEqual(t1, t2)
2700 t2 = t2.replace(tzinfo=None)
2701 self.assertEqual(t1, t2)
2702 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2703 self.assertEqual(t1, t2)
2704 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04002705 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002706
2707 # In time w/ identical tzinfo objects, utcoffset is ignored.
2708 class Varies(tzinfo):
2709 def __init__(self):
2710 self.offset = timedelta(minutes=22)
2711 def utcoffset(self, t):
2712 self.offset += timedelta(minutes=1)
2713 return self.offset
2714
2715 v = Varies()
2716 t1 = t2.replace(tzinfo=v)
2717 t2 = t2.replace(tzinfo=v)
2718 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2719 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2720 self.assertEqual(t1, t2)
2721
2722 # But if they're not identical, it isn't ignored.
2723 t2 = t2.replace(tzinfo=Varies())
2724 self.assertTrue(t1 < t2) # t1's offset counter still going up
2725
2726 def test_subclass_timetz(self):
2727
2728 class C(self.theclass):
2729 theAnswer = 42
2730
2731 def __new__(cls, *args, **kws):
2732 temp = kws.copy()
2733 extra = temp.pop('extra')
2734 result = self.theclass.__new__(cls, *args, **temp)
2735 result.extra = extra
2736 return result
2737
2738 def newmeth(self, start):
2739 return start + self.hour + self.second
2740
2741 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2742
2743 dt1 = self.theclass(*args)
2744 dt2 = C(*args, **{'extra': 7})
2745
2746 self.assertEqual(dt2.__class__, C)
2747 self.assertEqual(dt2.theAnswer, 42)
2748 self.assertEqual(dt2.extra, 7)
2749 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2750 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2751
2752
2753# Testing datetime objects with a non-None tzinfo.
2754
2755class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
2756 theclass = datetime
2757
2758 def test_trivial(self):
2759 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2760 self.assertEqual(dt.year, 1)
2761 self.assertEqual(dt.month, 2)
2762 self.assertEqual(dt.day, 3)
2763 self.assertEqual(dt.hour, 4)
2764 self.assertEqual(dt.minute, 5)
2765 self.assertEqual(dt.second, 6)
2766 self.assertEqual(dt.microsecond, 7)
2767 self.assertEqual(dt.tzinfo, None)
2768
2769 def test_even_more_compare(self):
2770 # The test_compare() and test_more_compare() inherited from TestDate
2771 # and TestDateTime covered non-tzinfo cases.
2772
2773 # Smallest possible after UTC adjustment.
2774 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2775 # Largest possible after UTC adjustment.
2776 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2777 tzinfo=FixedOffset(-1439, ""))
2778
2779 # Make sure those compare correctly, and w/o overflow.
2780 self.assertTrue(t1 < t2)
2781 self.assertTrue(t1 != t2)
2782 self.assertTrue(t2 > t1)
2783
2784 self.assertEqual(t1, t1)
2785 self.assertEqual(t2, t2)
2786
2787 # Equal afer adjustment.
2788 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2789 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2790 self.assertEqual(t1, t2)
2791
2792 # Change t1 not to subtract a minute, and t1 should be larger.
2793 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2794 self.assertTrue(t1 > t2)
2795
2796 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2797 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2798 self.assertTrue(t1 < t2)
2799
2800 # Back to the original t1, but make seconds resolve it.
2801 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2802 second=1)
2803 self.assertTrue(t1 > t2)
2804
2805 # Likewise, but make microseconds resolve it.
2806 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2807 microsecond=1)
2808 self.assertTrue(t1 > t2)
2809
Alexander Belopolsky08313822012-06-15 20:19:47 -04002810 # Make t2 naive and it should differ.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002811 t2 = self.theclass.min
Alexander Belopolsky08313822012-06-15 20:19:47 -04002812 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002813 self.assertEqual(t2, t2)
2814
2815 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2816 class Naive(tzinfo):
2817 def utcoffset(self, dt): return None
2818 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
Alexander Belopolsky08313822012-06-15 20:19:47 -04002819 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002820 self.assertEqual(t2, t2)
2821
2822 # OTOH, it's OK to compare two of these mixing the two ways of being
2823 # naive.
2824 t1 = self.theclass(5, 6, 7)
2825 self.assertEqual(t1, t2)
2826
2827 # Try a bogus uctoffset.
2828 class Bogus(tzinfo):
2829 def utcoffset(self, dt):
2830 return timedelta(minutes=1440) # out of bounds
2831 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2832 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
2833 self.assertRaises(ValueError, lambda: t1 == t2)
2834
2835 def test_pickling(self):
2836 # Try one without a tzinfo.
2837 args = 6, 7, 23, 20, 59, 1, 64**2
2838 orig = self.theclass(*args)
2839 for pickler, unpickler, proto in pickle_choices:
2840 green = pickler.dumps(orig, proto)
2841 derived = unpickler.loads(green)
2842 self.assertEqual(orig, derived)
2843
2844 # Try one with a tzinfo.
2845 tinfo = PicklableFixedOffset(-300, 'cookie')
2846 orig = self.theclass(*args, **{'tzinfo': tinfo})
2847 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
2848 for pickler, unpickler, proto in pickle_choices:
2849 green = pickler.dumps(orig, proto)
2850 derived = unpickler.loads(green)
2851 self.assertEqual(orig, derived)
2852 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
2853 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2854 self.assertEqual(derived.tzname(), 'cookie')
2855
2856 def test_extreme_hashes(self):
2857 # If an attempt is made to hash these via subtracting the offset
2858 # then hashing a datetime object, OverflowError results. The
2859 # Python implementation used to blow up here.
2860 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2861 hash(t)
2862 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2863 tzinfo=FixedOffset(-1439, ""))
2864 hash(t)
2865
2866 # OTOH, an OOB offset should blow up.
2867 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2868 self.assertRaises(ValueError, hash, t)
2869
2870 def test_zones(self):
2871 est = FixedOffset(-300, "EST")
2872 utc = FixedOffset(0, "UTC")
2873 met = FixedOffset(60, "MET")
2874 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2875 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2876 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
2877 self.assertEqual(t1.tzinfo, est)
2878 self.assertEqual(t2.tzinfo, utc)
2879 self.assertEqual(t3.tzinfo, met)
2880 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2881 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2882 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
2883 self.assertEqual(t1.tzname(), "EST")
2884 self.assertEqual(t2.tzname(), "UTC")
2885 self.assertEqual(t3.tzname(), "MET")
2886 self.assertEqual(hash(t1), hash(t2))
2887 self.assertEqual(hash(t1), hash(t3))
2888 self.assertEqual(hash(t2), hash(t3))
2889 self.assertEqual(t1, t2)
2890 self.assertEqual(t1, t3)
2891 self.assertEqual(t2, t3)
2892 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2893 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2894 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
2895 d = 'datetime.datetime(2002, 3, 19, '
2896 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2897 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2898 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2899
2900 def test_combine(self):
2901 met = FixedOffset(60, "MET")
2902 d = date(2002, 3, 4)
2903 tz = time(18, 45, 3, 1234, tzinfo=met)
2904 dt = datetime.combine(d, tz)
2905 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
2906 tzinfo=met))
2907
2908 def test_extract(self):
2909 met = FixedOffset(60, "MET")
2910 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2911 self.assertEqual(dt.date(), date(2002, 3, 4))
2912 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2913 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
2914
2915 def test_tz_aware_arithmetic(self):
2916 import random
2917
2918 now = self.theclass.now()
2919 tz55 = FixedOffset(-330, "west 5:30")
2920 timeaware = now.time().replace(tzinfo=tz55)
2921 nowaware = self.theclass.combine(now.date(), timeaware)
2922 self.assertTrue(nowaware.tzinfo is tz55)
2923 self.assertEqual(nowaware.timetz(), timeaware)
2924
2925 # Can't mix aware and non-aware.
2926 self.assertRaises(TypeError, lambda: now - nowaware)
2927 self.assertRaises(TypeError, lambda: nowaware - now)
2928
2929 # And adding datetime's doesn't make sense, aware or not.
2930 self.assertRaises(TypeError, lambda: now + nowaware)
2931 self.assertRaises(TypeError, lambda: nowaware + now)
2932 self.assertRaises(TypeError, lambda: nowaware + nowaware)
2933
2934 # Subtracting should yield 0.
2935 self.assertEqual(now - now, timedelta(0))
2936 self.assertEqual(nowaware - nowaware, timedelta(0))
2937
2938 # Adding a delta should preserve tzinfo.
2939 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
2940 nowawareplus = nowaware + delta
2941 self.assertTrue(nowaware.tzinfo is tz55)
2942 nowawareplus2 = delta + nowaware
2943 self.assertTrue(nowawareplus2.tzinfo is tz55)
2944 self.assertEqual(nowawareplus, nowawareplus2)
2945
2946 # that - delta should be what we started with, and that - what we
2947 # started with should be delta.
2948 diff = nowawareplus - delta
2949 self.assertTrue(diff.tzinfo is tz55)
2950 self.assertEqual(nowaware, diff)
2951 self.assertRaises(TypeError, lambda: delta - nowawareplus)
2952 self.assertEqual(nowawareplus - nowaware, delta)
2953
2954 # Make up a random timezone.
2955 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
2956 # Attach it to nowawareplus.
2957 nowawareplus = nowawareplus.replace(tzinfo=tzr)
2958 self.assertTrue(nowawareplus.tzinfo is tzr)
2959 # Make sure the difference takes the timezone adjustments into account.
2960 got = nowaware - nowawareplus
2961 # Expected: (nowaware base - nowaware offset) -
2962 # (nowawareplus base - nowawareplus offset) =
2963 # (nowaware base - nowawareplus base) +
2964 # (nowawareplus offset - nowaware offset) =
2965 # -delta + nowawareplus offset - nowaware offset
2966 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
2967 self.assertEqual(got, expected)
2968
2969 # Try max possible difference.
2970 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
2971 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2972 tzinfo=FixedOffset(-1439, "max"))
2973 maxdiff = max - min
2974 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
2975 timedelta(minutes=2*1439))
2976 # Different tzinfo, but the same offset
2977 tza = timezone(HOUR, 'A')
2978 tzb = timezone(HOUR, 'B')
2979 delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
2980 self.assertEqual(delta, self.theclass.min - self.theclass.max)
2981
2982 def test_tzinfo_now(self):
2983 meth = self.theclass.now
2984 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2985 base = meth()
2986 # Try with and without naming the keyword.
2987 off42 = FixedOffset(42, "42")
2988 another = meth(off42)
2989 again = meth(tz=off42)
2990 self.assertTrue(another.tzinfo is again.tzinfo)
2991 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
2992 # Bad argument with and w/o naming the keyword.
2993 self.assertRaises(TypeError, meth, 16)
2994 self.assertRaises(TypeError, meth, tzinfo=16)
2995 # Bad keyword name.
2996 self.assertRaises(TypeError, meth, tinfo=off42)
2997 # Too many args.
2998 self.assertRaises(TypeError, meth, off42, off42)
2999
3000 # We don't know which time zone we're in, and don't have a tzinfo
3001 # class to represent it, so seeing whether a tz argument actually
3002 # does a conversion is tricky.
3003 utc = FixedOffset(0, "utc", 0)
3004 for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
3005 timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
3006 for dummy in range(3):
3007 now = datetime.now(weirdtz)
3008 self.assertTrue(now.tzinfo is weirdtz)
3009 utcnow = datetime.utcnow().replace(tzinfo=utc)
3010 now2 = utcnow.astimezone(weirdtz)
3011 if abs(now - now2) < timedelta(seconds=30):
3012 break
3013 # Else the code is broken, or more than 30 seconds passed between
3014 # calls; assuming the latter, just try again.
3015 else:
3016 # Three strikes and we're out.
3017 self.fail("utcnow(), now(tz), or astimezone() may be broken")
3018
3019 def test_tzinfo_fromtimestamp(self):
3020 import time
3021 meth = self.theclass.fromtimestamp
3022 ts = time.time()
3023 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3024 base = meth(ts)
3025 # Try with and without naming the keyword.
3026 off42 = FixedOffset(42, "42")
3027 another = meth(ts, off42)
3028 again = meth(ts, tz=off42)
3029 self.assertTrue(another.tzinfo is again.tzinfo)
3030 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3031 # Bad argument with and w/o naming the keyword.
3032 self.assertRaises(TypeError, meth, ts, 16)
3033 self.assertRaises(TypeError, meth, ts, tzinfo=16)
3034 # Bad keyword name.
3035 self.assertRaises(TypeError, meth, ts, tinfo=off42)
3036 # Too many args.
3037 self.assertRaises(TypeError, meth, ts, off42, off42)
3038 # Too few args.
3039 self.assertRaises(TypeError, meth)
3040
3041 # Try to make sure tz= actually does some conversion.
3042 timestamp = 1000000000
3043 utcdatetime = datetime.utcfromtimestamp(timestamp)
3044 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
3045 # But on some flavor of Mac, it's nowhere near that. So we can't have
3046 # any idea here what time that actually is, we can only test that
3047 # relative changes match.
3048 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
3049 tz = FixedOffset(utcoffset, "tz", 0)
3050 expected = utcdatetime + utcoffset
3051 got = datetime.fromtimestamp(timestamp, tz)
3052 self.assertEqual(expected, got.replace(tzinfo=None))
3053
3054 def test_tzinfo_utcnow(self):
3055 meth = self.theclass.utcnow
3056 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3057 base = meth()
3058 # Try with and without naming the keyword; for whatever reason,
3059 # utcnow() doesn't accept a tzinfo argument.
3060 off42 = FixedOffset(42, "42")
3061 self.assertRaises(TypeError, meth, off42)
3062 self.assertRaises(TypeError, meth, tzinfo=off42)
3063
3064 def test_tzinfo_utcfromtimestamp(self):
3065 import time
3066 meth = self.theclass.utcfromtimestamp
3067 ts = time.time()
3068 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3069 base = meth(ts)
3070 # Try with and without naming the keyword; for whatever reason,
3071 # utcfromtimestamp() doesn't accept a tzinfo argument.
3072 off42 = FixedOffset(42, "42")
3073 self.assertRaises(TypeError, meth, ts, off42)
3074 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
3075
3076 def test_tzinfo_timetuple(self):
3077 # TestDateTime tested most of this. datetime adds a twist to the
3078 # DST flag.
3079 class DST(tzinfo):
3080 def __init__(self, dstvalue):
3081 if isinstance(dstvalue, int):
3082 dstvalue = timedelta(minutes=dstvalue)
3083 self.dstvalue = dstvalue
3084 def dst(self, dt):
3085 return self.dstvalue
3086
3087 cls = self.theclass
3088 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
3089 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
3090 t = d.timetuple()
3091 self.assertEqual(1, t.tm_year)
3092 self.assertEqual(1, t.tm_mon)
3093 self.assertEqual(1, t.tm_mday)
3094 self.assertEqual(10, t.tm_hour)
3095 self.assertEqual(20, t.tm_min)
3096 self.assertEqual(30, t.tm_sec)
3097 self.assertEqual(0, t.tm_wday)
3098 self.assertEqual(1, t.tm_yday)
3099 self.assertEqual(flag, t.tm_isdst)
3100
3101 # dst() returns wrong type.
3102 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
3103
3104 # dst() at the edge.
3105 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
3106 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
3107
3108 # dst() out of range.
3109 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
3110 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
3111
3112 def test_utctimetuple(self):
3113 class DST(tzinfo):
3114 def __init__(self, dstvalue=0):
3115 if isinstance(dstvalue, int):
3116 dstvalue = timedelta(minutes=dstvalue)
3117 self.dstvalue = dstvalue
3118 def dst(self, dt):
3119 return self.dstvalue
3120
3121 cls = self.theclass
3122 # This can't work: DST didn't implement utcoffset.
3123 self.assertRaises(NotImplementedError,
3124 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
3125
3126 class UOFS(DST):
3127 def __init__(self, uofs, dofs=None):
3128 DST.__init__(self, dofs)
3129 self.uofs = timedelta(minutes=uofs)
3130 def utcoffset(self, dt):
3131 return self.uofs
3132
3133 for dstvalue in -33, 33, 0, None:
3134 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
3135 t = d.utctimetuple()
3136 self.assertEqual(d.year, t.tm_year)
3137 self.assertEqual(d.month, t.tm_mon)
3138 self.assertEqual(d.day, t.tm_mday)
3139 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
3140 self.assertEqual(13, t.tm_min)
3141 self.assertEqual(d.second, t.tm_sec)
3142 self.assertEqual(d.weekday(), t.tm_wday)
3143 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
3144 t.tm_yday)
3145 # Ensure tm_isdst is 0 regardless of what dst() says: DST
3146 # is never in effect for a UTC time.
3147 self.assertEqual(0, t.tm_isdst)
3148
3149 # For naive datetime, utctimetuple == timetuple except for isdst
3150 d = cls(1, 2, 3, 10, 20, 30, 40)
3151 t = d.utctimetuple()
3152 self.assertEqual(t[:-1], d.timetuple()[:-1])
3153 self.assertEqual(0, t.tm_isdst)
3154 # Same if utcoffset is None
3155 class NOFS(DST):
3156 def utcoffset(self, dt):
3157 return None
3158 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
3159 t = d.utctimetuple()
3160 self.assertEqual(t[:-1], d.timetuple()[:-1])
3161 self.assertEqual(0, t.tm_isdst)
3162 # Check that bad tzinfo is detected
3163 class BOFS(DST):
3164 def utcoffset(self, dt):
3165 return "EST"
3166 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
3167 self.assertRaises(TypeError, d.utctimetuple)
3168
3169 # Check that utctimetuple() is the same as
3170 # astimezone(utc).timetuple()
3171 d = cls(2010, 11, 13, 14, 15, 16, 171819)
3172 for tz in [timezone.min, timezone.utc, timezone.max]:
3173 dtz = d.replace(tzinfo=tz)
3174 self.assertEqual(dtz.utctimetuple()[:-1],
3175 dtz.astimezone(timezone.utc).timetuple()[:-1])
3176 # At the edges, UTC adjustment can produce years out-of-range
3177 # for a datetime object. Ensure that an OverflowError is
3178 # raised.
3179 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
3180 # That goes back 1 minute less than a full day.
3181 self.assertRaises(OverflowError, tiny.utctimetuple)
3182
3183 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
3184 # That goes forward 1 minute less than a full day.
3185 self.assertRaises(OverflowError, huge.utctimetuple)
3186 # More overflow cases
3187 tiny = cls.min.replace(tzinfo=timezone(MINUTE))
3188 self.assertRaises(OverflowError, tiny.utctimetuple)
3189 huge = cls.max.replace(tzinfo=timezone(-MINUTE))
3190 self.assertRaises(OverflowError, huge.utctimetuple)
3191
3192 def test_tzinfo_isoformat(self):
3193 zero = FixedOffset(0, "+00:00")
3194 plus = FixedOffset(220, "+03:40")
3195 minus = FixedOffset(-231, "-03:51")
3196 unknown = FixedOffset(None, "")
3197
3198 cls = self.theclass
3199 datestr = '0001-02-03'
3200 for ofs in None, zero, plus, minus, unknown:
3201 for us in 0, 987001:
3202 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
3203 timestr = '04:05:59' + (us and '.987001' or '')
3204 ofsstr = ofs is not None and d.tzname() or ''
3205 tailstr = timestr + ofsstr
3206 iso = d.isoformat()
3207 self.assertEqual(iso, datestr + 'T' + tailstr)
3208 self.assertEqual(iso, d.isoformat('T'))
3209 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
3210 self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
3211 self.assertEqual(str(d), datestr + ' ' + tailstr)
3212
3213 def test_replace(self):
3214 cls = self.theclass
3215 z100 = FixedOffset(100, "+100")
3216 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3217 args = [1, 2, 3, 4, 5, 6, 7, z100]
3218 base = cls(*args)
3219 self.assertEqual(base, base.replace())
3220
3221 i = 0
3222 for name, newval in (("year", 2),
3223 ("month", 3),
3224 ("day", 4),
3225 ("hour", 5),
3226 ("minute", 6),
3227 ("second", 7),
3228 ("microsecond", 8),
3229 ("tzinfo", zm200)):
3230 newargs = args[:]
3231 newargs[i] = newval
3232 expected = cls(*newargs)
3233 got = base.replace(**{name: newval})
3234 self.assertEqual(expected, got)
3235 i += 1
3236
3237 # Ensure we can get rid of a tzinfo.
3238 self.assertEqual(base.tzname(), "+100")
3239 base2 = base.replace(tzinfo=None)
3240 self.assertTrue(base2.tzinfo is None)
3241 self.assertTrue(base2.tzname() is None)
3242
3243 # Ensure we can add one.
3244 base3 = base2.replace(tzinfo=z100)
3245 self.assertEqual(base, base3)
3246 self.assertTrue(base.tzinfo is base3.tzinfo)
3247
3248 # Out of bounds.
3249 base = cls(2000, 2, 29)
3250 self.assertRaises(ValueError, base.replace, year=2001)
3251
3252 def test_more_astimezone(self):
3253 # The inherited test_astimezone covered some trivial and error cases.
3254 fnone = FixedOffset(None, "None")
3255 f44m = FixedOffset(44, "44")
3256 fm5h = FixedOffset(-timedelta(hours=5), "m300")
3257
3258 dt = self.theclass.now(tz=f44m)
3259 self.assertTrue(dt.tzinfo is f44m)
3260 # Replacing with degenerate tzinfo raises an exception.
3261 self.assertRaises(ValueError, dt.astimezone, fnone)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003262 # Replacing with same tzinfo makes no change.
3263 x = dt.astimezone(dt.tzinfo)
3264 self.assertTrue(x.tzinfo is f44m)
3265 self.assertEqual(x.date(), dt.date())
3266 self.assertEqual(x.time(), dt.time())
3267
3268 # Replacing with different tzinfo does adjust.
3269 got = dt.astimezone(fm5h)
3270 self.assertTrue(got.tzinfo is fm5h)
3271 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
3272 expected = dt - dt.utcoffset() # in effect, convert to UTC
3273 expected += fm5h.utcoffset(dt) # and from there to local time
3274 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
3275 self.assertEqual(got.date(), expected.date())
3276 self.assertEqual(got.time(), expected.time())
3277 self.assertEqual(got.timetz(), expected.timetz())
3278 self.assertTrue(got.tzinfo is expected.tzinfo)
3279 self.assertEqual(got, expected)
3280
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003281 @support.run_with_tz('UTC')
3282 def test_astimezone_default_utc(self):
3283 dt = self.theclass.now(timezone.utc)
3284 self.assertEqual(dt.astimezone(None), dt)
3285 self.assertEqual(dt.astimezone(), dt)
3286
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003287 # Note that offset in TZ variable has the opposite sign to that
3288 # produced by %z directive.
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003289 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
3290 def test_astimezone_default_eastern(self):
3291 dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
3292 local = dt.astimezone()
3293 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003294 self.assertEqual(local.strftime("%z %Z"), "-0500 EST")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003295 dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc)
3296 local = dt.astimezone()
3297 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003298 self.assertEqual(local.strftime("%z %Z"), "-0400 EDT")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003299
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003300 def test_aware_subtract(self):
3301 cls = self.theclass
3302
3303 # Ensure that utcoffset() is ignored when the operands have the
3304 # same tzinfo member.
3305 class OperandDependentOffset(tzinfo):
3306 def utcoffset(self, t):
3307 if t.minute < 10:
3308 # d0 and d1 equal after adjustment
3309 return timedelta(minutes=t.minute)
3310 else:
3311 # d2 off in the weeds
3312 return timedelta(minutes=59)
3313
3314 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
3315 d0 = base.replace(minute=3)
3316 d1 = base.replace(minute=9)
3317 d2 = base.replace(minute=11)
3318 for x in d0, d1, d2:
3319 for y in d0, d1, d2:
3320 got = x - y
3321 expected = timedelta(minutes=x.minute - y.minute)
3322 self.assertEqual(got, expected)
3323
3324 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
3325 # ignored.
3326 base = cls(8, 9, 10, 11, 12, 13, 14)
3327 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
3328 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
3329 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
3330 for x in d0, d1, d2:
3331 for y in d0, d1, d2:
3332 got = x - y
3333 if (x is d0 or x is d1) and (y is d0 or y is d1):
3334 expected = timedelta(0)
3335 elif x is y is d2:
3336 expected = timedelta(0)
3337 elif x is d2:
3338 expected = timedelta(minutes=(11-59)-0)
3339 else:
3340 assert y is d2
3341 expected = timedelta(minutes=0-(11-59))
3342 self.assertEqual(got, expected)
3343
3344 def test_mixed_compare(self):
3345 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
3346 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
3347 self.assertEqual(t1, t2)
3348 t2 = t2.replace(tzinfo=None)
3349 self.assertEqual(t1, t2)
3350 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3351 self.assertEqual(t1, t2)
3352 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04003353 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003354
3355 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
3356 class Varies(tzinfo):
3357 def __init__(self):
3358 self.offset = timedelta(minutes=22)
3359 def utcoffset(self, t):
3360 self.offset += timedelta(minutes=1)
3361 return self.offset
3362
3363 v = Varies()
3364 t1 = t2.replace(tzinfo=v)
3365 t2 = t2.replace(tzinfo=v)
3366 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3367 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3368 self.assertEqual(t1, t2)
3369
3370 # But if they're not identical, it isn't ignored.
3371 t2 = t2.replace(tzinfo=Varies())
3372 self.assertTrue(t1 < t2) # t1's offset counter still going up
3373
3374 def test_subclass_datetimetz(self):
3375
3376 class C(self.theclass):
3377 theAnswer = 42
3378
3379 def __new__(cls, *args, **kws):
3380 temp = kws.copy()
3381 extra = temp.pop('extra')
3382 result = self.theclass.__new__(cls, *args, **temp)
3383 result.extra = extra
3384 return result
3385
3386 def newmeth(self, start):
3387 return start + self.hour + self.year
3388
3389 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3390
3391 dt1 = self.theclass(*args)
3392 dt2 = C(*args, **{'extra': 7})
3393
3394 self.assertEqual(dt2.__class__, C)
3395 self.assertEqual(dt2.theAnswer, 42)
3396 self.assertEqual(dt2.extra, 7)
3397 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3398 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
3399
3400# Pain to set up DST-aware tzinfo classes.
3401
3402def first_sunday_on_or_after(dt):
3403 days_to_go = 6 - dt.weekday()
3404 if days_to_go:
3405 dt += timedelta(days_to_go)
3406 return dt
3407
3408ZERO = timedelta(0)
3409MINUTE = timedelta(minutes=1)
3410HOUR = timedelta(hours=1)
3411DAY = timedelta(days=1)
3412# In the US, DST starts at 2am (standard time) on the first Sunday in April.
3413DSTSTART = datetime(1, 4, 1, 2)
3414# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
3415# which is the first Sunday on or after Oct 25. Because we view 1:MM as
3416# being standard time on that day, there is no spelling in local time of
3417# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
3418DSTEND = datetime(1, 10, 25, 1)
3419
3420class USTimeZone(tzinfo):
3421
3422 def __init__(self, hours, reprname, stdname, dstname):
3423 self.stdoffset = timedelta(hours=hours)
3424 self.reprname = reprname
3425 self.stdname = stdname
3426 self.dstname = dstname
3427
3428 def __repr__(self):
3429 return self.reprname
3430
3431 def tzname(self, dt):
3432 if self.dst(dt):
3433 return self.dstname
3434 else:
3435 return self.stdname
3436
3437 def utcoffset(self, dt):
3438 return self.stdoffset + self.dst(dt)
3439
3440 def dst(self, dt):
3441 if dt is None or dt.tzinfo is None:
3442 # An exception instead may be sensible here, in one or more of
3443 # the cases.
3444 return ZERO
3445 assert dt.tzinfo is self
3446
3447 # Find first Sunday in April.
3448 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
3449 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
3450
3451 # Find last Sunday in October.
3452 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
3453 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
3454
3455 # Can't compare naive to aware objects, so strip the timezone from
3456 # dt first.
3457 if start <= dt.replace(tzinfo=None) < end:
3458 return HOUR
3459 else:
3460 return ZERO
3461
3462Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
3463Central = USTimeZone(-6, "Central", "CST", "CDT")
3464Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
3465Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
3466utc_real = FixedOffset(0, "UTC", 0)
3467# For better test coverage, we want another flavor of UTC that's west of
3468# the Eastern and Pacific timezones.
3469utc_fake = FixedOffset(-12*60, "UTCfake", 0)
3470
3471class TestTimezoneConversions(unittest.TestCase):
3472 # The DST switch times for 2002, in std time.
3473 dston = datetime(2002, 4, 7, 2)
3474 dstoff = datetime(2002, 10, 27, 1)
3475
3476 theclass = datetime
3477
3478 # Check a time that's inside DST.
3479 def checkinside(self, dt, tz, utc, dston, dstoff):
3480 self.assertEqual(dt.dst(), HOUR)
3481
3482 # Conversion to our own timezone is always an identity.
3483 self.assertEqual(dt.astimezone(tz), dt)
3484
3485 asutc = dt.astimezone(utc)
3486 there_and_back = asutc.astimezone(tz)
3487
3488 # Conversion to UTC and back isn't always an identity here,
3489 # because there are redundant spellings (in local time) of
3490 # UTC time when DST begins: the clock jumps from 1:59:59
3491 # to 3:00:00, and a local time of 2:MM:SS doesn't really
3492 # make sense then. The classes above treat 2:MM:SS as
3493 # daylight time then (it's "after 2am"), really an alias
3494 # for 1:MM:SS standard time. The latter form is what
3495 # conversion back from UTC produces.
3496 if dt.date() == dston.date() and dt.hour == 2:
3497 # We're in the redundant hour, and coming back from
3498 # UTC gives the 1:MM:SS standard-time spelling.
3499 self.assertEqual(there_and_back + HOUR, dt)
3500 # Although during was considered to be in daylight
3501 # time, there_and_back is not.
3502 self.assertEqual(there_and_back.dst(), ZERO)
3503 # They're the same times in UTC.
3504 self.assertEqual(there_and_back.astimezone(utc),
3505 dt.astimezone(utc))
3506 else:
3507 # We're not in the redundant hour.
3508 self.assertEqual(dt, there_and_back)
3509
3510 # Because we have a redundant spelling when DST begins, there is
Ezio Melotti3b3499b2011-03-16 11:35:38 +02003511 # (unfortunately) an hour when DST ends that can't be spelled at all in
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003512 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
3513 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
3514 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
3515 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
3516 # expressed in local time. Nevertheless, we want conversion back
3517 # from UTC to mimic the local clock's "repeat an hour" behavior.
3518 nexthour_utc = asutc + HOUR
3519 nexthour_tz = nexthour_utc.astimezone(tz)
3520 if dt.date() == dstoff.date() and dt.hour == 0:
3521 # We're in the hour before the last DST hour. The last DST hour
3522 # is ineffable. We want the conversion back to repeat 1:MM.
3523 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3524 nexthour_utc += HOUR
3525 nexthour_tz = nexthour_utc.astimezone(tz)
3526 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3527 else:
3528 self.assertEqual(nexthour_tz - dt, HOUR)
3529
3530 # Check a time that's outside DST.
3531 def checkoutside(self, dt, tz, utc):
3532 self.assertEqual(dt.dst(), ZERO)
3533
3534 # Conversion to our own timezone is always an identity.
3535 self.assertEqual(dt.astimezone(tz), dt)
3536
3537 # Converting to UTC and back is an identity too.
3538 asutc = dt.astimezone(utc)
3539 there_and_back = asutc.astimezone(tz)
3540 self.assertEqual(dt, there_and_back)
3541
3542 def convert_between_tz_and_utc(self, tz, utc):
3543 dston = self.dston.replace(tzinfo=tz)
3544 # Because 1:MM on the day DST ends is taken as being standard time,
3545 # there is no spelling in tz for the last hour of daylight time.
3546 # For purposes of the test, the last hour of DST is 0:MM, which is
3547 # taken as being daylight time (and 1:MM is taken as being standard
3548 # time).
3549 dstoff = self.dstoff.replace(tzinfo=tz)
3550 for delta in (timedelta(weeks=13),
3551 DAY,
3552 HOUR,
3553 timedelta(minutes=1),
3554 timedelta(microseconds=1)):
3555
3556 self.checkinside(dston, tz, utc, dston, dstoff)
3557 for during in dston + delta, dstoff - delta:
3558 self.checkinside(during, tz, utc, dston, dstoff)
3559
3560 self.checkoutside(dstoff, tz, utc)
3561 for outside in dston - delta, dstoff + delta:
3562 self.checkoutside(outside, tz, utc)
3563
3564 def test_easy(self):
3565 # Despite the name of this test, the endcases are excruciating.
3566 self.convert_between_tz_and_utc(Eastern, utc_real)
3567 self.convert_between_tz_and_utc(Pacific, utc_real)
3568 self.convert_between_tz_and_utc(Eastern, utc_fake)
3569 self.convert_between_tz_and_utc(Pacific, utc_fake)
3570 # The next is really dancing near the edge. It works because
3571 # Pacific and Eastern are far enough apart that their "problem
3572 # hours" don't overlap.
3573 self.convert_between_tz_and_utc(Eastern, Pacific)
3574 self.convert_between_tz_and_utc(Pacific, Eastern)
3575 # OTOH, these fail! Don't enable them. The difficulty is that
3576 # the edge case tests assume that every hour is representable in
3577 # the "utc" class. This is always true for a fixed-offset tzinfo
3578 # class (lke utc_real and utc_fake), but not for Eastern or Central.
3579 # For these adjacent DST-aware time zones, the range of time offsets
3580 # tested ends up creating hours in the one that aren't representable
3581 # in the other. For the same reason, we would see failures in the
3582 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
3583 # offset deltas in convert_between_tz_and_utc().
3584 #
3585 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
3586 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
3587
3588 def test_tricky(self):
3589 # 22:00 on day before daylight starts.
3590 fourback = self.dston - timedelta(hours=4)
3591 ninewest = FixedOffset(-9*60, "-0900", 0)
3592 fourback = fourback.replace(tzinfo=ninewest)
3593 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
3594 # 2", we should get the 3 spelling.
3595 # If we plug 22:00 the day before into Eastern, it "looks like std
3596 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
3597 # to 22:00 lands on 2:00, which makes no sense in local time (the
3598 # local clock jumps from 1 to 3). The point here is to make sure we
3599 # get the 3 spelling.
3600 expected = self.dston.replace(hour=3)
3601 got = fourback.astimezone(Eastern).replace(tzinfo=None)
3602 self.assertEqual(expected, got)
3603
3604 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
3605 # case we want the 1:00 spelling.
3606 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
3607 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
3608 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
3609 # spelling.
3610 expected = self.dston.replace(hour=1)
3611 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
3612 self.assertEqual(expected, got)
3613
3614 # Now on the day DST ends, we want "repeat an hour" behavior.
3615 # UTC 4:MM 5:MM 6:MM 7:MM checking these
3616 # EST 23:MM 0:MM 1:MM 2:MM
3617 # EDT 0:MM 1:MM 2:MM 3:MM
3618 # wall 0:MM 1:MM 1:MM 2:MM against these
3619 for utc in utc_real, utc_fake:
3620 for tz in Eastern, Pacific:
3621 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
3622 # Convert that to UTC.
3623 first_std_hour -= tz.utcoffset(None)
3624 # Adjust for possibly fake UTC.
3625 asutc = first_std_hour + utc.utcoffset(None)
3626 # First UTC hour to convert; this is 4:00 when utc=utc_real &
3627 # tz=Eastern.
3628 asutcbase = asutc.replace(tzinfo=utc)
3629 for tzhour in (0, 1, 1, 2):
3630 expectedbase = self.dstoff.replace(hour=tzhour)
3631 for minute in 0, 30, 59:
3632 expected = expectedbase.replace(minute=minute)
3633 asutc = asutcbase.replace(minute=minute)
3634 astz = asutc.astimezone(tz)
3635 self.assertEqual(astz.replace(tzinfo=None), expected)
3636 asutcbase += HOUR
3637
3638
3639 def test_bogus_dst(self):
3640 class ok(tzinfo):
3641 def utcoffset(self, dt): return HOUR
3642 def dst(self, dt): return HOUR
3643
3644 now = self.theclass.now().replace(tzinfo=utc_real)
3645 # Doesn't blow up.
3646 now.astimezone(ok())
3647
3648 # Does blow up.
3649 class notok(ok):
3650 def dst(self, dt): return None
3651 self.assertRaises(ValueError, now.astimezone, notok())
3652
3653 # Sometimes blow up. In the following, tzinfo.dst()
3654 # implementation may return None or not None depending on
3655 # whether DST is assumed to be in effect. In this situation,
3656 # a ValueError should be raised by astimezone().
3657 class tricky_notok(ok):
3658 def dst(self, dt):
3659 if dt.year == 2000:
3660 return None
3661 else:
3662 return 10*HOUR
3663 dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
3664 self.assertRaises(ValueError, dt.astimezone, tricky_notok())
3665
3666 def test_fromutc(self):
3667 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
3668 now = datetime.utcnow().replace(tzinfo=utc_real)
3669 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3670 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
3671 enow = Eastern.fromutc(now) # doesn't blow up
3672 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
3673 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
3674 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
3675
3676 # Always converts UTC to standard time.
3677 class FauxUSTimeZone(USTimeZone):
3678 def fromutc(self, dt):
3679 return dt + self.stdoffset
3680 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
3681
3682 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
3683 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
3684 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
3685
3686 # Check around DST start.
3687 start = self.dston.replace(hour=4, tzinfo=Eastern)
3688 fstart = start.replace(tzinfo=FEastern)
3689 for wall in 23, 0, 1, 3, 4, 5:
3690 expected = start.replace(hour=wall)
3691 if wall == 23:
3692 expected -= timedelta(days=1)
3693 got = Eastern.fromutc(start)
3694 self.assertEqual(expected, got)
3695
3696 expected = fstart + FEastern.stdoffset
3697 got = FEastern.fromutc(fstart)
3698 self.assertEqual(expected, got)
3699
3700 # Ensure astimezone() calls fromutc() too.
3701 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3702 self.assertEqual(expected, got)
3703
3704 start += HOUR
3705 fstart += HOUR
3706
3707 # Check around DST end.
3708 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
3709 fstart = start.replace(tzinfo=FEastern)
3710 for wall in 0, 1, 1, 2, 3, 4:
3711 expected = start.replace(hour=wall)
3712 got = Eastern.fromutc(start)
3713 self.assertEqual(expected, got)
3714
3715 expected = fstart + FEastern.stdoffset
3716 got = FEastern.fromutc(fstart)
3717 self.assertEqual(expected, got)
3718
3719 # Ensure astimezone() calls fromutc() too.
3720 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3721 self.assertEqual(expected, got)
3722
3723 start += HOUR
3724 fstart += HOUR
3725
3726
3727#############################################################################
3728# oddballs
3729
3730class Oddballs(unittest.TestCase):
3731
3732 def test_bug_1028306(self):
3733 # Trying to compare a date to a datetime should act like a mixed-
3734 # type comparison, despite that datetime is a subclass of date.
3735 as_date = date.today()
3736 as_datetime = datetime.combine(as_date, time())
3737 self.assertTrue(as_date != as_datetime)
3738 self.assertTrue(as_datetime != as_date)
3739 self.assertTrue(not as_date == as_datetime)
3740 self.assertTrue(not as_datetime == as_date)
3741 self.assertRaises(TypeError, lambda: as_date < as_datetime)
3742 self.assertRaises(TypeError, lambda: as_datetime < as_date)
3743 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
3744 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
3745 self.assertRaises(TypeError, lambda: as_date > as_datetime)
3746 self.assertRaises(TypeError, lambda: as_datetime > as_date)
3747 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
3748 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
3749
3750 # Neverthelss, comparison should work with the base-class (date)
3751 # projection if use of a date method is forced.
3752 self.assertEqual(as_date.__eq__(as_datetime), True)
3753 different_day = (as_date.day + 1) % 20 + 1
3754 as_different = as_datetime.replace(day= different_day)
3755 self.assertEqual(as_date.__eq__(as_different), False)
3756
3757 # And date should compare with other subclasses of date. If a
3758 # subclass wants to stop this, it's up to the subclass to do so.
3759 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
3760 self.assertEqual(as_date, date_sc)
3761 self.assertEqual(date_sc, as_date)
3762
3763 # Ditto for datetimes.
3764 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
3765 as_date.day, 0, 0, 0)
3766 self.assertEqual(as_datetime, datetime_sc)
3767 self.assertEqual(datetime_sc, as_datetime)
3768
3769def test_main():
3770 support.run_unittest(__name__)
3771
3772if __name__ == "__main__":
3773 test_main()