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