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