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