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