blob: 853806b02c8501696df7116bce0500013da4d2f6 [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)
Victor Stinner8050ca92012-03-14 00:17:05 +01001744 try:
1745 minus_one = fts(-1e-6)
1746 except OSError:
1747 # localtime(-1) and gmtime(-1) is not supported on Windows
1748 pass
1749 else:
1750 self.assertEqual(minus_one.second, 59)
1751 self.assertEqual(minus_one.microsecond, 999999)
Victor Stinner5d272cc2012-03-13 13:35:55 +01001752
Victor Stinner8050ca92012-03-14 00:17:05 +01001753 t = fts(-1e-8)
1754 self.assertEqual(t, minus_one)
1755 t = fts(-9e-7)
1756 self.assertEqual(t, minus_one)
1757 t = fts(-1e-7)
1758 self.assertEqual(t, minus_one)
Victor Stinner5d272cc2012-03-13 13:35:55 +01001759
1760 t = fts(1e-7)
1761 self.assertEqual(t, zero)
1762 t = fts(9e-7)
1763 self.assertEqual(t, zero)
1764 t = fts(0.99999949)
1765 self.assertEqual(t.second, 0)
1766 self.assertEqual(t.microsecond, 999999)
1767 t = fts(0.9999999)
1768 self.assertEqual(t.second, 0)
1769 self.assertEqual(t.microsecond, 999999)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001770
1771 def test_insane_fromtimestamp(self):
1772 # It's possible that some platform maps time_t to double,
1773 # and that this test will fail there. This test should
1774 # exempt such platforms (provided they return reasonable
1775 # results!).
1776 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001777 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001778 insane)
1779
1780 def test_insane_utcfromtimestamp(self):
1781 # It's possible that some platform maps time_t to double,
1782 # and that this test will fail there. This test should
1783 # exempt such platforms (provided they return reasonable
1784 # results!).
1785 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001786 self.assertRaises(OverflowError, self.theclass.utcfromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001787 insane)
1788 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
1789 def test_negative_float_fromtimestamp(self):
1790 # The result is tz-dependent; at least test that this doesn't
1791 # fail (like it did before bug 1646728 was fixed).
1792 self.theclass.fromtimestamp(-1.05)
1793
1794 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
1795 def test_negative_float_utcfromtimestamp(self):
1796 d = self.theclass.utcfromtimestamp(-1.05)
1797 self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
1798
1799 def test_utcnow(self):
1800 import time
1801
1802 # Call it a success if utcnow() and utcfromtimestamp() are within
1803 # a second of each other.
1804 tolerance = timedelta(seconds=1)
1805 for dummy in range(3):
1806 from_now = self.theclass.utcnow()
1807 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1808 if abs(from_timestamp - from_now) <= tolerance:
1809 break
1810 # Else try again a few times.
1811 self.assertTrue(abs(from_timestamp - from_now) <= tolerance)
1812
1813 def test_strptime(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001814 string = '2004-12-01 13:02:47.197'
1815 format = '%Y-%m-%d %H:%M:%S.%f'
1816 expected = _strptime._strptime_datetime(self.theclass, string, format)
1817 got = self.theclass.strptime(string, format)
1818 self.assertEqual(expected, got)
1819 self.assertIs(type(expected), self.theclass)
1820 self.assertIs(type(got), self.theclass)
1821
1822 strptime = self.theclass.strptime
1823 self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
1824 self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
1825 # Only local timezone and UTC are supported
1826 for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
1827 (-_time.timezone, _time.tzname[0])):
1828 if tzseconds < 0:
1829 sign = '-'
1830 seconds = -tzseconds
1831 else:
1832 sign ='+'
1833 seconds = tzseconds
1834 hours, minutes = divmod(seconds//60, 60)
1835 dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
1836 dt = strptime(dtstr, "%z %Z")
1837 self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
1838 self.assertEqual(dt.tzname(), tzname)
1839 # Can produce inconsistent datetime
1840 dtstr, fmt = "+1234 UTC", "%z %Z"
1841 dt = strptime(dtstr, fmt)
1842 self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE)
1843 self.assertEqual(dt.tzname(), 'UTC')
1844 # yet will roundtrip
1845 self.assertEqual(dt.strftime(fmt), dtstr)
1846
1847 # Produce naive datetime if no %z is provided
1848 self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
1849
1850 with self.assertRaises(ValueError): strptime("-2400", "%z")
1851 with self.assertRaises(ValueError): strptime("-000", "%z")
1852
1853 def test_more_timetuple(self):
1854 # This tests fields beyond those tested by the TestDate.test_timetuple.
1855 t = self.theclass(2004, 12, 31, 6, 22, 33)
1856 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
1857 self.assertEqual(t.timetuple(),
1858 (t.year, t.month, t.day,
1859 t.hour, t.minute, t.second,
1860 t.weekday(),
1861 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
1862 -1))
1863 tt = t.timetuple()
1864 self.assertEqual(tt.tm_year, t.year)
1865 self.assertEqual(tt.tm_mon, t.month)
1866 self.assertEqual(tt.tm_mday, t.day)
1867 self.assertEqual(tt.tm_hour, t.hour)
1868 self.assertEqual(tt.tm_min, t.minute)
1869 self.assertEqual(tt.tm_sec, t.second)
1870 self.assertEqual(tt.tm_wday, t.weekday())
1871 self.assertEqual(tt.tm_yday, t.toordinal() -
1872 date(t.year, 1, 1).toordinal() + 1)
1873 self.assertEqual(tt.tm_isdst, -1)
1874
1875 def test_more_strftime(self):
1876 # This tests fields beyond those tested by the TestDate.test_strftime.
1877 t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
1878 self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
1879 "12 31 04 000047 33 22 06 366")
1880
1881 def test_extract(self):
1882 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1883 self.assertEqual(dt.date(), date(2002, 3, 4))
1884 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
1885
1886 def test_combine(self):
1887 d = date(2002, 3, 4)
1888 t = time(18, 45, 3, 1234)
1889 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1890 combine = self.theclass.combine
1891 dt = combine(d, t)
1892 self.assertEqual(dt, expected)
1893
1894 dt = combine(time=t, date=d)
1895 self.assertEqual(dt, expected)
1896
1897 self.assertEqual(d, dt.date())
1898 self.assertEqual(t, dt.time())
1899 self.assertEqual(dt, combine(dt.date(), dt.time()))
1900
1901 self.assertRaises(TypeError, combine) # need an arg
1902 self.assertRaises(TypeError, combine, d) # need two args
1903 self.assertRaises(TypeError, combine, t, d) # args reversed
1904 self.assertRaises(TypeError, combine, d, t, 1) # too many args
1905 self.assertRaises(TypeError, combine, "date", "time") # wrong types
1906 self.assertRaises(TypeError, combine, d, "time") # wrong type
1907 self.assertRaises(TypeError, combine, "date", t) # wrong type
1908
1909 def test_replace(self):
1910 cls = self.theclass
1911 args = [1, 2, 3, 4, 5, 6, 7]
1912 base = cls(*args)
1913 self.assertEqual(base, base.replace())
1914
1915 i = 0
1916 for name, newval in (("year", 2),
1917 ("month", 3),
1918 ("day", 4),
1919 ("hour", 5),
1920 ("minute", 6),
1921 ("second", 7),
1922 ("microsecond", 8)):
1923 newargs = args[:]
1924 newargs[i] = newval
1925 expected = cls(*newargs)
1926 got = base.replace(**{name: newval})
1927 self.assertEqual(expected, got)
1928 i += 1
1929
1930 # Out of bounds.
1931 base = cls(2000, 2, 29)
1932 self.assertRaises(ValueError, base.replace, year=2001)
1933
1934 def test_astimezone(self):
1935 # Pretty boring! The TZ test is more interesting here. astimezone()
1936 # simply can't be applied to a naive object.
1937 dt = self.theclass.now()
1938 f = FixedOffset(44, "")
1939 self.assertRaises(TypeError, dt.astimezone) # not enough args
1940 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
1941 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
1942 self.assertRaises(ValueError, dt.astimezone, f) # naive
1943 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
1944
1945 class Bogus(tzinfo):
1946 def utcoffset(self, dt): return None
1947 def dst(self, dt): return timedelta(0)
1948 bog = Bogus()
1949 self.assertRaises(ValueError, dt.astimezone, bog) # naive
1950 self.assertRaises(ValueError,
1951 dt.replace(tzinfo=bog).astimezone, f)
1952
1953 class AlsoBogus(tzinfo):
1954 def utcoffset(self, dt): return timedelta(0)
1955 def dst(self, dt): return None
1956 alsobog = AlsoBogus()
1957 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
1958
1959 def test_subclass_datetime(self):
1960
1961 class C(self.theclass):
1962 theAnswer = 42
1963
1964 def __new__(cls, *args, **kws):
1965 temp = kws.copy()
1966 extra = temp.pop('extra')
1967 result = self.theclass.__new__(cls, *args, **temp)
1968 result.extra = extra
1969 return result
1970
1971 def newmeth(self, start):
1972 return start + self.year + self.month + self.second
1973
1974 args = 2003, 4, 14, 12, 13, 41
1975
1976 dt1 = self.theclass(*args)
1977 dt2 = C(*args, **{'extra': 7})
1978
1979 self.assertEqual(dt2.__class__, C)
1980 self.assertEqual(dt2.theAnswer, 42)
1981 self.assertEqual(dt2.extra, 7)
1982 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1983 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
1984 dt1.second - 7)
1985
1986class TestSubclassDateTime(TestDateTime):
1987 theclass = SubclassDatetime
1988 # Override tests not designed for subclass
1989 def test_roundtrip(self):
1990 pass
1991
1992class SubclassTime(time):
1993 sub_var = 1
1994
1995class TestTime(HarmlessMixedComparison, unittest.TestCase):
1996
1997 theclass = time
1998
1999 def test_basic_attributes(self):
2000 t = self.theclass(12, 0)
2001 self.assertEqual(t.hour, 12)
2002 self.assertEqual(t.minute, 0)
2003 self.assertEqual(t.second, 0)
2004 self.assertEqual(t.microsecond, 0)
2005
2006 def test_basic_attributes_nonzero(self):
2007 # Make sure all attributes are non-zero so bugs in
2008 # bit-shifting access show up.
2009 t = self.theclass(12, 59, 59, 8000)
2010 self.assertEqual(t.hour, 12)
2011 self.assertEqual(t.minute, 59)
2012 self.assertEqual(t.second, 59)
2013 self.assertEqual(t.microsecond, 8000)
2014
2015 def test_roundtrip(self):
2016 t = self.theclass(1, 2, 3, 4)
2017
2018 # Verify t -> string -> time identity.
2019 s = repr(t)
2020 self.assertTrue(s.startswith('datetime.'))
2021 s = s[9:]
2022 t2 = eval(s)
2023 self.assertEqual(t, t2)
2024
2025 # Verify identity via reconstructing from pieces.
2026 t2 = self.theclass(t.hour, t.minute, t.second,
2027 t.microsecond)
2028 self.assertEqual(t, t2)
2029
2030 def test_comparing(self):
2031 args = [1, 2, 3, 4]
2032 t1 = self.theclass(*args)
2033 t2 = self.theclass(*args)
2034 self.assertEqual(t1, t2)
2035 self.assertTrue(t1 <= t2)
2036 self.assertTrue(t1 >= t2)
2037 self.assertTrue(not t1 != t2)
2038 self.assertTrue(not t1 < t2)
2039 self.assertTrue(not t1 > t2)
2040
2041 for i in range(len(args)):
2042 newargs = args[:]
2043 newargs[i] = args[i] + 1
2044 t2 = self.theclass(*newargs) # this is larger than t1
2045 self.assertTrue(t1 < t2)
2046 self.assertTrue(t2 > t1)
2047 self.assertTrue(t1 <= t2)
2048 self.assertTrue(t2 >= t1)
2049 self.assertTrue(t1 != t2)
2050 self.assertTrue(t2 != t1)
2051 self.assertTrue(not t1 == t2)
2052 self.assertTrue(not t2 == t1)
2053 self.assertTrue(not t1 > t2)
2054 self.assertTrue(not t2 < t1)
2055 self.assertTrue(not t1 >= t2)
2056 self.assertTrue(not t2 <= t1)
2057
2058 for badarg in OTHERSTUFF:
2059 self.assertEqual(t1 == badarg, False)
2060 self.assertEqual(t1 != badarg, True)
2061 self.assertEqual(badarg == t1, False)
2062 self.assertEqual(badarg != t1, True)
2063
2064 self.assertRaises(TypeError, lambda: t1 <= badarg)
2065 self.assertRaises(TypeError, lambda: t1 < badarg)
2066 self.assertRaises(TypeError, lambda: t1 > badarg)
2067 self.assertRaises(TypeError, lambda: t1 >= badarg)
2068 self.assertRaises(TypeError, lambda: badarg <= t1)
2069 self.assertRaises(TypeError, lambda: badarg < t1)
2070 self.assertRaises(TypeError, lambda: badarg > t1)
2071 self.assertRaises(TypeError, lambda: badarg >= t1)
2072
2073 def test_bad_constructor_arguments(self):
2074 # bad hours
2075 self.theclass(0, 0) # no exception
2076 self.theclass(23, 0) # no exception
2077 self.assertRaises(ValueError, self.theclass, -1, 0)
2078 self.assertRaises(ValueError, self.theclass, 24, 0)
2079 # bad minutes
2080 self.theclass(23, 0) # no exception
2081 self.theclass(23, 59) # no exception
2082 self.assertRaises(ValueError, self.theclass, 23, -1)
2083 self.assertRaises(ValueError, self.theclass, 23, 60)
2084 # bad seconds
2085 self.theclass(23, 59, 0) # no exception
2086 self.theclass(23, 59, 59) # no exception
2087 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
2088 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
2089 # bad microseconds
2090 self.theclass(23, 59, 59, 0) # no exception
2091 self.theclass(23, 59, 59, 999999) # no exception
2092 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
2093 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
2094
2095 def test_hash_equality(self):
2096 d = self.theclass(23, 30, 17)
2097 e = self.theclass(23, 30, 17)
2098 self.assertEqual(d, e)
2099 self.assertEqual(hash(d), hash(e))
2100
2101 dic = {d: 1}
2102 dic[e] = 2
2103 self.assertEqual(len(dic), 1)
2104 self.assertEqual(dic[d], 2)
2105 self.assertEqual(dic[e], 2)
2106
2107 d = self.theclass(0, 5, 17)
2108 e = self.theclass(0, 5, 17)
2109 self.assertEqual(d, e)
2110 self.assertEqual(hash(d), hash(e))
2111
2112 dic = {d: 1}
2113 dic[e] = 2
2114 self.assertEqual(len(dic), 1)
2115 self.assertEqual(dic[d], 2)
2116 self.assertEqual(dic[e], 2)
2117
2118 def test_isoformat(self):
2119 t = self.theclass(4, 5, 1, 123)
2120 self.assertEqual(t.isoformat(), "04:05:01.000123")
2121 self.assertEqual(t.isoformat(), str(t))
2122
2123 t = self.theclass()
2124 self.assertEqual(t.isoformat(), "00:00:00")
2125 self.assertEqual(t.isoformat(), str(t))
2126
2127 t = self.theclass(microsecond=1)
2128 self.assertEqual(t.isoformat(), "00:00:00.000001")
2129 self.assertEqual(t.isoformat(), str(t))
2130
2131 t = self.theclass(microsecond=10)
2132 self.assertEqual(t.isoformat(), "00:00:00.000010")
2133 self.assertEqual(t.isoformat(), str(t))
2134
2135 t = self.theclass(microsecond=100)
2136 self.assertEqual(t.isoformat(), "00:00:00.000100")
2137 self.assertEqual(t.isoformat(), str(t))
2138
2139 t = self.theclass(microsecond=1000)
2140 self.assertEqual(t.isoformat(), "00:00:00.001000")
2141 self.assertEqual(t.isoformat(), str(t))
2142
2143 t = self.theclass(microsecond=10000)
2144 self.assertEqual(t.isoformat(), "00:00:00.010000")
2145 self.assertEqual(t.isoformat(), str(t))
2146
2147 t = self.theclass(microsecond=100000)
2148 self.assertEqual(t.isoformat(), "00:00:00.100000")
2149 self.assertEqual(t.isoformat(), str(t))
2150
2151 def test_1653736(self):
2152 # verify it doesn't accept extra keyword arguments
2153 t = self.theclass(second=1)
2154 self.assertRaises(TypeError, t.isoformat, foo=3)
2155
2156 def test_strftime(self):
2157 t = self.theclass(1, 2, 3, 4)
2158 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
2159 # A naive object replaces %z and %Z with empty strings.
2160 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
2161
2162 def test_format(self):
2163 t = self.theclass(1, 2, 3, 4)
2164 self.assertEqual(t.__format__(''), str(t))
2165
2166 # check that a derived class's __str__() gets called
2167 class A(self.theclass):
2168 def __str__(self):
2169 return 'A'
2170 a = A(1, 2, 3, 4)
2171 self.assertEqual(a.__format__(''), 'A')
2172
2173 # check that a derived class's strftime gets called
2174 class B(self.theclass):
2175 def strftime(self, format_spec):
2176 return 'B'
2177 b = B(1, 2, 3, 4)
2178 self.assertEqual(b.__format__(''), str(t))
2179
2180 for fmt in ['%H %M %S',
2181 ]:
2182 self.assertEqual(t.__format__(fmt), t.strftime(fmt))
2183 self.assertEqual(a.__format__(fmt), t.strftime(fmt))
2184 self.assertEqual(b.__format__(fmt), 'B')
2185
2186 def test_str(self):
2187 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
2188 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
2189 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
2190 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
2191 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
2192
2193 def test_repr(self):
2194 name = 'datetime.' + self.theclass.__name__
2195 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
2196 "%s(1, 2, 3, 4)" % name)
2197 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
2198 "%s(10, 2, 3, 4000)" % name)
2199 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
2200 "%s(0, 2, 3, 400000)" % name)
2201 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
2202 "%s(12, 2, 3)" % name)
2203 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
2204 "%s(23, 15)" % name)
2205
2206 def test_resolution_info(self):
2207 self.assertIsInstance(self.theclass.min, self.theclass)
2208 self.assertIsInstance(self.theclass.max, self.theclass)
2209 self.assertIsInstance(self.theclass.resolution, timedelta)
2210 self.assertTrue(self.theclass.max > self.theclass.min)
2211
2212 def test_pickling(self):
2213 args = 20, 59, 16, 64**2
2214 orig = self.theclass(*args)
2215 for pickler, unpickler, proto in pickle_choices:
2216 green = pickler.dumps(orig, proto)
2217 derived = unpickler.loads(green)
2218 self.assertEqual(orig, derived)
2219
2220 def test_pickling_subclass_time(self):
2221 args = 20, 59, 16, 64**2
2222 orig = SubclassTime(*args)
2223 for pickler, unpickler, proto in pickle_choices:
2224 green = pickler.dumps(orig, proto)
2225 derived = unpickler.loads(green)
2226 self.assertEqual(orig, derived)
2227
2228 def test_bool(self):
2229 cls = self.theclass
2230 self.assertTrue(cls(1))
2231 self.assertTrue(cls(0, 1))
2232 self.assertTrue(cls(0, 0, 1))
2233 self.assertTrue(cls(0, 0, 0, 1))
2234 self.assertTrue(not cls(0))
2235 self.assertTrue(not cls())
2236
2237 def test_replace(self):
2238 cls = self.theclass
2239 args = [1, 2, 3, 4]
2240 base = cls(*args)
2241 self.assertEqual(base, base.replace())
2242
2243 i = 0
2244 for name, newval in (("hour", 5),
2245 ("minute", 6),
2246 ("second", 7),
2247 ("microsecond", 8)):
2248 newargs = args[:]
2249 newargs[i] = newval
2250 expected = cls(*newargs)
2251 got = base.replace(**{name: newval})
2252 self.assertEqual(expected, got)
2253 i += 1
2254
2255 # Out of bounds.
2256 base = cls(1)
2257 self.assertRaises(ValueError, base.replace, hour=24)
2258 self.assertRaises(ValueError, base.replace, minute=-1)
2259 self.assertRaises(ValueError, base.replace, second=100)
2260 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2261
2262 def test_subclass_time(self):
2263
2264 class C(self.theclass):
2265 theAnswer = 42
2266
2267 def __new__(cls, *args, **kws):
2268 temp = kws.copy()
2269 extra = temp.pop('extra')
2270 result = self.theclass.__new__(cls, *args, **temp)
2271 result.extra = extra
2272 return result
2273
2274 def newmeth(self, start):
2275 return start + self.hour + self.second
2276
2277 args = 4, 5, 6
2278
2279 dt1 = self.theclass(*args)
2280 dt2 = C(*args, **{'extra': 7})
2281
2282 self.assertEqual(dt2.__class__, C)
2283 self.assertEqual(dt2.theAnswer, 42)
2284 self.assertEqual(dt2.extra, 7)
2285 self.assertEqual(dt1.isoformat(), dt2.isoformat())
2286 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2287
2288 def test_backdoor_resistance(self):
2289 # see TestDate.test_backdoor_resistance().
2290 base = '2:59.0'
2291 for hour_byte in ' ', '9', chr(24), '\xff':
2292 self.assertRaises(TypeError, self.theclass,
2293 hour_byte + base[1:])
2294
2295# A mixin for classes with a tzinfo= argument. Subclasses must define
2296# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
2297# must be legit (which is true for time and datetime).
2298class TZInfoBase:
2299
2300 def test_argument_passing(self):
2301 cls = self.theclass
2302 # A datetime passes itself on, a time passes None.
2303 class introspective(tzinfo):
2304 def tzname(self, dt): return dt and "real" or "none"
2305 def utcoffset(self, dt):
2306 return timedelta(minutes = dt and 42 or -42)
2307 dst = utcoffset
2308
2309 obj = cls(1, 2, 3, tzinfo=introspective())
2310
2311 expected = cls is time and "none" or "real"
2312 self.assertEqual(obj.tzname(), expected)
2313
2314 expected = timedelta(minutes=(cls is time and -42 or 42))
2315 self.assertEqual(obj.utcoffset(), expected)
2316 self.assertEqual(obj.dst(), expected)
2317
2318 def test_bad_tzinfo_classes(self):
2319 cls = self.theclass
2320 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
2321
2322 class NiceTry(object):
2323 def __init__(self): pass
2324 def utcoffset(self, dt): pass
2325 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
2326
2327 class BetterTry(tzinfo):
2328 def __init__(self): pass
2329 def utcoffset(self, dt): pass
2330 b = BetterTry()
2331 t = cls(1, 1, 1, tzinfo=b)
2332 self.assertTrue(t.tzinfo is b)
2333
2334 def test_utc_offset_out_of_bounds(self):
2335 class Edgy(tzinfo):
2336 def __init__(self, offset):
2337 self.offset = timedelta(minutes=offset)
2338 def utcoffset(self, dt):
2339 return self.offset
2340
2341 cls = self.theclass
2342 for offset, legit in ((-1440, False),
2343 (-1439, True),
2344 (1439, True),
2345 (1440, False)):
2346 if cls is time:
2347 t = cls(1, 2, 3, tzinfo=Edgy(offset))
2348 elif cls is datetime:
2349 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
2350 else:
2351 assert 0, "impossible"
2352 if legit:
2353 aofs = abs(offset)
2354 h, m = divmod(aofs, 60)
2355 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
2356 if isinstance(t, datetime):
2357 t = t.timetz()
2358 self.assertEqual(str(t), "01:02:03" + tag)
2359 else:
2360 self.assertRaises(ValueError, str, t)
2361
2362 def test_tzinfo_classes(self):
2363 cls = self.theclass
2364 class C1(tzinfo):
2365 def utcoffset(self, dt): return None
2366 def dst(self, dt): return None
2367 def tzname(self, dt): return None
2368 for t in (cls(1, 1, 1),
2369 cls(1, 1, 1, tzinfo=None),
2370 cls(1, 1, 1, tzinfo=C1())):
2371 self.assertTrue(t.utcoffset() is None)
2372 self.assertTrue(t.dst() is None)
2373 self.assertTrue(t.tzname() is None)
2374
2375 class C3(tzinfo):
2376 def utcoffset(self, dt): return timedelta(minutes=-1439)
2377 def dst(self, dt): return timedelta(minutes=1439)
2378 def tzname(self, dt): return "aname"
2379 t = cls(1, 1, 1, tzinfo=C3())
2380 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
2381 self.assertEqual(t.dst(), timedelta(minutes=1439))
2382 self.assertEqual(t.tzname(), "aname")
2383
2384 # Wrong types.
2385 class C4(tzinfo):
2386 def utcoffset(self, dt): return "aname"
2387 def dst(self, dt): return 7
2388 def tzname(self, dt): return 0
2389 t = cls(1, 1, 1, tzinfo=C4())
2390 self.assertRaises(TypeError, t.utcoffset)
2391 self.assertRaises(TypeError, t.dst)
2392 self.assertRaises(TypeError, t.tzname)
2393
2394 # Offset out of range.
2395 class C6(tzinfo):
2396 def utcoffset(self, dt): return timedelta(hours=-24)
2397 def dst(self, dt): return timedelta(hours=24)
2398 t = cls(1, 1, 1, tzinfo=C6())
2399 self.assertRaises(ValueError, t.utcoffset)
2400 self.assertRaises(ValueError, t.dst)
2401
2402 # Not a whole number of minutes.
2403 class C7(tzinfo):
2404 def utcoffset(self, dt): return timedelta(seconds=61)
2405 def dst(self, dt): return timedelta(microseconds=-81)
2406 t = cls(1, 1, 1, tzinfo=C7())
2407 self.assertRaises(ValueError, t.utcoffset)
2408 self.assertRaises(ValueError, t.dst)
2409
2410 def test_aware_compare(self):
2411 cls = self.theclass
2412
2413 # Ensure that utcoffset() gets ignored if the comparands have
2414 # the same tzinfo member.
2415 class OperandDependentOffset(tzinfo):
2416 def utcoffset(self, t):
2417 if t.minute < 10:
2418 # d0 and d1 equal after adjustment
2419 return timedelta(minutes=t.minute)
2420 else:
2421 # d2 off in the weeds
2422 return timedelta(minutes=59)
2423
2424 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
2425 d0 = base.replace(minute=3)
2426 d1 = base.replace(minute=9)
2427 d2 = base.replace(minute=11)
2428 for x in d0, d1, d2:
2429 for y in d0, d1, d2:
2430 for op in lt, le, gt, ge, eq, ne:
2431 got = op(x, y)
2432 expected = op(x.minute, y.minute)
2433 self.assertEqual(got, expected)
2434
2435 # However, if they're different members, uctoffset is not ignored.
2436 # Note that a time can't actually have an operand-depedent offset,
2437 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
2438 # so skip this test for time.
2439 if cls is not time:
2440 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2441 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2442 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2443 for x in d0, d1, d2:
2444 for y in d0, d1, d2:
2445 got = (x > y) - (x < y)
2446 if (x is d0 or x is d1) and (y is d0 or y is d1):
2447 expected = 0
2448 elif x is y is d2:
2449 expected = 0
2450 elif x is d2:
2451 expected = -1
2452 else:
2453 assert y is d2
2454 expected = 1
2455 self.assertEqual(got, expected)
2456
2457
2458# Testing time objects with a non-None tzinfo.
2459class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
2460 theclass = time
2461
2462 def test_empty(self):
2463 t = self.theclass()
2464 self.assertEqual(t.hour, 0)
2465 self.assertEqual(t.minute, 0)
2466 self.assertEqual(t.second, 0)
2467 self.assertEqual(t.microsecond, 0)
2468 self.assertTrue(t.tzinfo is None)
2469
2470 def test_zones(self):
2471 est = FixedOffset(-300, "EST", 1)
2472 utc = FixedOffset(0, "UTC", -2)
2473 met = FixedOffset(60, "MET", 3)
2474 t1 = time( 7, 47, tzinfo=est)
2475 t2 = time(12, 47, tzinfo=utc)
2476 t3 = time(13, 47, tzinfo=met)
2477 t4 = time(microsecond=40)
2478 t5 = time(microsecond=40, tzinfo=utc)
2479
2480 self.assertEqual(t1.tzinfo, est)
2481 self.assertEqual(t2.tzinfo, utc)
2482 self.assertEqual(t3.tzinfo, met)
2483 self.assertTrue(t4.tzinfo is None)
2484 self.assertEqual(t5.tzinfo, utc)
2485
2486 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2487 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2488 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
2489 self.assertTrue(t4.utcoffset() is None)
2490 self.assertRaises(TypeError, t1.utcoffset, "no args")
2491
2492 self.assertEqual(t1.tzname(), "EST")
2493 self.assertEqual(t2.tzname(), "UTC")
2494 self.assertEqual(t3.tzname(), "MET")
2495 self.assertTrue(t4.tzname() is None)
2496 self.assertRaises(TypeError, t1.tzname, "no args")
2497
2498 self.assertEqual(t1.dst(), timedelta(minutes=1))
2499 self.assertEqual(t2.dst(), timedelta(minutes=-2))
2500 self.assertEqual(t3.dst(), timedelta(minutes=3))
2501 self.assertTrue(t4.dst() is None)
2502 self.assertRaises(TypeError, t1.dst, "no args")
2503
2504 self.assertEqual(hash(t1), hash(t2))
2505 self.assertEqual(hash(t1), hash(t3))
2506 self.assertEqual(hash(t2), hash(t3))
2507
2508 self.assertEqual(t1, t2)
2509 self.assertEqual(t1, t3)
2510 self.assertEqual(t2, t3)
2511 self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
2512 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
2513 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
2514
2515 self.assertEqual(str(t1), "07:47:00-05:00")
2516 self.assertEqual(str(t2), "12:47:00+00:00")
2517 self.assertEqual(str(t3), "13:47:00+01:00")
2518 self.assertEqual(str(t4), "00:00:00.000040")
2519 self.assertEqual(str(t5), "00:00:00.000040+00:00")
2520
2521 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
2522 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
2523 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
2524 self.assertEqual(t4.isoformat(), "00:00:00.000040")
2525 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
2526
2527 d = 'datetime.time'
2528 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
2529 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
2530 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
2531 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
2532 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
2533
2534 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
2535 "07:47:00 %Z=EST %z=-0500")
2536 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
2537 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
2538
2539 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
2540 t1 = time(23, 59, tzinfo=yuck)
2541 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
2542 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
2543
2544 # Check that an invalid tzname result raises an exception.
2545 class Badtzname(tzinfo):
Alexander Belopolskye239d232010-12-08 23:31:48 +00002546 tz = 42
2547 def tzname(self, dt): return self.tz
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002548 t = time(2, 3, 4, tzinfo=Badtzname())
2549 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
2550 self.assertRaises(TypeError, t.strftime, "%Z")
2551
Alexander Belopolskye239d232010-12-08 23:31:48 +00002552 # Issue #6697:
2553 if '_Fast' in str(type(self)):
2554 Badtzname.tz = '\ud800'
2555 self.assertRaises(ValueError, t.strftime, "%Z")
2556
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002557 def test_hash_edge_cases(self):
2558 # Offsets that overflow a basic time.
2559 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2560 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2561 self.assertEqual(hash(t1), hash(t2))
2562
2563 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2564 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2565 self.assertEqual(hash(t1), hash(t2))
2566
2567 def test_pickling(self):
2568 # Try one without a tzinfo.
2569 args = 20, 59, 16, 64**2
2570 orig = self.theclass(*args)
2571 for pickler, unpickler, proto in pickle_choices:
2572 green = pickler.dumps(orig, proto)
2573 derived = unpickler.loads(green)
2574 self.assertEqual(orig, derived)
2575
2576 # Try one with a tzinfo.
2577 tinfo = PicklableFixedOffset(-300, 'cookie')
2578 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
2579 for pickler, unpickler, proto in pickle_choices:
2580 green = pickler.dumps(orig, proto)
2581 derived = unpickler.loads(green)
2582 self.assertEqual(orig, derived)
2583 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
2584 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2585 self.assertEqual(derived.tzname(), 'cookie')
2586
2587 def test_more_bool(self):
2588 # Test cases with non-None tzinfo.
2589 cls = self.theclass
2590
2591 t = cls(0, tzinfo=FixedOffset(-300, ""))
2592 self.assertTrue(t)
2593
2594 t = cls(5, tzinfo=FixedOffset(-300, ""))
2595 self.assertTrue(t)
2596
2597 t = cls(5, tzinfo=FixedOffset(300, ""))
2598 self.assertTrue(not t)
2599
2600 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2601 self.assertTrue(not t)
2602
2603 # Mostly ensuring this doesn't overflow internally.
2604 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
2605 self.assertTrue(t)
2606
2607 # But this should yield a value error -- the utcoffset is bogus.
2608 t = cls(0, tzinfo=FixedOffset(24*60, ""))
2609 self.assertRaises(ValueError, lambda: bool(t))
2610
2611 # Likewise.
2612 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
2613 self.assertRaises(ValueError, lambda: bool(t))
2614
2615 def test_replace(self):
2616 cls = self.theclass
2617 z100 = FixedOffset(100, "+100")
2618 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2619 args = [1, 2, 3, 4, z100]
2620 base = cls(*args)
2621 self.assertEqual(base, base.replace())
2622
2623 i = 0
2624 for name, newval in (("hour", 5),
2625 ("minute", 6),
2626 ("second", 7),
2627 ("microsecond", 8),
2628 ("tzinfo", zm200)):
2629 newargs = args[:]
2630 newargs[i] = newval
2631 expected = cls(*newargs)
2632 got = base.replace(**{name: newval})
2633 self.assertEqual(expected, got)
2634 i += 1
2635
2636 # Ensure we can get rid of a tzinfo.
2637 self.assertEqual(base.tzname(), "+100")
2638 base2 = base.replace(tzinfo=None)
2639 self.assertTrue(base2.tzinfo is None)
2640 self.assertTrue(base2.tzname() is None)
2641
2642 # Ensure we can add one.
2643 base3 = base2.replace(tzinfo=z100)
2644 self.assertEqual(base, base3)
2645 self.assertTrue(base.tzinfo is base3.tzinfo)
2646
2647 # Out of bounds.
2648 base = cls(1)
2649 self.assertRaises(ValueError, base.replace, hour=24)
2650 self.assertRaises(ValueError, base.replace, minute=-1)
2651 self.assertRaises(ValueError, base.replace, second=100)
2652 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2653
2654 def test_mixed_compare(self):
2655 t1 = time(1, 2, 3)
2656 t2 = time(1, 2, 3)
2657 self.assertEqual(t1, t2)
2658 t2 = t2.replace(tzinfo=None)
2659 self.assertEqual(t1, t2)
2660 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2661 self.assertEqual(t1, t2)
2662 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2663 self.assertRaises(TypeError, lambda: t1 == t2)
2664
2665 # In time w/ identical tzinfo objects, utcoffset is ignored.
2666 class Varies(tzinfo):
2667 def __init__(self):
2668 self.offset = timedelta(minutes=22)
2669 def utcoffset(self, t):
2670 self.offset += timedelta(minutes=1)
2671 return self.offset
2672
2673 v = Varies()
2674 t1 = t2.replace(tzinfo=v)
2675 t2 = t2.replace(tzinfo=v)
2676 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2677 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2678 self.assertEqual(t1, t2)
2679
2680 # But if they're not identical, it isn't ignored.
2681 t2 = t2.replace(tzinfo=Varies())
2682 self.assertTrue(t1 < t2) # t1's offset counter still going up
2683
2684 def test_subclass_timetz(self):
2685
2686 class C(self.theclass):
2687 theAnswer = 42
2688
2689 def __new__(cls, *args, **kws):
2690 temp = kws.copy()
2691 extra = temp.pop('extra')
2692 result = self.theclass.__new__(cls, *args, **temp)
2693 result.extra = extra
2694 return result
2695
2696 def newmeth(self, start):
2697 return start + self.hour + self.second
2698
2699 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2700
2701 dt1 = self.theclass(*args)
2702 dt2 = C(*args, **{'extra': 7})
2703
2704 self.assertEqual(dt2.__class__, C)
2705 self.assertEqual(dt2.theAnswer, 42)
2706 self.assertEqual(dt2.extra, 7)
2707 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2708 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2709
2710
2711# Testing datetime objects with a non-None tzinfo.
2712
2713class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
2714 theclass = datetime
2715
2716 def test_trivial(self):
2717 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2718 self.assertEqual(dt.year, 1)
2719 self.assertEqual(dt.month, 2)
2720 self.assertEqual(dt.day, 3)
2721 self.assertEqual(dt.hour, 4)
2722 self.assertEqual(dt.minute, 5)
2723 self.assertEqual(dt.second, 6)
2724 self.assertEqual(dt.microsecond, 7)
2725 self.assertEqual(dt.tzinfo, None)
2726
2727 def test_even_more_compare(self):
2728 # The test_compare() and test_more_compare() inherited from TestDate
2729 # and TestDateTime covered non-tzinfo cases.
2730
2731 # Smallest possible after UTC adjustment.
2732 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2733 # Largest possible after UTC adjustment.
2734 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2735 tzinfo=FixedOffset(-1439, ""))
2736
2737 # Make sure those compare correctly, and w/o overflow.
2738 self.assertTrue(t1 < t2)
2739 self.assertTrue(t1 != t2)
2740 self.assertTrue(t2 > t1)
2741
2742 self.assertEqual(t1, t1)
2743 self.assertEqual(t2, t2)
2744
2745 # Equal afer adjustment.
2746 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2747 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2748 self.assertEqual(t1, t2)
2749
2750 # Change t1 not to subtract a minute, and t1 should be larger.
2751 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2752 self.assertTrue(t1 > t2)
2753
2754 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2755 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2756 self.assertTrue(t1 < t2)
2757
2758 # Back to the original t1, but make seconds resolve it.
2759 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2760 second=1)
2761 self.assertTrue(t1 > t2)
2762
2763 # Likewise, but make microseconds resolve it.
2764 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2765 microsecond=1)
2766 self.assertTrue(t1 > t2)
2767
2768 # Make t2 naive and it should fail.
2769 t2 = self.theclass.min
2770 self.assertRaises(TypeError, lambda: t1 == t2)
2771 self.assertEqual(t2, t2)
2772
2773 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2774 class Naive(tzinfo):
2775 def utcoffset(self, dt): return None
2776 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
2777 self.assertRaises(TypeError, lambda: t1 == t2)
2778 self.assertEqual(t2, t2)
2779
2780 # OTOH, it's OK to compare two of these mixing the two ways of being
2781 # naive.
2782 t1 = self.theclass(5, 6, 7)
2783 self.assertEqual(t1, t2)
2784
2785 # Try a bogus uctoffset.
2786 class Bogus(tzinfo):
2787 def utcoffset(self, dt):
2788 return timedelta(minutes=1440) # out of bounds
2789 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2790 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
2791 self.assertRaises(ValueError, lambda: t1 == t2)
2792
2793 def test_pickling(self):
2794 # Try one without a tzinfo.
2795 args = 6, 7, 23, 20, 59, 1, 64**2
2796 orig = self.theclass(*args)
2797 for pickler, unpickler, proto in pickle_choices:
2798 green = pickler.dumps(orig, proto)
2799 derived = unpickler.loads(green)
2800 self.assertEqual(orig, derived)
2801
2802 # Try one with a tzinfo.
2803 tinfo = PicklableFixedOffset(-300, 'cookie')
2804 orig = self.theclass(*args, **{'tzinfo': tinfo})
2805 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
2806 for pickler, unpickler, proto in pickle_choices:
2807 green = pickler.dumps(orig, proto)
2808 derived = unpickler.loads(green)
2809 self.assertEqual(orig, derived)
2810 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
2811 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2812 self.assertEqual(derived.tzname(), 'cookie')
2813
2814 def test_extreme_hashes(self):
2815 # If an attempt is made to hash these via subtracting the offset
2816 # then hashing a datetime object, OverflowError results. The
2817 # Python implementation used to blow up here.
2818 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2819 hash(t)
2820 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2821 tzinfo=FixedOffset(-1439, ""))
2822 hash(t)
2823
2824 # OTOH, an OOB offset should blow up.
2825 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2826 self.assertRaises(ValueError, hash, t)
2827
2828 def test_zones(self):
2829 est = FixedOffset(-300, "EST")
2830 utc = FixedOffset(0, "UTC")
2831 met = FixedOffset(60, "MET")
2832 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2833 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2834 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
2835 self.assertEqual(t1.tzinfo, est)
2836 self.assertEqual(t2.tzinfo, utc)
2837 self.assertEqual(t3.tzinfo, met)
2838 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2839 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2840 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
2841 self.assertEqual(t1.tzname(), "EST")
2842 self.assertEqual(t2.tzname(), "UTC")
2843 self.assertEqual(t3.tzname(), "MET")
2844 self.assertEqual(hash(t1), hash(t2))
2845 self.assertEqual(hash(t1), hash(t3))
2846 self.assertEqual(hash(t2), hash(t3))
2847 self.assertEqual(t1, t2)
2848 self.assertEqual(t1, t3)
2849 self.assertEqual(t2, t3)
2850 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2851 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2852 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
2853 d = 'datetime.datetime(2002, 3, 19, '
2854 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2855 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2856 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2857
2858 def test_combine(self):
2859 met = FixedOffset(60, "MET")
2860 d = date(2002, 3, 4)
2861 tz = time(18, 45, 3, 1234, tzinfo=met)
2862 dt = datetime.combine(d, tz)
2863 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
2864 tzinfo=met))
2865
2866 def test_extract(self):
2867 met = FixedOffset(60, "MET")
2868 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2869 self.assertEqual(dt.date(), date(2002, 3, 4))
2870 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2871 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
2872
2873 def test_tz_aware_arithmetic(self):
2874 import random
2875
2876 now = self.theclass.now()
2877 tz55 = FixedOffset(-330, "west 5:30")
2878 timeaware = now.time().replace(tzinfo=tz55)
2879 nowaware = self.theclass.combine(now.date(), timeaware)
2880 self.assertTrue(nowaware.tzinfo is tz55)
2881 self.assertEqual(nowaware.timetz(), timeaware)
2882
2883 # Can't mix aware and non-aware.
2884 self.assertRaises(TypeError, lambda: now - nowaware)
2885 self.assertRaises(TypeError, lambda: nowaware - now)
2886
2887 # And adding datetime's doesn't make sense, aware or not.
2888 self.assertRaises(TypeError, lambda: now + nowaware)
2889 self.assertRaises(TypeError, lambda: nowaware + now)
2890 self.assertRaises(TypeError, lambda: nowaware + nowaware)
2891
2892 # Subtracting should yield 0.
2893 self.assertEqual(now - now, timedelta(0))
2894 self.assertEqual(nowaware - nowaware, timedelta(0))
2895
2896 # Adding a delta should preserve tzinfo.
2897 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
2898 nowawareplus = nowaware + delta
2899 self.assertTrue(nowaware.tzinfo is tz55)
2900 nowawareplus2 = delta + nowaware
2901 self.assertTrue(nowawareplus2.tzinfo is tz55)
2902 self.assertEqual(nowawareplus, nowawareplus2)
2903
2904 # that - delta should be what we started with, and that - what we
2905 # started with should be delta.
2906 diff = nowawareplus - delta
2907 self.assertTrue(diff.tzinfo is tz55)
2908 self.assertEqual(nowaware, diff)
2909 self.assertRaises(TypeError, lambda: delta - nowawareplus)
2910 self.assertEqual(nowawareplus - nowaware, delta)
2911
2912 # Make up a random timezone.
2913 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
2914 # Attach it to nowawareplus.
2915 nowawareplus = nowawareplus.replace(tzinfo=tzr)
2916 self.assertTrue(nowawareplus.tzinfo is tzr)
2917 # Make sure the difference takes the timezone adjustments into account.
2918 got = nowaware - nowawareplus
2919 # Expected: (nowaware base - nowaware offset) -
2920 # (nowawareplus base - nowawareplus offset) =
2921 # (nowaware base - nowawareplus base) +
2922 # (nowawareplus offset - nowaware offset) =
2923 # -delta + nowawareplus offset - nowaware offset
2924 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
2925 self.assertEqual(got, expected)
2926
2927 # Try max possible difference.
2928 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
2929 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2930 tzinfo=FixedOffset(-1439, "max"))
2931 maxdiff = max - min
2932 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
2933 timedelta(minutes=2*1439))
2934 # Different tzinfo, but the same offset
2935 tza = timezone(HOUR, 'A')
2936 tzb = timezone(HOUR, 'B')
2937 delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
2938 self.assertEqual(delta, self.theclass.min - self.theclass.max)
2939
2940 def test_tzinfo_now(self):
2941 meth = self.theclass.now
2942 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2943 base = meth()
2944 # Try with and without naming the keyword.
2945 off42 = FixedOffset(42, "42")
2946 another = meth(off42)
2947 again = meth(tz=off42)
2948 self.assertTrue(another.tzinfo is again.tzinfo)
2949 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
2950 # Bad argument with and w/o naming the keyword.
2951 self.assertRaises(TypeError, meth, 16)
2952 self.assertRaises(TypeError, meth, tzinfo=16)
2953 # Bad keyword name.
2954 self.assertRaises(TypeError, meth, tinfo=off42)
2955 # Too many args.
2956 self.assertRaises(TypeError, meth, off42, off42)
2957
2958 # We don't know which time zone we're in, and don't have a tzinfo
2959 # class to represent it, so seeing whether a tz argument actually
2960 # does a conversion is tricky.
2961 utc = FixedOffset(0, "utc", 0)
2962 for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
2963 timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
2964 for dummy in range(3):
2965 now = datetime.now(weirdtz)
2966 self.assertTrue(now.tzinfo is weirdtz)
2967 utcnow = datetime.utcnow().replace(tzinfo=utc)
2968 now2 = utcnow.astimezone(weirdtz)
2969 if abs(now - now2) < timedelta(seconds=30):
2970 break
2971 # Else the code is broken, or more than 30 seconds passed between
2972 # calls; assuming the latter, just try again.
2973 else:
2974 # Three strikes and we're out.
2975 self.fail("utcnow(), now(tz), or astimezone() may be broken")
2976
2977 def test_tzinfo_fromtimestamp(self):
2978 import time
2979 meth = self.theclass.fromtimestamp
2980 ts = time.time()
2981 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2982 base = meth(ts)
2983 # Try with and without naming the keyword.
2984 off42 = FixedOffset(42, "42")
2985 another = meth(ts, off42)
2986 again = meth(ts, tz=off42)
2987 self.assertTrue(another.tzinfo is again.tzinfo)
2988 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
2989 # Bad argument with and w/o naming the keyword.
2990 self.assertRaises(TypeError, meth, ts, 16)
2991 self.assertRaises(TypeError, meth, ts, tzinfo=16)
2992 # Bad keyword name.
2993 self.assertRaises(TypeError, meth, ts, tinfo=off42)
2994 # Too many args.
2995 self.assertRaises(TypeError, meth, ts, off42, off42)
2996 # Too few args.
2997 self.assertRaises(TypeError, meth)
2998
2999 # Try to make sure tz= actually does some conversion.
3000 timestamp = 1000000000
3001 utcdatetime = datetime.utcfromtimestamp(timestamp)
3002 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
3003 # But on some flavor of Mac, it's nowhere near that. So we can't have
3004 # any idea here what time that actually is, we can only test that
3005 # relative changes match.
3006 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
3007 tz = FixedOffset(utcoffset, "tz", 0)
3008 expected = utcdatetime + utcoffset
3009 got = datetime.fromtimestamp(timestamp, tz)
3010 self.assertEqual(expected, got.replace(tzinfo=None))
3011
3012 def test_tzinfo_utcnow(self):
3013 meth = self.theclass.utcnow
3014 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3015 base = meth()
3016 # Try with and without naming the keyword; for whatever reason,
3017 # utcnow() doesn't accept a tzinfo argument.
3018 off42 = FixedOffset(42, "42")
3019 self.assertRaises(TypeError, meth, off42)
3020 self.assertRaises(TypeError, meth, tzinfo=off42)
3021
3022 def test_tzinfo_utcfromtimestamp(self):
3023 import time
3024 meth = self.theclass.utcfromtimestamp
3025 ts = time.time()
3026 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3027 base = meth(ts)
3028 # Try with and without naming the keyword; for whatever reason,
3029 # utcfromtimestamp() doesn't accept a tzinfo argument.
3030 off42 = FixedOffset(42, "42")
3031 self.assertRaises(TypeError, meth, ts, off42)
3032 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
3033
3034 def test_tzinfo_timetuple(self):
3035 # TestDateTime tested most of this. datetime adds a twist to the
3036 # DST flag.
3037 class DST(tzinfo):
3038 def __init__(self, dstvalue):
3039 if isinstance(dstvalue, int):
3040 dstvalue = timedelta(minutes=dstvalue)
3041 self.dstvalue = dstvalue
3042 def dst(self, dt):
3043 return self.dstvalue
3044
3045 cls = self.theclass
3046 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
3047 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
3048 t = d.timetuple()
3049 self.assertEqual(1, t.tm_year)
3050 self.assertEqual(1, t.tm_mon)
3051 self.assertEqual(1, t.tm_mday)
3052 self.assertEqual(10, t.tm_hour)
3053 self.assertEqual(20, t.tm_min)
3054 self.assertEqual(30, t.tm_sec)
3055 self.assertEqual(0, t.tm_wday)
3056 self.assertEqual(1, t.tm_yday)
3057 self.assertEqual(flag, t.tm_isdst)
3058
3059 # dst() returns wrong type.
3060 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
3061
3062 # dst() at the edge.
3063 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
3064 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
3065
3066 # dst() out of range.
3067 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
3068 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
3069
3070 def test_utctimetuple(self):
3071 class DST(tzinfo):
3072 def __init__(self, dstvalue=0):
3073 if isinstance(dstvalue, int):
3074 dstvalue = timedelta(minutes=dstvalue)
3075 self.dstvalue = dstvalue
3076 def dst(self, dt):
3077 return self.dstvalue
3078
3079 cls = self.theclass
3080 # This can't work: DST didn't implement utcoffset.
3081 self.assertRaises(NotImplementedError,
3082 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
3083
3084 class UOFS(DST):
3085 def __init__(self, uofs, dofs=None):
3086 DST.__init__(self, dofs)
3087 self.uofs = timedelta(minutes=uofs)
3088 def utcoffset(self, dt):
3089 return self.uofs
3090
3091 for dstvalue in -33, 33, 0, None:
3092 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
3093 t = d.utctimetuple()
3094 self.assertEqual(d.year, t.tm_year)
3095 self.assertEqual(d.month, t.tm_mon)
3096 self.assertEqual(d.day, t.tm_mday)
3097 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
3098 self.assertEqual(13, t.tm_min)
3099 self.assertEqual(d.second, t.tm_sec)
3100 self.assertEqual(d.weekday(), t.tm_wday)
3101 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
3102 t.tm_yday)
3103 # Ensure tm_isdst is 0 regardless of what dst() says: DST
3104 # is never in effect for a UTC time.
3105 self.assertEqual(0, t.tm_isdst)
3106
3107 # For naive datetime, utctimetuple == timetuple except for isdst
3108 d = cls(1, 2, 3, 10, 20, 30, 40)
3109 t = d.utctimetuple()
3110 self.assertEqual(t[:-1], d.timetuple()[:-1])
3111 self.assertEqual(0, t.tm_isdst)
3112 # Same if utcoffset is None
3113 class NOFS(DST):
3114 def utcoffset(self, dt):
3115 return None
3116 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
3117 t = d.utctimetuple()
3118 self.assertEqual(t[:-1], d.timetuple()[:-1])
3119 self.assertEqual(0, t.tm_isdst)
3120 # Check that bad tzinfo is detected
3121 class BOFS(DST):
3122 def utcoffset(self, dt):
3123 return "EST"
3124 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
3125 self.assertRaises(TypeError, d.utctimetuple)
3126
3127 # Check that utctimetuple() is the same as
3128 # astimezone(utc).timetuple()
3129 d = cls(2010, 11, 13, 14, 15, 16, 171819)
3130 for tz in [timezone.min, timezone.utc, timezone.max]:
3131 dtz = d.replace(tzinfo=tz)
3132 self.assertEqual(dtz.utctimetuple()[:-1],
3133 dtz.astimezone(timezone.utc).timetuple()[:-1])
3134 # At the edges, UTC adjustment can produce years out-of-range
3135 # for a datetime object. Ensure that an OverflowError is
3136 # raised.
3137 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
3138 # That goes back 1 minute less than a full day.
3139 self.assertRaises(OverflowError, tiny.utctimetuple)
3140
3141 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
3142 # That goes forward 1 minute less than a full day.
3143 self.assertRaises(OverflowError, huge.utctimetuple)
3144 # More overflow cases
3145 tiny = cls.min.replace(tzinfo=timezone(MINUTE))
3146 self.assertRaises(OverflowError, tiny.utctimetuple)
3147 huge = cls.max.replace(tzinfo=timezone(-MINUTE))
3148 self.assertRaises(OverflowError, huge.utctimetuple)
3149
3150 def test_tzinfo_isoformat(self):
3151 zero = FixedOffset(0, "+00:00")
3152 plus = FixedOffset(220, "+03:40")
3153 minus = FixedOffset(-231, "-03:51")
3154 unknown = FixedOffset(None, "")
3155
3156 cls = self.theclass
3157 datestr = '0001-02-03'
3158 for ofs in None, zero, plus, minus, unknown:
3159 for us in 0, 987001:
3160 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
3161 timestr = '04:05:59' + (us and '.987001' or '')
3162 ofsstr = ofs is not None and d.tzname() or ''
3163 tailstr = timestr + ofsstr
3164 iso = d.isoformat()
3165 self.assertEqual(iso, datestr + 'T' + tailstr)
3166 self.assertEqual(iso, d.isoformat('T'))
3167 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
3168 self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
3169 self.assertEqual(str(d), datestr + ' ' + tailstr)
3170
3171 def test_replace(self):
3172 cls = self.theclass
3173 z100 = FixedOffset(100, "+100")
3174 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3175 args = [1, 2, 3, 4, 5, 6, 7, z100]
3176 base = cls(*args)
3177 self.assertEqual(base, base.replace())
3178
3179 i = 0
3180 for name, newval in (("year", 2),
3181 ("month", 3),
3182 ("day", 4),
3183 ("hour", 5),
3184 ("minute", 6),
3185 ("second", 7),
3186 ("microsecond", 8),
3187 ("tzinfo", zm200)):
3188 newargs = args[:]
3189 newargs[i] = newval
3190 expected = cls(*newargs)
3191 got = base.replace(**{name: newval})
3192 self.assertEqual(expected, got)
3193 i += 1
3194
3195 # Ensure we can get rid of a tzinfo.
3196 self.assertEqual(base.tzname(), "+100")
3197 base2 = base.replace(tzinfo=None)
3198 self.assertTrue(base2.tzinfo is None)
3199 self.assertTrue(base2.tzname() is None)
3200
3201 # Ensure we can add one.
3202 base3 = base2.replace(tzinfo=z100)
3203 self.assertEqual(base, base3)
3204 self.assertTrue(base.tzinfo is base3.tzinfo)
3205
3206 # Out of bounds.
3207 base = cls(2000, 2, 29)
3208 self.assertRaises(ValueError, base.replace, year=2001)
3209
3210 def test_more_astimezone(self):
3211 # The inherited test_astimezone covered some trivial and error cases.
3212 fnone = FixedOffset(None, "None")
3213 f44m = FixedOffset(44, "44")
3214 fm5h = FixedOffset(-timedelta(hours=5), "m300")
3215
3216 dt = self.theclass.now(tz=f44m)
3217 self.assertTrue(dt.tzinfo is f44m)
3218 # Replacing with degenerate tzinfo raises an exception.
3219 self.assertRaises(ValueError, dt.astimezone, fnone)
3220 # Ditto with None tz.
3221 self.assertRaises(TypeError, dt.astimezone, None)
3222 # Replacing with same tzinfo makes no change.
3223 x = dt.astimezone(dt.tzinfo)
3224 self.assertTrue(x.tzinfo is f44m)
3225 self.assertEqual(x.date(), dt.date())
3226 self.assertEqual(x.time(), dt.time())
3227
3228 # Replacing with different tzinfo does adjust.
3229 got = dt.astimezone(fm5h)
3230 self.assertTrue(got.tzinfo is fm5h)
3231 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
3232 expected = dt - dt.utcoffset() # in effect, convert to UTC
3233 expected += fm5h.utcoffset(dt) # and from there to local time
3234 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
3235 self.assertEqual(got.date(), expected.date())
3236 self.assertEqual(got.time(), expected.time())
3237 self.assertEqual(got.timetz(), expected.timetz())
3238 self.assertTrue(got.tzinfo is expected.tzinfo)
3239 self.assertEqual(got, expected)
3240
3241 def test_aware_subtract(self):
3242 cls = self.theclass
3243
3244 # Ensure that utcoffset() is ignored when the operands have the
3245 # same tzinfo member.
3246 class OperandDependentOffset(tzinfo):
3247 def utcoffset(self, t):
3248 if t.minute < 10:
3249 # d0 and d1 equal after adjustment
3250 return timedelta(minutes=t.minute)
3251 else:
3252 # d2 off in the weeds
3253 return timedelta(minutes=59)
3254
3255 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
3256 d0 = base.replace(minute=3)
3257 d1 = base.replace(minute=9)
3258 d2 = base.replace(minute=11)
3259 for x in d0, d1, d2:
3260 for y in d0, d1, d2:
3261 got = x - y
3262 expected = timedelta(minutes=x.minute - y.minute)
3263 self.assertEqual(got, expected)
3264
3265 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
3266 # ignored.
3267 base = cls(8, 9, 10, 11, 12, 13, 14)
3268 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
3269 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
3270 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
3271 for x in d0, d1, d2:
3272 for y in d0, d1, d2:
3273 got = x - y
3274 if (x is d0 or x is d1) and (y is d0 or y is d1):
3275 expected = timedelta(0)
3276 elif x is y is d2:
3277 expected = timedelta(0)
3278 elif x is d2:
3279 expected = timedelta(minutes=(11-59)-0)
3280 else:
3281 assert y is d2
3282 expected = timedelta(minutes=0-(11-59))
3283 self.assertEqual(got, expected)
3284
3285 def test_mixed_compare(self):
3286 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
3287 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
3288 self.assertEqual(t1, t2)
3289 t2 = t2.replace(tzinfo=None)
3290 self.assertEqual(t1, t2)
3291 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3292 self.assertEqual(t1, t2)
3293 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
3294 self.assertRaises(TypeError, lambda: t1 == t2)
3295
3296 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
3297 class Varies(tzinfo):
3298 def __init__(self):
3299 self.offset = timedelta(minutes=22)
3300 def utcoffset(self, t):
3301 self.offset += timedelta(minutes=1)
3302 return self.offset
3303
3304 v = Varies()
3305 t1 = t2.replace(tzinfo=v)
3306 t2 = t2.replace(tzinfo=v)
3307 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3308 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3309 self.assertEqual(t1, t2)
3310
3311 # But if they're not identical, it isn't ignored.
3312 t2 = t2.replace(tzinfo=Varies())
3313 self.assertTrue(t1 < t2) # t1's offset counter still going up
3314
3315 def test_subclass_datetimetz(self):
3316
3317 class C(self.theclass):
3318 theAnswer = 42
3319
3320 def __new__(cls, *args, **kws):
3321 temp = kws.copy()
3322 extra = temp.pop('extra')
3323 result = self.theclass.__new__(cls, *args, **temp)
3324 result.extra = extra
3325 return result
3326
3327 def newmeth(self, start):
3328 return start + self.hour + self.year
3329
3330 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3331
3332 dt1 = self.theclass(*args)
3333 dt2 = C(*args, **{'extra': 7})
3334
3335 self.assertEqual(dt2.__class__, C)
3336 self.assertEqual(dt2.theAnswer, 42)
3337 self.assertEqual(dt2.extra, 7)
3338 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3339 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
3340
3341# Pain to set up DST-aware tzinfo classes.
3342
3343def first_sunday_on_or_after(dt):
3344 days_to_go = 6 - dt.weekday()
3345 if days_to_go:
3346 dt += timedelta(days_to_go)
3347 return dt
3348
3349ZERO = timedelta(0)
3350MINUTE = timedelta(minutes=1)
3351HOUR = timedelta(hours=1)
3352DAY = timedelta(days=1)
3353# In the US, DST starts at 2am (standard time) on the first Sunday in April.
3354DSTSTART = datetime(1, 4, 1, 2)
3355# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
3356# which is the first Sunday on or after Oct 25. Because we view 1:MM as
3357# being standard time on that day, there is no spelling in local time of
3358# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
3359DSTEND = datetime(1, 10, 25, 1)
3360
3361class USTimeZone(tzinfo):
3362
3363 def __init__(self, hours, reprname, stdname, dstname):
3364 self.stdoffset = timedelta(hours=hours)
3365 self.reprname = reprname
3366 self.stdname = stdname
3367 self.dstname = dstname
3368
3369 def __repr__(self):
3370 return self.reprname
3371
3372 def tzname(self, dt):
3373 if self.dst(dt):
3374 return self.dstname
3375 else:
3376 return self.stdname
3377
3378 def utcoffset(self, dt):
3379 return self.stdoffset + self.dst(dt)
3380
3381 def dst(self, dt):
3382 if dt is None or dt.tzinfo is None:
3383 # An exception instead may be sensible here, in one or more of
3384 # the cases.
3385 return ZERO
3386 assert dt.tzinfo is self
3387
3388 # Find first Sunday in April.
3389 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
3390 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
3391
3392 # Find last Sunday in October.
3393 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
3394 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
3395
3396 # Can't compare naive to aware objects, so strip the timezone from
3397 # dt first.
3398 if start <= dt.replace(tzinfo=None) < end:
3399 return HOUR
3400 else:
3401 return ZERO
3402
3403Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
3404Central = USTimeZone(-6, "Central", "CST", "CDT")
3405Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
3406Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
3407utc_real = FixedOffset(0, "UTC", 0)
3408# For better test coverage, we want another flavor of UTC that's west of
3409# the Eastern and Pacific timezones.
3410utc_fake = FixedOffset(-12*60, "UTCfake", 0)
3411
3412class TestTimezoneConversions(unittest.TestCase):
3413 # The DST switch times for 2002, in std time.
3414 dston = datetime(2002, 4, 7, 2)
3415 dstoff = datetime(2002, 10, 27, 1)
3416
3417 theclass = datetime
3418
3419 # Check a time that's inside DST.
3420 def checkinside(self, dt, tz, utc, dston, dstoff):
3421 self.assertEqual(dt.dst(), HOUR)
3422
3423 # Conversion to our own timezone is always an identity.
3424 self.assertEqual(dt.astimezone(tz), dt)
3425
3426 asutc = dt.astimezone(utc)
3427 there_and_back = asutc.astimezone(tz)
3428
3429 # Conversion to UTC and back isn't always an identity here,
3430 # because there are redundant spellings (in local time) of
3431 # UTC time when DST begins: the clock jumps from 1:59:59
3432 # to 3:00:00, and a local time of 2:MM:SS doesn't really
3433 # make sense then. The classes above treat 2:MM:SS as
3434 # daylight time then (it's "after 2am"), really an alias
3435 # for 1:MM:SS standard time. The latter form is what
3436 # conversion back from UTC produces.
3437 if dt.date() == dston.date() and dt.hour == 2:
3438 # We're in the redundant hour, and coming back from
3439 # UTC gives the 1:MM:SS standard-time spelling.
3440 self.assertEqual(there_and_back + HOUR, dt)
3441 # Although during was considered to be in daylight
3442 # time, there_and_back is not.
3443 self.assertEqual(there_and_back.dst(), ZERO)
3444 # They're the same times in UTC.
3445 self.assertEqual(there_and_back.astimezone(utc),
3446 dt.astimezone(utc))
3447 else:
3448 # We're not in the redundant hour.
3449 self.assertEqual(dt, there_and_back)
3450
3451 # Because we have a redundant spelling when DST begins, there is
Ezio Melotti3b3499b2011-03-16 11:35:38 +02003452 # (unfortunately) an hour when DST ends that can't be spelled at all in
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003453 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
3454 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
3455 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
3456 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
3457 # expressed in local time. Nevertheless, we want conversion back
3458 # from UTC to mimic the local clock's "repeat an hour" behavior.
3459 nexthour_utc = asutc + HOUR
3460 nexthour_tz = nexthour_utc.astimezone(tz)
3461 if dt.date() == dstoff.date() and dt.hour == 0:
3462 # We're in the hour before the last DST hour. The last DST hour
3463 # is ineffable. We want the conversion back to repeat 1:MM.
3464 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3465 nexthour_utc += HOUR
3466 nexthour_tz = nexthour_utc.astimezone(tz)
3467 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3468 else:
3469 self.assertEqual(nexthour_tz - dt, HOUR)
3470
3471 # Check a time that's outside DST.
3472 def checkoutside(self, dt, tz, utc):
3473 self.assertEqual(dt.dst(), ZERO)
3474
3475 # Conversion to our own timezone is always an identity.
3476 self.assertEqual(dt.astimezone(tz), dt)
3477
3478 # Converting to UTC and back is an identity too.
3479 asutc = dt.astimezone(utc)
3480 there_and_back = asutc.astimezone(tz)
3481 self.assertEqual(dt, there_and_back)
3482
3483 def convert_between_tz_and_utc(self, tz, utc):
3484 dston = self.dston.replace(tzinfo=tz)
3485 # Because 1:MM on the day DST ends is taken as being standard time,
3486 # there is no spelling in tz for the last hour of daylight time.
3487 # For purposes of the test, the last hour of DST is 0:MM, which is
3488 # taken as being daylight time (and 1:MM is taken as being standard
3489 # time).
3490 dstoff = self.dstoff.replace(tzinfo=tz)
3491 for delta in (timedelta(weeks=13),
3492 DAY,
3493 HOUR,
3494 timedelta(minutes=1),
3495 timedelta(microseconds=1)):
3496
3497 self.checkinside(dston, tz, utc, dston, dstoff)
3498 for during in dston + delta, dstoff - delta:
3499 self.checkinside(during, tz, utc, dston, dstoff)
3500
3501 self.checkoutside(dstoff, tz, utc)
3502 for outside in dston - delta, dstoff + delta:
3503 self.checkoutside(outside, tz, utc)
3504
3505 def test_easy(self):
3506 # Despite the name of this test, the endcases are excruciating.
3507 self.convert_between_tz_and_utc(Eastern, utc_real)
3508 self.convert_between_tz_and_utc(Pacific, utc_real)
3509 self.convert_between_tz_and_utc(Eastern, utc_fake)
3510 self.convert_between_tz_and_utc(Pacific, utc_fake)
3511 # The next is really dancing near the edge. It works because
3512 # Pacific and Eastern are far enough apart that their "problem
3513 # hours" don't overlap.
3514 self.convert_between_tz_and_utc(Eastern, Pacific)
3515 self.convert_between_tz_and_utc(Pacific, Eastern)
3516 # OTOH, these fail! Don't enable them. The difficulty is that
3517 # the edge case tests assume that every hour is representable in
3518 # the "utc" class. This is always true for a fixed-offset tzinfo
3519 # class (lke utc_real and utc_fake), but not for Eastern or Central.
3520 # For these adjacent DST-aware time zones, the range of time offsets
3521 # tested ends up creating hours in the one that aren't representable
3522 # in the other. For the same reason, we would see failures in the
3523 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
3524 # offset deltas in convert_between_tz_and_utc().
3525 #
3526 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
3527 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
3528
3529 def test_tricky(self):
3530 # 22:00 on day before daylight starts.
3531 fourback = self.dston - timedelta(hours=4)
3532 ninewest = FixedOffset(-9*60, "-0900", 0)
3533 fourback = fourback.replace(tzinfo=ninewest)
3534 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
3535 # 2", we should get the 3 spelling.
3536 # If we plug 22:00 the day before into Eastern, it "looks like std
3537 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
3538 # to 22:00 lands on 2:00, which makes no sense in local time (the
3539 # local clock jumps from 1 to 3). The point here is to make sure we
3540 # get the 3 spelling.
3541 expected = self.dston.replace(hour=3)
3542 got = fourback.astimezone(Eastern).replace(tzinfo=None)
3543 self.assertEqual(expected, got)
3544
3545 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
3546 # case we want the 1:00 spelling.
3547 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
3548 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
3549 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
3550 # spelling.
3551 expected = self.dston.replace(hour=1)
3552 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
3553 self.assertEqual(expected, got)
3554
3555 # Now on the day DST ends, we want "repeat an hour" behavior.
3556 # UTC 4:MM 5:MM 6:MM 7:MM checking these
3557 # EST 23:MM 0:MM 1:MM 2:MM
3558 # EDT 0:MM 1:MM 2:MM 3:MM
3559 # wall 0:MM 1:MM 1:MM 2:MM against these
3560 for utc in utc_real, utc_fake:
3561 for tz in Eastern, Pacific:
3562 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
3563 # Convert that to UTC.
3564 first_std_hour -= tz.utcoffset(None)
3565 # Adjust for possibly fake UTC.
3566 asutc = first_std_hour + utc.utcoffset(None)
3567 # First UTC hour to convert; this is 4:00 when utc=utc_real &
3568 # tz=Eastern.
3569 asutcbase = asutc.replace(tzinfo=utc)
3570 for tzhour in (0, 1, 1, 2):
3571 expectedbase = self.dstoff.replace(hour=tzhour)
3572 for minute in 0, 30, 59:
3573 expected = expectedbase.replace(minute=minute)
3574 asutc = asutcbase.replace(minute=minute)
3575 astz = asutc.astimezone(tz)
3576 self.assertEqual(astz.replace(tzinfo=None), expected)
3577 asutcbase += HOUR
3578
3579
3580 def test_bogus_dst(self):
3581 class ok(tzinfo):
3582 def utcoffset(self, dt): return HOUR
3583 def dst(self, dt): return HOUR
3584
3585 now = self.theclass.now().replace(tzinfo=utc_real)
3586 # Doesn't blow up.
3587 now.astimezone(ok())
3588
3589 # Does blow up.
3590 class notok(ok):
3591 def dst(self, dt): return None
3592 self.assertRaises(ValueError, now.astimezone, notok())
3593
3594 # Sometimes blow up. In the following, tzinfo.dst()
3595 # implementation may return None or not None depending on
3596 # whether DST is assumed to be in effect. In this situation,
3597 # a ValueError should be raised by astimezone().
3598 class tricky_notok(ok):
3599 def dst(self, dt):
3600 if dt.year == 2000:
3601 return None
3602 else:
3603 return 10*HOUR
3604 dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
3605 self.assertRaises(ValueError, dt.astimezone, tricky_notok())
3606
3607 def test_fromutc(self):
3608 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
3609 now = datetime.utcnow().replace(tzinfo=utc_real)
3610 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3611 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
3612 enow = Eastern.fromutc(now) # doesn't blow up
3613 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
3614 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
3615 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
3616
3617 # Always converts UTC to standard time.
3618 class FauxUSTimeZone(USTimeZone):
3619 def fromutc(self, dt):
3620 return dt + self.stdoffset
3621 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
3622
3623 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
3624 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
3625 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
3626
3627 # Check around DST start.
3628 start = self.dston.replace(hour=4, tzinfo=Eastern)
3629 fstart = start.replace(tzinfo=FEastern)
3630 for wall in 23, 0, 1, 3, 4, 5:
3631 expected = start.replace(hour=wall)
3632 if wall == 23:
3633 expected -= timedelta(days=1)
3634 got = Eastern.fromutc(start)
3635 self.assertEqual(expected, got)
3636
3637 expected = fstart + FEastern.stdoffset
3638 got = FEastern.fromutc(fstart)
3639 self.assertEqual(expected, got)
3640
3641 # Ensure astimezone() calls fromutc() too.
3642 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3643 self.assertEqual(expected, got)
3644
3645 start += HOUR
3646 fstart += HOUR
3647
3648 # Check around DST end.
3649 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
3650 fstart = start.replace(tzinfo=FEastern)
3651 for wall in 0, 1, 1, 2, 3, 4:
3652 expected = start.replace(hour=wall)
3653 got = Eastern.fromutc(start)
3654 self.assertEqual(expected, got)
3655
3656 expected = fstart + FEastern.stdoffset
3657 got = FEastern.fromutc(fstart)
3658 self.assertEqual(expected, got)
3659
3660 # Ensure astimezone() calls fromutc() too.
3661 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3662 self.assertEqual(expected, got)
3663
3664 start += HOUR
3665 fstart += HOUR
3666
3667
3668#############################################################################
3669# oddballs
3670
3671class Oddballs(unittest.TestCase):
3672
3673 def test_bug_1028306(self):
3674 # Trying to compare a date to a datetime should act like a mixed-
3675 # type comparison, despite that datetime is a subclass of date.
3676 as_date = date.today()
3677 as_datetime = datetime.combine(as_date, time())
3678 self.assertTrue(as_date != as_datetime)
3679 self.assertTrue(as_datetime != as_date)
3680 self.assertTrue(not as_date == as_datetime)
3681 self.assertTrue(not as_datetime == as_date)
3682 self.assertRaises(TypeError, lambda: as_date < as_datetime)
3683 self.assertRaises(TypeError, lambda: as_datetime < as_date)
3684 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
3685 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
3686 self.assertRaises(TypeError, lambda: as_date > as_datetime)
3687 self.assertRaises(TypeError, lambda: as_datetime > as_date)
3688 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
3689 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
3690
3691 # Neverthelss, comparison should work with the base-class (date)
3692 # projection if use of a date method is forced.
3693 self.assertEqual(as_date.__eq__(as_datetime), True)
3694 different_day = (as_date.day + 1) % 20 + 1
3695 as_different = as_datetime.replace(day= different_day)
3696 self.assertEqual(as_date.__eq__(as_different), False)
3697
3698 # And date should compare with other subclasses of date. If a
3699 # subclass wants to stop this, it's up to the subclass to do so.
3700 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
3701 self.assertEqual(as_date, date_sc)
3702 self.assertEqual(date_sc, as_date)
3703
3704 # Ditto for datetimes.
3705 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
3706 as_date.day, 0, 0, 0)
3707 self.assertEqual(as_datetime, datetime_sc)
3708 self.assertEqual(datetime_sc, as_datetime)
3709
3710def test_main():
3711 support.run_unittest(__name__)
3712
3713if __name__ == "__main__":
3714 test_main()