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