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