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