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