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