blob: 8be72a4c7df3861eb28068afbb28c8b42d2def3d [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
1731 # than one microsecond smaller than an integer.
1732 self.assertEqual(self.theclass.fromtimestamp(0.9999999),
1733 self.theclass.fromtimestamp(1))
1734
1735 def test_insane_fromtimestamp(self):
1736 # It's possible that some platform maps time_t to double,
1737 # and that this test will fail there. This test should
1738 # exempt such platforms (provided they return reasonable
1739 # results!).
1740 for insane in -1e200, 1e200:
1741 self.assertRaises(ValueError, self.theclass.fromtimestamp,
1742 insane)
1743
1744 def test_insane_utcfromtimestamp(self):
1745 # It's possible that some platform maps time_t to double,
1746 # and that this test will fail there. This test should
1747 # exempt such platforms (provided they return reasonable
1748 # results!).
1749 for insane in -1e200, 1e200:
1750 self.assertRaises(ValueError, self.theclass.utcfromtimestamp,
1751 insane)
1752 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
1753 def test_negative_float_fromtimestamp(self):
1754 # The result is tz-dependent; at least test that this doesn't
1755 # fail (like it did before bug 1646728 was fixed).
1756 self.theclass.fromtimestamp(-1.05)
1757
1758 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
1759 def test_negative_float_utcfromtimestamp(self):
1760 d = self.theclass.utcfromtimestamp(-1.05)
1761 self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
1762
1763 def test_utcnow(self):
1764 import time
1765
1766 # Call it a success if utcnow() and utcfromtimestamp() are within
1767 # a second of each other.
1768 tolerance = timedelta(seconds=1)
1769 for dummy in range(3):
1770 from_now = self.theclass.utcnow()
1771 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1772 if abs(from_timestamp - from_now) <= tolerance:
1773 break
1774 # Else try again a few times.
1775 self.assertTrue(abs(from_timestamp - from_now) <= tolerance)
1776
1777 def test_strptime(self):
1778 import _strptime
1779
1780 string = '2004-12-01 13:02:47.197'
1781 format = '%Y-%m-%d %H:%M:%S.%f'
1782 expected = _strptime._strptime_datetime(self.theclass, string, format)
1783 got = self.theclass.strptime(string, format)
1784 self.assertEqual(expected, got)
1785 self.assertIs(type(expected), self.theclass)
1786 self.assertIs(type(got), self.theclass)
1787
1788 strptime = self.theclass.strptime
1789 self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
1790 self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
1791 # Only local timezone and UTC are supported
1792 for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
1793 (-_time.timezone, _time.tzname[0])):
1794 if tzseconds < 0:
1795 sign = '-'
1796 seconds = -tzseconds
1797 else:
1798 sign ='+'
1799 seconds = tzseconds
1800 hours, minutes = divmod(seconds//60, 60)
1801 dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
1802 dt = strptime(dtstr, "%z %Z")
1803 self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
1804 self.assertEqual(dt.tzname(), tzname)
1805 # Can produce inconsistent datetime
1806 dtstr, fmt = "+1234 UTC", "%z %Z"
1807 dt = strptime(dtstr, fmt)
1808 self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE)
1809 self.assertEqual(dt.tzname(), 'UTC')
1810 # yet will roundtrip
1811 self.assertEqual(dt.strftime(fmt), dtstr)
1812
1813 # Produce naive datetime if no %z is provided
1814 self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
1815
1816 with self.assertRaises(ValueError): strptime("-2400", "%z")
1817 with self.assertRaises(ValueError): strptime("-000", "%z")
1818
1819 def test_more_timetuple(self):
1820 # This tests fields beyond those tested by the TestDate.test_timetuple.
1821 t = self.theclass(2004, 12, 31, 6, 22, 33)
1822 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
1823 self.assertEqual(t.timetuple(),
1824 (t.year, t.month, t.day,
1825 t.hour, t.minute, t.second,
1826 t.weekday(),
1827 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
1828 -1))
1829 tt = t.timetuple()
1830 self.assertEqual(tt.tm_year, t.year)
1831 self.assertEqual(tt.tm_mon, t.month)
1832 self.assertEqual(tt.tm_mday, t.day)
1833 self.assertEqual(tt.tm_hour, t.hour)
1834 self.assertEqual(tt.tm_min, t.minute)
1835 self.assertEqual(tt.tm_sec, t.second)
1836 self.assertEqual(tt.tm_wday, t.weekday())
1837 self.assertEqual(tt.tm_yday, t.toordinal() -
1838 date(t.year, 1, 1).toordinal() + 1)
1839 self.assertEqual(tt.tm_isdst, -1)
1840
1841 def test_more_strftime(self):
1842 # This tests fields beyond those tested by the TestDate.test_strftime.
1843 t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
1844 self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
1845 "12 31 04 000047 33 22 06 366")
1846
1847 def test_extract(self):
1848 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1849 self.assertEqual(dt.date(), date(2002, 3, 4))
1850 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
1851
1852 def test_combine(self):
1853 d = date(2002, 3, 4)
1854 t = time(18, 45, 3, 1234)
1855 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1856 combine = self.theclass.combine
1857 dt = combine(d, t)
1858 self.assertEqual(dt, expected)
1859
1860 dt = combine(time=t, date=d)
1861 self.assertEqual(dt, expected)
1862
1863 self.assertEqual(d, dt.date())
1864 self.assertEqual(t, dt.time())
1865 self.assertEqual(dt, combine(dt.date(), dt.time()))
1866
1867 self.assertRaises(TypeError, combine) # need an arg
1868 self.assertRaises(TypeError, combine, d) # need two args
1869 self.assertRaises(TypeError, combine, t, d) # args reversed
1870 self.assertRaises(TypeError, combine, d, t, 1) # too many args
1871 self.assertRaises(TypeError, combine, "date", "time") # wrong types
1872 self.assertRaises(TypeError, combine, d, "time") # wrong type
1873 self.assertRaises(TypeError, combine, "date", t) # wrong type
1874
1875 def test_replace(self):
1876 cls = self.theclass
1877 args = [1, 2, 3, 4, 5, 6, 7]
1878 base = cls(*args)
1879 self.assertEqual(base, base.replace())
1880
1881 i = 0
1882 for name, newval in (("year", 2),
1883 ("month", 3),
1884 ("day", 4),
1885 ("hour", 5),
1886 ("minute", 6),
1887 ("second", 7),
1888 ("microsecond", 8)):
1889 newargs = args[:]
1890 newargs[i] = newval
1891 expected = cls(*newargs)
1892 got = base.replace(**{name: newval})
1893 self.assertEqual(expected, got)
1894 i += 1
1895
1896 # Out of bounds.
1897 base = cls(2000, 2, 29)
1898 self.assertRaises(ValueError, base.replace, year=2001)
1899
1900 def test_astimezone(self):
1901 # Pretty boring! The TZ test is more interesting here. astimezone()
1902 # simply can't be applied to a naive object.
1903 dt = self.theclass.now()
1904 f = FixedOffset(44, "")
1905 self.assertRaises(TypeError, dt.astimezone) # not enough args
1906 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
1907 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
1908 self.assertRaises(ValueError, dt.astimezone, f) # naive
1909 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
1910
1911 class Bogus(tzinfo):
1912 def utcoffset(self, dt): return None
1913 def dst(self, dt): return timedelta(0)
1914 bog = Bogus()
1915 self.assertRaises(ValueError, dt.astimezone, bog) # naive
1916 self.assertRaises(ValueError,
1917 dt.replace(tzinfo=bog).astimezone, f)
1918
1919 class AlsoBogus(tzinfo):
1920 def utcoffset(self, dt): return timedelta(0)
1921 def dst(self, dt): return None
1922 alsobog = AlsoBogus()
1923 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
1924
1925 def test_subclass_datetime(self):
1926
1927 class C(self.theclass):
1928 theAnswer = 42
1929
1930 def __new__(cls, *args, **kws):
1931 temp = kws.copy()
1932 extra = temp.pop('extra')
1933 result = self.theclass.__new__(cls, *args, **temp)
1934 result.extra = extra
1935 return result
1936
1937 def newmeth(self, start):
1938 return start + self.year + self.month + self.second
1939
1940 args = 2003, 4, 14, 12, 13, 41
1941
1942 dt1 = self.theclass(*args)
1943 dt2 = C(*args, **{'extra': 7})
1944
1945 self.assertEqual(dt2.__class__, C)
1946 self.assertEqual(dt2.theAnswer, 42)
1947 self.assertEqual(dt2.extra, 7)
1948 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1949 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
1950 dt1.second - 7)
1951
1952class TestSubclassDateTime(TestDateTime):
1953 theclass = SubclassDatetime
1954 # Override tests not designed for subclass
1955 def test_roundtrip(self):
1956 pass
1957
1958class SubclassTime(time):
1959 sub_var = 1
1960
1961class TestTime(HarmlessMixedComparison, unittest.TestCase):
1962
1963 theclass = time
1964
1965 def test_basic_attributes(self):
1966 t = self.theclass(12, 0)
1967 self.assertEqual(t.hour, 12)
1968 self.assertEqual(t.minute, 0)
1969 self.assertEqual(t.second, 0)
1970 self.assertEqual(t.microsecond, 0)
1971
1972 def test_basic_attributes_nonzero(self):
1973 # Make sure all attributes are non-zero so bugs in
1974 # bit-shifting access show up.
1975 t = self.theclass(12, 59, 59, 8000)
1976 self.assertEqual(t.hour, 12)
1977 self.assertEqual(t.minute, 59)
1978 self.assertEqual(t.second, 59)
1979 self.assertEqual(t.microsecond, 8000)
1980
1981 def test_roundtrip(self):
1982 t = self.theclass(1, 2, 3, 4)
1983
1984 # Verify t -> string -> time identity.
1985 s = repr(t)
1986 self.assertTrue(s.startswith('datetime.'))
1987 s = s[9:]
1988 t2 = eval(s)
1989 self.assertEqual(t, t2)
1990
1991 # Verify identity via reconstructing from pieces.
1992 t2 = self.theclass(t.hour, t.minute, t.second,
1993 t.microsecond)
1994 self.assertEqual(t, t2)
1995
1996 def test_comparing(self):
1997 args = [1, 2, 3, 4]
1998 t1 = self.theclass(*args)
1999 t2 = self.theclass(*args)
2000 self.assertEqual(t1, t2)
2001 self.assertTrue(t1 <= t2)
2002 self.assertTrue(t1 >= t2)
2003 self.assertTrue(not t1 != t2)
2004 self.assertTrue(not t1 < t2)
2005 self.assertTrue(not t1 > t2)
2006
2007 for i in range(len(args)):
2008 newargs = args[:]
2009 newargs[i] = args[i] + 1
2010 t2 = self.theclass(*newargs) # this is larger than t1
2011 self.assertTrue(t1 < t2)
2012 self.assertTrue(t2 > t1)
2013 self.assertTrue(t1 <= t2)
2014 self.assertTrue(t2 >= t1)
2015 self.assertTrue(t1 != t2)
2016 self.assertTrue(t2 != t1)
2017 self.assertTrue(not t1 == t2)
2018 self.assertTrue(not 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
2024 for badarg in OTHERSTUFF:
2025 self.assertEqual(t1 == badarg, False)
2026 self.assertEqual(t1 != badarg, True)
2027 self.assertEqual(badarg == t1, False)
2028 self.assertEqual(badarg != t1, True)
2029
2030 self.assertRaises(TypeError, lambda: t1 <= badarg)
2031 self.assertRaises(TypeError, lambda: t1 < badarg)
2032 self.assertRaises(TypeError, lambda: t1 > badarg)
2033 self.assertRaises(TypeError, lambda: t1 >= badarg)
2034 self.assertRaises(TypeError, lambda: badarg <= t1)
2035 self.assertRaises(TypeError, lambda: badarg < t1)
2036 self.assertRaises(TypeError, lambda: badarg > t1)
2037 self.assertRaises(TypeError, lambda: badarg >= t1)
2038
2039 def test_bad_constructor_arguments(self):
2040 # bad hours
2041 self.theclass(0, 0) # no exception
2042 self.theclass(23, 0) # no exception
2043 self.assertRaises(ValueError, self.theclass, -1, 0)
2044 self.assertRaises(ValueError, self.theclass, 24, 0)
2045 # bad minutes
2046 self.theclass(23, 0) # no exception
2047 self.theclass(23, 59) # no exception
2048 self.assertRaises(ValueError, self.theclass, 23, -1)
2049 self.assertRaises(ValueError, self.theclass, 23, 60)
2050 # bad seconds
2051 self.theclass(23, 59, 0) # no exception
2052 self.theclass(23, 59, 59) # no exception
2053 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
2054 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
2055 # bad microseconds
2056 self.theclass(23, 59, 59, 0) # no exception
2057 self.theclass(23, 59, 59, 999999) # no exception
2058 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
2059 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
2060
2061 def test_hash_equality(self):
2062 d = self.theclass(23, 30, 17)
2063 e = self.theclass(23, 30, 17)
2064 self.assertEqual(d, e)
2065 self.assertEqual(hash(d), hash(e))
2066
2067 dic = {d: 1}
2068 dic[e] = 2
2069 self.assertEqual(len(dic), 1)
2070 self.assertEqual(dic[d], 2)
2071 self.assertEqual(dic[e], 2)
2072
2073 d = self.theclass(0, 5, 17)
2074 e = self.theclass(0, 5, 17)
2075 self.assertEqual(d, e)
2076 self.assertEqual(hash(d), hash(e))
2077
2078 dic = {d: 1}
2079 dic[e] = 2
2080 self.assertEqual(len(dic), 1)
2081 self.assertEqual(dic[d], 2)
2082 self.assertEqual(dic[e], 2)
2083
2084 def test_isoformat(self):
2085 t = self.theclass(4, 5, 1, 123)
2086 self.assertEqual(t.isoformat(), "04:05:01.000123")
2087 self.assertEqual(t.isoformat(), str(t))
2088
2089 t = self.theclass()
2090 self.assertEqual(t.isoformat(), "00:00:00")
2091 self.assertEqual(t.isoformat(), str(t))
2092
2093 t = self.theclass(microsecond=1)
2094 self.assertEqual(t.isoformat(), "00:00:00.000001")
2095 self.assertEqual(t.isoformat(), str(t))
2096
2097 t = self.theclass(microsecond=10)
2098 self.assertEqual(t.isoformat(), "00:00:00.000010")
2099 self.assertEqual(t.isoformat(), str(t))
2100
2101 t = self.theclass(microsecond=100)
2102 self.assertEqual(t.isoformat(), "00:00:00.000100")
2103 self.assertEqual(t.isoformat(), str(t))
2104
2105 t = self.theclass(microsecond=1000)
2106 self.assertEqual(t.isoformat(), "00:00:00.001000")
2107 self.assertEqual(t.isoformat(), str(t))
2108
2109 t = self.theclass(microsecond=10000)
2110 self.assertEqual(t.isoformat(), "00:00:00.010000")
2111 self.assertEqual(t.isoformat(), str(t))
2112
2113 t = self.theclass(microsecond=100000)
2114 self.assertEqual(t.isoformat(), "00:00:00.100000")
2115 self.assertEqual(t.isoformat(), str(t))
2116
2117 def test_1653736(self):
2118 # verify it doesn't accept extra keyword arguments
2119 t = self.theclass(second=1)
2120 self.assertRaises(TypeError, t.isoformat, foo=3)
2121
2122 def test_strftime(self):
2123 t = self.theclass(1, 2, 3, 4)
2124 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
2125 # A naive object replaces %z and %Z with empty strings.
2126 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
2127
2128 def test_format(self):
2129 t = self.theclass(1, 2, 3, 4)
2130 self.assertEqual(t.__format__(''), str(t))
2131
2132 # check that a derived class's __str__() gets called
2133 class A(self.theclass):
2134 def __str__(self):
2135 return 'A'
2136 a = A(1, 2, 3, 4)
2137 self.assertEqual(a.__format__(''), 'A')
2138
2139 # check that a derived class's strftime gets called
2140 class B(self.theclass):
2141 def strftime(self, format_spec):
2142 return 'B'
2143 b = B(1, 2, 3, 4)
2144 self.assertEqual(b.__format__(''), str(t))
2145
2146 for fmt in ['%H %M %S',
2147 ]:
2148 self.assertEqual(t.__format__(fmt), t.strftime(fmt))
2149 self.assertEqual(a.__format__(fmt), t.strftime(fmt))
2150 self.assertEqual(b.__format__(fmt), 'B')
2151
2152 def test_str(self):
2153 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
2154 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
2155 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
2156 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
2157 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
2158
2159 def test_repr(self):
2160 name = 'datetime.' + self.theclass.__name__
2161 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
2162 "%s(1, 2, 3, 4)" % name)
2163 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
2164 "%s(10, 2, 3, 4000)" % name)
2165 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
2166 "%s(0, 2, 3, 400000)" % name)
2167 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
2168 "%s(12, 2, 3)" % name)
2169 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
2170 "%s(23, 15)" % name)
2171
2172 def test_resolution_info(self):
2173 self.assertIsInstance(self.theclass.min, self.theclass)
2174 self.assertIsInstance(self.theclass.max, self.theclass)
2175 self.assertIsInstance(self.theclass.resolution, timedelta)
2176 self.assertTrue(self.theclass.max > self.theclass.min)
2177
2178 def test_pickling(self):
2179 args = 20, 59, 16, 64**2
2180 orig = self.theclass(*args)
2181 for pickler, unpickler, proto in pickle_choices:
2182 green = pickler.dumps(orig, proto)
2183 derived = unpickler.loads(green)
2184 self.assertEqual(orig, derived)
2185
2186 def test_pickling_subclass_time(self):
2187 args = 20, 59, 16, 64**2
2188 orig = SubclassTime(*args)
2189 for pickler, unpickler, proto in pickle_choices:
2190 green = pickler.dumps(orig, proto)
2191 derived = unpickler.loads(green)
2192 self.assertEqual(orig, derived)
2193
2194 def test_bool(self):
2195 cls = self.theclass
2196 self.assertTrue(cls(1))
2197 self.assertTrue(cls(0, 1))
2198 self.assertTrue(cls(0, 0, 1))
2199 self.assertTrue(cls(0, 0, 0, 1))
2200 self.assertTrue(not cls(0))
2201 self.assertTrue(not cls())
2202
2203 def test_replace(self):
2204 cls = self.theclass
2205 args = [1, 2, 3, 4]
2206 base = cls(*args)
2207 self.assertEqual(base, base.replace())
2208
2209 i = 0
2210 for name, newval in (("hour", 5),
2211 ("minute", 6),
2212 ("second", 7),
2213 ("microsecond", 8)):
2214 newargs = args[:]
2215 newargs[i] = newval
2216 expected = cls(*newargs)
2217 got = base.replace(**{name: newval})
2218 self.assertEqual(expected, got)
2219 i += 1
2220
2221 # Out of bounds.
2222 base = cls(1)
2223 self.assertRaises(ValueError, base.replace, hour=24)
2224 self.assertRaises(ValueError, base.replace, minute=-1)
2225 self.assertRaises(ValueError, base.replace, second=100)
2226 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2227
2228 def test_subclass_time(self):
2229
2230 class C(self.theclass):
2231 theAnswer = 42
2232
2233 def __new__(cls, *args, **kws):
2234 temp = kws.copy()
2235 extra = temp.pop('extra')
2236 result = self.theclass.__new__(cls, *args, **temp)
2237 result.extra = extra
2238 return result
2239
2240 def newmeth(self, start):
2241 return start + self.hour + self.second
2242
2243 args = 4, 5, 6
2244
2245 dt1 = self.theclass(*args)
2246 dt2 = C(*args, **{'extra': 7})
2247
2248 self.assertEqual(dt2.__class__, C)
2249 self.assertEqual(dt2.theAnswer, 42)
2250 self.assertEqual(dt2.extra, 7)
2251 self.assertEqual(dt1.isoformat(), dt2.isoformat())
2252 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2253
2254 def test_backdoor_resistance(self):
2255 # see TestDate.test_backdoor_resistance().
2256 base = '2:59.0'
2257 for hour_byte in ' ', '9', chr(24), '\xff':
2258 self.assertRaises(TypeError, self.theclass,
2259 hour_byte + base[1:])
2260
2261# A mixin for classes with a tzinfo= argument. Subclasses must define
2262# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
2263# must be legit (which is true for time and datetime).
2264class TZInfoBase:
2265
2266 def test_argument_passing(self):
2267 cls = self.theclass
2268 # A datetime passes itself on, a time passes None.
2269 class introspective(tzinfo):
2270 def tzname(self, dt): return dt and "real" or "none"
2271 def utcoffset(self, dt):
2272 return timedelta(minutes = dt and 42 or -42)
2273 dst = utcoffset
2274
2275 obj = cls(1, 2, 3, tzinfo=introspective())
2276
2277 expected = cls is time and "none" or "real"
2278 self.assertEqual(obj.tzname(), expected)
2279
2280 expected = timedelta(minutes=(cls is time and -42 or 42))
2281 self.assertEqual(obj.utcoffset(), expected)
2282 self.assertEqual(obj.dst(), expected)
2283
2284 def test_bad_tzinfo_classes(self):
2285 cls = self.theclass
2286 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
2287
2288 class NiceTry(object):
2289 def __init__(self): pass
2290 def utcoffset(self, dt): pass
2291 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
2292
2293 class BetterTry(tzinfo):
2294 def __init__(self): pass
2295 def utcoffset(self, dt): pass
2296 b = BetterTry()
2297 t = cls(1, 1, 1, tzinfo=b)
2298 self.assertTrue(t.tzinfo is b)
2299
2300 def test_utc_offset_out_of_bounds(self):
2301 class Edgy(tzinfo):
2302 def __init__(self, offset):
2303 self.offset = timedelta(minutes=offset)
2304 def utcoffset(self, dt):
2305 return self.offset
2306
2307 cls = self.theclass
2308 for offset, legit in ((-1440, False),
2309 (-1439, True),
2310 (1439, True),
2311 (1440, False)):
2312 if cls is time:
2313 t = cls(1, 2, 3, tzinfo=Edgy(offset))
2314 elif cls is datetime:
2315 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
2316 else:
2317 assert 0, "impossible"
2318 if legit:
2319 aofs = abs(offset)
2320 h, m = divmod(aofs, 60)
2321 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
2322 if isinstance(t, datetime):
2323 t = t.timetz()
2324 self.assertEqual(str(t), "01:02:03" + tag)
2325 else:
2326 self.assertRaises(ValueError, str, t)
2327
2328 def test_tzinfo_classes(self):
2329 cls = self.theclass
2330 class C1(tzinfo):
2331 def utcoffset(self, dt): return None
2332 def dst(self, dt): return None
2333 def tzname(self, dt): return None
2334 for t in (cls(1, 1, 1),
2335 cls(1, 1, 1, tzinfo=None),
2336 cls(1, 1, 1, tzinfo=C1())):
2337 self.assertTrue(t.utcoffset() is None)
2338 self.assertTrue(t.dst() is None)
2339 self.assertTrue(t.tzname() is None)
2340
2341 class C3(tzinfo):
2342 def utcoffset(self, dt): return timedelta(minutes=-1439)
2343 def dst(self, dt): return timedelta(minutes=1439)
2344 def tzname(self, dt): return "aname"
2345 t = cls(1, 1, 1, tzinfo=C3())
2346 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
2347 self.assertEqual(t.dst(), timedelta(minutes=1439))
2348 self.assertEqual(t.tzname(), "aname")
2349
2350 # Wrong types.
2351 class C4(tzinfo):
2352 def utcoffset(self, dt): return "aname"
2353 def dst(self, dt): return 7
2354 def tzname(self, dt): return 0
2355 t = cls(1, 1, 1, tzinfo=C4())
2356 self.assertRaises(TypeError, t.utcoffset)
2357 self.assertRaises(TypeError, t.dst)
2358 self.assertRaises(TypeError, t.tzname)
2359
2360 # Offset out of range.
2361 class C6(tzinfo):
2362 def utcoffset(self, dt): return timedelta(hours=-24)
2363 def dst(self, dt): return timedelta(hours=24)
2364 t = cls(1, 1, 1, tzinfo=C6())
2365 self.assertRaises(ValueError, t.utcoffset)
2366 self.assertRaises(ValueError, t.dst)
2367
2368 # Not a whole number of minutes.
2369 class C7(tzinfo):
2370 def utcoffset(self, dt): return timedelta(seconds=61)
2371 def dst(self, dt): return timedelta(microseconds=-81)
2372 t = cls(1, 1, 1, tzinfo=C7())
2373 self.assertRaises(ValueError, t.utcoffset)
2374 self.assertRaises(ValueError, t.dst)
2375
2376 def test_aware_compare(self):
2377 cls = self.theclass
2378
2379 # Ensure that utcoffset() gets ignored if the comparands have
2380 # the same tzinfo member.
2381 class OperandDependentOffset(tzinfo):
2382 def utcoffset(self, t):
2383 if t.minute < 10:
2384 # d0 and d1 equal after adjustment
2385 return timedelta(minutes=t.minute)
2386 else:
2387 # d2 off in the weeds
2388 return timedelta(minutes=59)
2389
2390 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
2391 d0 = base.replace(minute=3)
2392 d1 = base.replace(minute=9)
2393 d2 = base.replace(minute=11)
2394 for x in d0, d1, d2:
2395 for y in d0, d1, d2:
2396 for op in lt, le, gt, ge, eq, ne:
2397 got = op(x, y)
2398 expected = op(x.minute, y.minute)
2399 self.assertEqual(got, expected)
2400
2401 # However, if they're different members, uctoffset is not ignored.
2402 # Note that a time can't actually have an operand-depedent offset,
2403 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
2404 # so skip this test for time.
2405 if cls is not time:
2406 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2407 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2408 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2409 for x in d0, d1, d2:
2410 for y in d0, d1, d2:
2411 got = (x > y) - (x < y)
2412 if (x is d0 or x is d1) and (y is d0 or y is d1):
2413 expected = 0
2414 elif x is y is d2:
2415 expected = 0
2416 elif x is d2:
2417 expected = -1
2418 else:
2419 assert y is d2
2420 expected = 1
2421 self.assertEqual(got, expected)
2422
2423
2424# Testing time objects with a non-None tzinfo.
2425class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
2426 theclass = time
2427
2428 def test_empty(self):
2429 t = self.theclass()
2430 self.assertEqual(t.hour, 0)
2431 self.assertEqual(t.minute, 0)
2432 self.assertEqual(t.second, 0)
2433 self.assertEqual(t.microsecond, 0)
2434 self.assertTrue(t.tzinfo is None)
2435
2436 def test_zones(self):
2437 est = FixedOffset(-300, "EST", 1)
2438 utc = FixedOffset(0, "UTC", -2)
2439 met = FixedOffset(60, "MET", 3)
2440 t1 = time( 7, 47, tzinfo=est)
2441 t2 = time(12, 47, tzinfo=utc)
2442 t3 = time(13, 47, tzinfo=met)
2443 t4 = time(microsecond=40)
2444 t5 = time(microsecond=40, tzinfo=utc)
2445
2446 self.assertEqual(t1.tzinfo, est)
2447 self.assertEqual(t2.tzinfo, utc)
2448 self.assertEqual(t3.tzinfo, met)
2449 self.assertTrue(t4.tzinfo is None)
2450 self.assertEqual(t5.tzinfo, utc)
2451
2452 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2453 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2454 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
2455 self.assertTrue(t4.utcoffset() is None)
2456 self.assertRaises(TypeError, t1.utcoffset, "no args")
2457
2458 self.assertEqual(t1.tzname(), "EST")
2459 self.assertEqual(t2.tzname(), "UTC")
2460 self.assertEqual(t3.tzname(), "MET")
2461 self.assertTrue(t4.tzname() is None)
2462 self.assertRaises(TypeError, t1.tzname, "no args")
2463
2464 self.assertEqual(t1.dst(), timedelta(minutes=1))
2465 self.assertEqual(t2.dst(), timedelta(minutes=-2))
2466 self.assertEqual(t3.dst(), timedelta(minutes=3))
2467 self.assertTrue(t4.dst() is None)
2468 self.assertRaises(TypeError, t1.dst, "no args")
2469
2470 self.assertEqual(hash(t1), hash(t2))
2471 self.assertEqual(hash(t1), hash(t3))
2472 self.assertEqual(hash(t2), hash(t3))
2473
2474 self.assertEqual(t1, t2)
2475 self.assertEqual(t1, t3)
2476 self.assertEqual(t2, t3)
2477 self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
2478 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
2479 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
2480
2481 self.assertEqual(str(t1), "07:47:00-05:00")
2482 self.assertEqual(str(t2), "12:47:00+00:00")
2483 self.assertEqual(str(t3), "13:47:00+01:00")
2484 self.assertEqual(str(t4), "00:00:00.000040")
2485 self.assertEqual(str(t5), "00:00:00.000040+00:00")
2486
2487 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
2488 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
2489 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
2490 self.assertEqual(t4.isoformat(), "00:00:00.000040")
2491 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
2492
2493 d = 'datetime.time'
2494 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
2495 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
2496 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
2497 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
2498 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
2499
2500 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
2501 "07:47:00 %Z=EST %z=-0500")
2502 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
2503 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
2504
2505 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
2506 t1 = time(23, 59, tzinfo=yuck)
2507 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
2508 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
2509
2510 # Check that an invalid tzname result raises an exception.
2511 class Badtzname(tzinfo):
2512 def tzname(self, dt): return 42
2513 t = time(2, 3, 4, tzinfo=Badtzname())
2514 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
2515 self.assertRaises(TypeError, t.strftime, "%Z")
2516
2517 def test_hash_edge_cases(self):
2518 # Offsets that overflow a basic time.
2519 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2520 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2521 self.assertEqual(hash(t1), hash(t2))
2522
2523 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2524 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2525 self.assertEqual(hash(t1), hash(t2))
2526
2527 def test_pickling(self):
2528 # Try one without a tzinfo.
2529 args = 20, 59, 16, 64**2
2530 orig = self.theclass(*args)
2531 for pickler, unpickler, proto in pickle_choices:
2532 green = pickler.dumps(orig, proto)
2533 derived = unpickler.loads(green)
2534 self.assertEqual(orig, derived)
2535
2536 # Try one with a tzinfo.
2537 tinfo = PicklableFixedOffset(-300, 'cookie')
2538 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
2539 for pickler, unpickler, proto in pickle_choices:
2540 green = pickler.dumps(orig, proto)
2541 derived = unpickler.loads(green)
2542 self.assertEqual(orig, derived)
2543 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
2544 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2545 self.assertEqual(derived.tzname(), 'cookie')
2546
2547 def test_more_bool(self):
2548 # Test cases with non-None tzinfo.
2549 cls = self.theclass
2550
2551 t = cls(0, tzinfo=FixedOffset(-300, ""))
2552 self.assertTrue(t)
2553
2554 t = cls(5, tzinfo=FixedOffset(-300, ""))
2555 self.assertTrue(t)
2556
2557 t = cls(5, tzinfo=FixedOffset(300, ""))
2558 self.assertTrue(not t)
2559
2560 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2561 self.assertTrue(not t)
2562
2563 # Mostly ensuring this doesn't overflow internally.
2564 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
2565 self.assertTrue(t)
2566
2567 # But this should yield a value error -- the utcoffset is bogus.
2568 t = cls(0, tzinfo=FixedOffset(24*60, ""))
2569 self.assertRaises(ValueError, lambda: bool(t))
2570
2571 # Likewise.
2572 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
2573 self.assertRaises(ValueError, lambda: bool(t))
2574
2575 def test_replace(self):
2576 cls = self.theclass
2577 z100 = FixedOffset(100, "+100")
2578 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2579 args = [1, 2, 3, 4, z100]
2580 base = cls(*args)
2581 self.assertEqual(base, base.replace())
2582
2583 i = 0
2584 for name, newval in (("hour", 5),
2585 ("minute", 6),
2586 ("second", 7),
2587 ("microsecond", 8),
2588 ("tzinfo", zm200)):
2589 newargs = args[:]
2590 newargs[i] = newval
2591 expected = cls(*newargs)
2592 got = base.replace(**{name: newval})
2593 self.assertEqual(expected, got)
2594 i += 1
2595
2596 # Ensure we can get rid of a tzinfo.
2597 self.assertEqual(base.tzname(), "+100")
2598 base2 = base.replace(tzinfo=None)
2599 self.assertTrue(base2.tzinfo is None)
2600 self.assertTrue(base2.tzname() is None)
2601
2602 # Ensure we can add one.
2603 base3 = base2.replace(tzinfo=z100)
2604 self.assertEqual(base, base3)
2605 self.assertTrue(base.tzinfo is base3.tzinfo)
2606
2607 # Out of bounds.
2608 base = cls(1)
2609 self.assertRaises(ValueError, base.replace, hour=24)
2610 self.assertRaises(ValueError, base.replace, minute=-1)
2611 self.assertRaises(ValueError, base.replace, second=100)
2612 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2613
2614 def test_mixed_compare(self):
2615 t1 = time(1, 2, 3)
2616 t2 = time(1, 2, 3)
2617 self.assertEqual(t1, t2)
2618 t2 = t2.replace(tzinfo=None)
2619 self.assertEqual(t1, t2)
2620 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2621 self.assertEqual(t1, t2)
2622 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2623 self.assertRaises(TypeError, lambda: t1 == t2)
2624
2625 # In time w/ identical tzinfo objects, utcoffset is ignored.
2626 class Varies(tzinfo):
2627 def __init__(self):
2628 self.offset = timedelta(minutes=22)
2629 def utcoffset(self, t):
2630 self.offset += timedelta(minutes=1)
2631 return self.offset
2632
2633 v = Varies()
2634 t1 = t2.replace(tzinfo=v)
2635 t2 = t2.replace(tzinfo=v)
2636 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2637 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2638 self.assertEqual(t1, t2)
2639
2640 # But if they're not identical, it isn't ignored.
2641 t2 = t2.replace(tzinfo=Varies())
2642 self.assertTrue(t1 < t2) # t1's offset counter still going up
2643
2644 def test_subclass_timetz(self):
2645
2646 class C(self.theclass):
2647 theAnswer = 42
2648
2649 def __new__(cls, *args, **kws):
2650 temp = kws.copy()
2651 extra = temp.pop('extra')
2652 result = self.theclass.__new__(cls, *args, **temp)
2653 result.extra = extra
2654 return result
2655
2656 def newmeth(self, start):
2657 return start + self.hour + self.second
2658
2659 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2660
2661 dt1 = self.theclass(*args)
2662 dt2 = C(*args, **{'extra': 7})
2663
2664 self.assertEqual(dt2.__class__, C)
2665 self.assertEqual(dt2.theAnswer, 42)
2666 self.assertEqual(dt2.extra, 7)
2667 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2668 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2669
2670
2671# Testing datetime objects with a non-None tzinfo.
2672
2673class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
2674 theclass = datetime
2675
2676 def test_trivial(self):
2677 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2678 self.assertEqual(dt.year, 1)
2679 self.assertEqual(dt.month, 2)
2680 self.assertEqual(dt.day, 3)
2681 self.assertEqual(dt.hour, 4)
2682 self.assertEqual(dt.minute, 5)
2683 self.assertEqual(dt.second, 6)
2684 self.assertEqual(dt.microsecond, 7)
2685 self.assertEqual(dt.tzinfo, None)
2686
2687 def test_even_more_compare(self):
2688 # The test_compare() and test_more_compare() inherited from TestDate
2689 # and TestDateTime covered non-tzinfo cases.
2690
2691 # Smallest possible after UTC adjustment.
2692 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2693 # Largest possible after UTC adjustment.
2694 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2695 tzinfo=FixedOffset(-1439, ""))
2696
2697 # Make sure those compare correctly, and w/o overflow.
2698 self.assertTrue(t1 < t2)
2699 self.assertTrue(t1 != t2)
2700 self.assertTrue(t2 > t1)
2701
2702 self.assertEqual(t1, t1)
2703 self.assertEqual(t2, t2)
2704
2705 # Equal afer adjustment.
2706 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2707 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2708 self.assertEqual(t1, t2)
2709
2710 # Change t1 not to subtract a minute, and t1 should be larger.
2711 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2712 self.assertTrue(t1 > t2)
2713
2714 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2715 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2716 self.assertTrue(t1 < t2)
2717
2718 # Back to the original t1, but make seconds resolve it.
2719 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2720 second=1)
2721 self.assertTrue(t1 > t2)
2722
2723 # Likewise, but make microseconds resolve it.
2724 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2725 microsecond=1)
2726 self.assertTrue(t1 > t2)
2727
2728 # Make t2 naive and it should fail.
2729 t2 = self.theclass.min
2730 self.assertRaises(TypeError, lambda: t1 == t2)
2731 self.assertEqual(t2, t2)
2732
2733 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2734 class Naive(tzinfo):
2735 def utcoffset(self, dt): return None
2736 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
2737 self.assertRaises(TypeError, lambda: t1 == t2)
2738 self.assertEqual(t2, t2)
2739
2740 # OTOH, it's OK to compare two of these mixing the two ways of being
2741 # naive.
2742 t1 = self.theclass(5, 6, 7)
2743 self.assertEqual(t1, t2)
2744
2745 # Try a bogus uctoffset.
2746 class Bogus(tzinfo):
2747 def utcoffset(self, dt):
2748 return timedelta(minutes=1440) # out of bounds
2749 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2750 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
2751 self.assertRaises(ValueError, lambda: t1 == t2)
2752
2753 def test_pickling(self):
2754 # Try one without a tzinfo.
2755 args = 6, 7, 23, 20, 59, 1, 64**2
2756 orig = self.theclass(*args)
2757 for pickler, unpickler, proto in pickle_choices:
2758 green = pickler.dumps(orig, proto)
2759 derived = unpickler.loads(green)
2760 self.assertEqual(orig, derived)
2761
2762 # Try one with a tzinfo.
2763 tinfo = PicklableFixedOffset(-300, 'cookie')
2764 orig = self.theclass(*args, **{'tzinfo': tinfo})
2765 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
2766 for pickler, unpickler, proto in pickle_choices:
2767 green = pickler.dumps(orig, proto)
2768 derived = unpickler.loads(green)
2769 self.assertEqual(orig, derived)
2770 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
2771 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2772 self.assertEqual(derived.tzname(), 'cookie')
2773
2774 def test_extreme_hashes(self):
2775 # If an attempt is made to hash these via subtracting the offset
2776 # then hashing a datetime object, OverflowError results. The
2777 # Python implementation used to blow up here.
2778 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2779 hash(t)
2780 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2781 tzinfo=FixedOffset(-1439, ""))
2782 hash(t)
2783
2784 # OTOH, an OOB offset should blow up.
2785 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2786 self.assertRaises(ValueError, hash, t)
2787
2788 def test_zones(self):
2789 est = FixedOffset(-300, "EST")
2790 utc = FixedOffset(0, "UTC")
2791 met = FixedOffset(60, "MET")
2792 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2793 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2794 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
2795 self.assertEqual(t1.tzinfo, est)
2796 self.assertEqual(t2.tzinfo, utc)
2797 self.assertEqual(t3.tzinfo, met)
2798 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2799 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2800 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
2801 self.assertEqual(t1.tzname(), "EST")
2802 self.assertEqual(t2.tzname(), "UTC")
2803 self.assertEqual(t3.tzname(), "MET")
2804 self.assertEqual(hash(t1), hash(t2))
2805 self.assertEqual(hash(t1), hash(t3))
2806 self.assertEqual(hash(t2), hash(t3))
2807 self.assertEqual(t1, t2)
2808 self.assertEqual(t1, t3)
2809 self.assertEqual(t2, t3)
2810 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2811 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2812 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
2813 d = 'datetime.datetime(2002, 3, 19, '
2814 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2815 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2816 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2817
2818 def test_combine(self):
2819 met = FixedOffset(60, "MET")
2820 d = date(2002, 3, 4)
2821 tz = time(18, 45, 3, 1234, tzinfo=met)
2822 dt = datetime.combine(d, tz)
2823 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
2824 tzinfo=met))
2825
2826 def test_extract(self):
2827 met = FixedOffset(60, "MET")
2828 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2829 self.assertEqual(dt.date(), date(2002, 3, 4))
2830 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2831 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
2832
2833 def test_tz_aware_arithmetic(self):
2834 import random
2835
2836 now = self.theclass.now()
2837 tz55 = FixedOffset(-330, "west 5:30")
2838 timeaware = now.time().replace(tzinfo=tz55)
2839 nowaware = self.theclass.combine(now.date(), timeaware)
2840 self.assertTrue(nowaware.tzinfo is tz55)
2841 self.assertEqual(nowaware.timetz(), timeaware)
2842
2843 # Can't mix aware and non-aware.
2844 self.assertRaises(TypeError, lambda: now - nowaware)
2845 self.assertRaises(TypeError, lambda: nowaware - now)
2846
2847 # And adding datetime's doesn't make sense, aware or not.
2848 self.assertRaises(TypeError, lambda: now + nowaware)
2849 self.assertRaises(TypeError, lambda: nowaware + now)
2850 self.assertRaises(TypeError, lambda: nowaware + nowaware)
2851
2852 # Subtracting should yield 0.
2853 self.assertEqual(now - now, timedelta(0))
2854 self.assertEqual(nowaware - nowaware, timedelta(0))
2855
2856 # Adding a delta should preserve tzinfo.
2857 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
2858 nowawareplus = nowaware + delta
2859 self.assertTrue(nowaware.tzinfo is tz55)
2860 nowawareplus2 = delta + nowaware
2861 self.assertTrue(nowawareplus2.tzinfo is tz55)
2862 self.assertEqual(nowawareplus, nowawareplus2)
2863
2864 # that - delta should be what we started with, and that - what we
2865 # started with should be delta.
2866 diff = nowawareplus - delta
2867 self.assertTrue(diff.tzinfo is tz55)
2868 self.assertEqual(nowaware, diff)
2869 self.assertRaises(TypeError, lambda: delta - nowawareplus)
2870 self.assertEqual(nowawareplus - nowaware, delta)
2871
2872 # Make up a random timezone.
2873 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
2874 # Attach it to nowawareplus.
2875 nowawareplus = nowawareplus.replace(tzinfo=tzr)
2876 self.assertTrue(nowawareplus.tzinfo is tzr)
2877 # Make sure the difference takes the timezone adjustments into account.
2878 got = nowaware - nowawareplus
2879 # Expected: (nowaware base - nowaware offset) -
2880 # (nowawareplus base - nowawareplus offset) =
2881 # (nowaware base - nowawareplus base) +
2882 # (nowawareplus offset - nowaware offset) =
2883 # -delta + nowawareplus offset - nowaware offset
2884 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
2885 self.assertEqual(got, expected)
2886
2887 # Try max possible difference.
2888 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
2889 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2890 tzinfo=FixedOffset(-1439, "max"))
2891 maxdiff = max - min
2892 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
2893 timedelta(minutes=2*1439))
2894 # Different tzinfo, but the same offset
2895 tza = timezone(HOUR, 'A')
2896 tzb = timezone(HOUR, 'B')
2897 delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
2898 self.assertEqual(delta, self.theclass.min - self.theclass.max)
2899
2900 def test_tzinfo_now(self):
2901 meth = self.theclass.now
2902 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2903 base = meth()
2904 # Try with and without naming the keyword.
2905 off42 = FixedOffset(42, "42")
2906 another = meth(off42)
2907 again = meth(tz=off42)
2908 self.assertTrue(another.tzinfo is again.tzinfo)
2909 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
2910 # Bad argument with and w/o naming the keyword.
2911 self.assertRaises(TypeError, meth, 16)
2912 self.assertRaises(TypeError, meth, tzinfo=16)
2913 # Bad keyword name.
2914 self.assertRaises(TypeError, meth, tinfo=off42)
2915 # Too many args.
2916 self.assertRaises(TypeError, meth, off42, off42)
2917
2918 # We don't know which time zone we're in, and don't have a tzinfo
2919 # class to represent it, so seeing whether a tz argument actually
2920 # does a conversion is tricky.
2921 utc = FixedOffset(0, "utc", 0)
2922 for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
2923 timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
2924 for dummy in range(3):
2925 now = datetime.now(weirdtz)
2926 self.assertTrue(now.tzinfo is weirdtz)
2927 utcnow = datetime.utcnow().replace(tzinfo=utc)
2928 now2 = utcnow.astimezone(weirdtz)
2929 if abs(now - now2) < timedelta(seconds=30):
2930 break
2931 # Else the code is broken, or more than 30 seconds passed between
2932 # calls; assuming the latter, just try again.
2933 else:
2934 # Three strikes and we're out.
2935 self.fail("utcnow(), now(tz), or astimezone() may be broken")
2936
2937 def test_tzinfo_fromtimestamp(self):
2938 import time
2939 meth = self.theclass.fromtimestamp
2940 ts = time.time()
2941 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2942 base = meth(ts)
2943 # Try with and without naming the keyword.
2944 off42 = FixedOffset(42, "42")
2945 another = meth(ts, off42)
2946 again = meth(ts, tz=off42)
2947 self.assertTrue(another.tzinfo is again.tzinfo)
2948 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
2949 # Bad argument with and w/o naming the keyword.
2950 self.assertRaises(TypeError, meth, ts, 16)
2951 self.assertRaises(TypeError, meth, ts, tzinfo=16)
2952 # Bad keyword name.
2953 self.assertRaises(TypeError, meth, ts, tinfo=off42)
2954 # Too many args.
2955 self.assertRaises(TypeError, meth, ts, off42, off42)
2956 # Too few args.
2957 self.assertRaises(TypeError, meth)
2958
2959 # Try to make sure tz= actually does some conversion.
2960 timestamp = 1000000000
2961 utcdatetime = datetime.utcfromtimestamp(timestamp)
2962 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
2963 # But on some flavor of Mac, it's nowhere near that. So we can't have
2964 # any idea here what time that actually is, we can only test that
2965 # relative changes match.
2966 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
2967 tz = FixedOffset(utcoffset, "tz", 0)
2968 expected = utcdatetime + utcoffset
2969 got = datetime.fromtimestamp(timestamp, tz)
2970 self.assertEqual(expected, got.replace(tzinfo=None))
2971
2972 def test_tzinfo_utcnow(self):
2973 meth = self.theclass.utcnow
2974 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2975 base = meth()
2976 # Try with and without naming the keyword; for whatever reason,
2977 # utcnow() doesn't accept a tzinfo argument.
2978 off42 = FixedOffset(42, "42")
2979 self.assertRaises(TypeError, meth, off42)
2980 self.assertRaises(TypeError, meth, tzinfo=off42)
2981
2982 def test_tzinfo_utcfromtimestamp(self):
2983 import time
2984 meth = self.theclass.utcfromtimestamp
2985 ts = time.time()
2986 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2987 base = meth(ts)
2988 # Try with and without naming the keyword; for whatever reason,
2989 # utcfromtimestamp() doesn't accept a tzinfo argument.
2990 off42 = FixedOffset(42, "42")
2991 self.assertRaises(TypeError, meth, ts, off42)
2992 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
2993
2994 def test_tzinfo_timetuple(self):
2995 # TestDateTime tested most of this. datetime adds a twist to the
2996 # DST flag.
2997 class DST(tzinfo):
2998 def __init__(self, dstvalue):
2999 if isinstance(dstvalue, int):
3000 dstvalue = timedelta(minutes=dstvalue)
3001 self.dstvalue = dstvalue
3002 def dst(self, dt):
3003 return self.dstvalue
3004
3005 cls = self.theclass
3006 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
3007 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
3008 t = d.timetuple()
3009 self.assertEqual(1, t.tm_year)
3010 self.assertEqual(1, t.tm_mon)
3011 self.assertEqual(1, t.tm_mday)
3012 self.assertEqual(10, t.tm_hour)
3013 self.assertEqual(20, t.tm_min)
3014 self.assertEqual(30, t.tm_sec)
3015 self.assertEqual(0, t.tm_wday)
3016 self.assertEqual(1, t.tm_yday)
3017 self.assertEqual(flag, t.tm_isdst)
3018
3019 # dst() returns wrong type.
3020 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
3021
3022 # dst() at the edge.
3023 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
3024 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
3025
3026 # dst() out of range.
3027 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
3028 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
3029
3030 def test_utctimetuple(self):
3031 class DST(tzinfo):
3032 def __init__(self, dstvalue=0):
3033 if isinstance(dstvalue, int):
3034 dstvalue = timedelta(minutes=dstvalue)
3035 self.dstvalue = dstvalue
3036 def dst(self, dt):
3037 return self.dstvalue
3038
3039 cls = self.theclass
3040 # This can't work: DST didn't implement utcoffset.
3041 self.assertRaises(NotImplementedError,
3042 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
3043
3044 class UOFS(DST):
3045 def __init__(self, uofs, dofs=None):
3046 DST.__init__(self, dofs)
3047 self.uofs = timedelta(minutes=uofs)
3048 def utcoffset(self, dt):
3049 return self.uofs
3050
3051 for dstvalue in -33, 33, 0, None:
3052 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
3053 t = d.utctimetuple()
3054 self.assertEqual(d.year, t.tm_year)
3055 self.assertEqual(d.month, t.tm_mon)
3056 self.assertEqual(d.day, t.tm_mday)
3057 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
3058 self.assertEqual(13, t.tm_min)
3059 self.assertEqual(d.second, t.tm_sec)
3060 self.assertEqual(d.weekday(), t.tm_wday)
3061 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
3062 t.tm_yday)
3063 # Ensure tm_isdst is 0 regardless of what dst() says: DST
3064 # is never in effect for a UTC time.
3065 self.assertEqual(0, t.tm_isdst)
3066
3067 # For naive datetime, utctimetuple == timetuple except for isdst
3068 d = cls(1, 2, 3, 10, 20, 30, 40)
3069 t = d.utctimetuple()
3070 self.assertEqual(t[:-1], d.timetuple()[:-1])
3071 self.assertEqual(0, t.tm_isdst)
3072 # Same if utcoffset is None
3073 class NOFS(DST):
3074 def utcoffset(self, dt):
3075 return None
3076 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
3077 t = d.utctimetuple()
3078 self.assertEqual(t[:-1], d.timetuple()[:-1])
3079 self.assertEqual(0, t.tm_isdst)
3080 # Check that bad tzinfo is detected
3081 class BOFS(DST):
3082 def utcoffset(self, dt):
3083 return "EST"
3084 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
3085 self.assertRaises(TypeError, d.utctimetuple)
3086
3087 # Check that utctimetuple() is the same as
3088 # astimezone(utc).timetuple()
3089 d = cls(2010, 11, 13, 14, 15, 16, 171819)
3090 for tz in [timezone.min, timezone.utc, timezone.max]:
3091 dtz = d.replace(tzinfo=tz)
3092 self.assertEqual(dtz.utctimetuple()[:-1],
3093 dtz.astimezone(timezone.utc).timetuple()[:-1])
3094 # At the edges, UTC adjustment can produce years out-of-range
3095 # for a datetime object. Ensure that an OverflowError is
3096 # raised.
3097 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
3098 # That goes back 1 minute less than a full day.
3099 self.assertRaises(OverflowError, tiny.utctimetuple)
3100
3101 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
3102 # That goes forward 1 minute less than a full day.
3103 self.assertRaises(OverflowError, huge.utctimetuple)
3104 # More overflow cases
3105 tiny = cls.min.replace(tzinfo=timezone(MINUTE))
3106 self.assertRaises(OverflowError, tiny.utctimetuple)
3107 huge = cls.max.replace(tzinfo=timezone(-MINUTE))
3108 self.assertRaises(OverflowError, huge.utctimetuple)
3109
3110 def test_tzinfo_isoformat(self):
3111 zero = FixedOffset(0, "+00:00")
3112 plus = FixedOffset(220, "+03:40")
3113 minus = FixedOffset(-231, "-03:51")
3114 unknown = FixedOffset(None, "")
3115
3116 cls = self.theclass
3117 datestr = '0001-02-03'
3118 for ofs in None, zero, plus, minus, unknown:
3119 for us in 0, 987001:
3120 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
3121 timestr = '04:05:59' + (us and '.987001' or '')
3122 ofsstr = ofs is not None and d.tzname() or ''
3123 tailstr = timestr + ofsstr
3124 iso = d.isoformat()
3125 self.assertEqual(iso, datestr + 'T' + tailstr)
3126 self.assertEqual(iso, d.isoformat('T'))
3127 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
3128 self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
3129 self.assertEqual(str(d), datestr + ' ' + tailstr)
3130
3131 def test_replace(self):
3132 cls = self.theclass
3133 z100 = FixedOffset(100, "+100")
3134 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3135 args = [1, 2, 3, 4, 5, 6, 7, z100]
3136 base = cls(*args)
3137 self.assertEqual(base, base.replace())
3138
3139 i = 0
3140 for name, newval in (("year", 2),
3141 ("month", 3),
3142 ("day", 4),
3143 ("hour", 5),
3144 ("minute", 6),
3145 ("second", 7),
3146 ("microsecond", 8),
3147 ("tzinfo", zm200)):
3148 newargs = args[:]
3149 newargs[i] = newval
3150 expected = cls(*newargs)
3151 got = base.replace(**{name: newval})
3152 self.assertEqual(expected, got)
3153 i += 1
3154
3155 # Ensure we can get rid of a tzinfo.
3156 self.assertEqual(base.tzname(), "+100")
3157 base2 = base.replace(tzinfo=None)
3158 self.assertTrue(base2.tzinfo is None)
3159 self.assertTrue(base2.tzname() is None)
3160
3161 # Ensure we can add one.
3162 base3 = base2.replace(tzinfo=z100)
3163 self.assertEqual(base, base3)
3164 self.assertTrue(base.tzinfo is base3.tzinfo)
3165
3166 # Out of bounds.
3167 base = cls(2000, 2, 29)
3168 self.assertRaises(ValueError, base.replace, year=2001)
3169
3170 def test_more_astimezone(self):
3171 # The inherited test_astimezone covered some trivial and error cases.
3172 fnone = FixedOffset(None, "None")
3173 f44m = FixedOffset(44, "44")
3174 fm5h = FixedOffset(-timedelta(hours=5), "m300")
3175
3176 dt = self.theclass.now(tz=f44m)
3177 self.assertTrue(dt.tzinfo is f44m)
3178 # Replacing with degenerate tzinfo raises an exception.
3179 self.assertRaises(ValueError, dt.astimezone, fnone)
3180 # Ditto with None tz.
3181 self.assertRaises(TypeError, dt.astimezone, None)
3182 # Replacing with same tzinfo makes no change.
3183 x = dt.astimezone(dt.tzinfo)
3184 self.assertTrue(x.tzinfo is f44m)
3185 self.assertEqual(x.date(), dt.date())
3186 self.assertEqual(x.time(), dt.time())
3187
3188 # Replacing with different tzinfo does adjust.
3189 got = dt.astimezone(fm5h)
3190 self.assertTrue(got.tzinfo is fm5h)
3191 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
3192 expected = dt - dt.utcoffset() # in effect, convert to UTC
3193 expected += fm5h.utcoffset(dt) # and from there to local time
3194 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
3195 self.assertEqual(got.date(), expected.date())
3196 self.assertEqual(got.time(), expected.time())
3197 self.assertEqual(got.timetz(), expected.timetz())
3198 self.assertTrue(got.tzinfo is expected.tzinfo)
3199 self.assertEqual(got, expected)
3200
3201 def test_aware_subtract(self):
3202 cls = self.theclass
3203
3204 # Ensure that utcoffset() is ignored when the operands have the
3205 # same tzinfo member.
3206 class OperandDependentOffset(tzinfo):
3207 def utcoffset(self, t):
3208 if t.minute < 10:
3209 # d0 and d1 equal after adjustment
3210 return timedelta(minutes=t.minute)
3211 else:
3212 # d2 off in the weeds
3213 return timedelta(minutes=59)
3214
3215 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
3216 d0 = base.replace(minute=3)
3217 d1 = base.replace(minute=9)
3218 d2 = base.replace(minute=11)
3219 for x in d0, d1, d2:
3220 for y in d0, d1, d2:
3221 got = x - y
3222 expected = timedelta(minutes=x.minute - y.minute)
3223 self.assertEqual(got, expected)
3224
3225 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
3226 # ignored.
3227 base = cls(8, 9, 10, 11, 12, 13, 14)
3228 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
3229 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
3230 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
3231 for x in d0, d1, d2:
3232 for y in d0, d1, d2:
3233 got = x - y
3234 if (x is d0 or x is d1) and (y is d0 or y is d1):
3235 expected = timedelta(0)
3236 elif x is y is d2:
3237 expected = timedelta(0)
3238 elif x is d2:
3239 expected = timedelta(minutes=(11-59)-0)
3240 else:
3241 assert y is d2
3242 expected = timedelta(minutes=0-(11-59))
3243 self.assertEqual(got, expected)
3244
3245 def test_mixed_compare(self):
3246 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
3247 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
3248 self.assertEqual(t1, t2)
3249 t2 = t2.replace(tzinfo=None)
3250 self.assertEqual(t1, t2)
3251 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3252 self.assertEqual(t1, t2)
3253 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
3254 self.assertRaises(TypeError, lambda: t1 == t2)
3255
3256 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
3257 class Varies(tzinfo):
3258 def __init__(self):
3259 self.offset = timedelta(minutes=22)
3260 def utcoffset(self, t):
3261 self.offset += timedelta(minutes=1)
3262 return self.offset
3263
3264 v = Varies()
3265 t1 = t2.replace(tzinfo=v)
3266 t2 = t2.replace(tzinfo=v)
3267 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3268 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3269 self.assertEqual(t1, t2)
3270
3271 # But if they're not identical, it isn't ignored.
3272 t2 = t2.replace(tzinfo=Varies())
3273 self.assertTrue(t1 < t2) # t1's offset counter still going up
3274
3275 def test_subclass_datetimetz(self):
3276
3277 class C(self.theclass):
3278 theAnswer = 42
3279
3280 def __new__(cls, *args, **kws):
3281 temp = kws.copy()
3282 extra = temp.pop('extra')
3283 result = self.theclass.__new__(cls, *args, **temp)
3284 result.extra = extra
3285 return result
3286
3287 def newmeth(self, start):
3288 return start + self.hour + self.year
3289
3290 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3291
3292 dt1 = self.theclass(*args)
3293 dt2 = C(*args, **{'extra': 7})
3294
3295 self.assertEqual(dt2.__class__, C)
3296 self.assertEqual(dt2.theAnswer, 42)
3297 self.assertEqual(dt2.extra, 7)
3298 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3299 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
3300
3301# Pain to set up DST-aware tzinfo classes.
3302
3303def first_sunday_on_or_after(dt):
3304 days_to_go = 6 - dt.weekday()
3305 if days_to_go:
3306 dt += timedelta(days_to_go)
3307 return dt
3308
3309ZERO = timedelta(0)
3310MINUTE = timedelta(minutes=1)
3311HOUR = timedelta(hours=1)
3312DAY = timedelta(days=1)
3313# In the US, DST starts at 2am (standard time) on the first Sunday in April.
3314DSTSTART = datetime(1, 4, 1, 2)
3315# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
3316# which is the first Sunday on or after Oct 25. Because we view 1:MM as
3317# being standard time on that day, there is no spelling in local time of
3318# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
3319DSTEND = datetime(1, 10, 25, 1)
3320
3321class USTimeZone(tzinfo):
3322
3323 def __init__(self, hours, reprname, stdname, dstname):
3324 self.stdoffset = timedelta(hours=hours)
3325 self.reprname = reprname
3326 self.stdname = stdname
3327 self.dstname = dstname
3328
3329 def __repr__(self):
3330 return self.reprname
3331
3332 def tzname(self, dt):
3333 if self.dst(dt):
3334 return self.dstname
3335 else:
3336 return self.stdname
3337
3338 def utcoffset(self, dt):
3339 return self.stdoffset + self.dst(dt)
3340
3341 def dst(self, dt):
3342 if dt is None or dt.tzinfo is None:
3343 # An exception instead may be sensible here, in one or more of
3344 # the cases.
3345 return ZERO
3346 assert dt.tzinfo is self
3347
3348 # Find first Sunday in April.
3349 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
3350 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
3351
3352 # Find last Sunday in October.
3353 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
3354 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
3355
3356 # Can't compare naive to aware objects, so strip the timezone from
3357 # dt first.
3358 if start <= dt.replace(tzinfo=None) < end:
3359 return HOUR
3360 else:
3361 return ZERO
3362
3363Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
3364Central = USTimeZone(-6, "Central", "CST", "CDT")
3365Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
3366Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
3367utc_real = FixedOffset(0, "UTC", 0)
3368# For better test coverage, we want another flavor of UTC that's west of
3369# the Eastern and Pacific timezones.
3370utc_fake = FixedOffset(-12*60, "UTCfake", 0)
3371
3372class TestTimezoneConversions(unittest.TestCase):
3373 # The DST switch times for 2002, in std time.
3374 dston = datetime(2002, 4, 7, 2)
3375 dstoff = datetime(2002, 10, 27, 1)
3376
3377 theclass = datetime
3378
3379 # Check a time that's inside DST.
3380 def checkinside(self, dt, tz, utc, dston, dstoff):
3381 self.assertEqual(dt.dst(), HOUR)
3382
3383 # Conversion to our own timezone is always an identity.
3384 self.assertEqual(dt.astimezone(tz), dt)
3385
3386 asutc = dt.astimezone(utc)
3387 there_and_back = asutc.astimezone(tz)
3388
3389 # Conversion to UTC and back isn't always an identity here,
3390 # because there are redundant spellings (in local time) of
3391 # UTC time when DST begins: the clock jumps from 1:59:59
3392 # to 3:00:00, and a local time of 2:MM:SS doesn't really
3393 # make sense then. The classes above treat 2:MM:SS as
3394 # daylight time then (it's "after 2am"), really an alias
3395 # for 1:MM:SS standard time. The latter form is what
3396 # conversion back from UTC produces.
3397 if dt.date() == dston.date() and dt.hour == 2:
3398 # We're in the redundant hour, and coming back from
3399 # UTC gives the 1:MM:SS standard-time spelling.
3400 self.assertEqual(there_and_back + HOUR, dt)
3401 # Although during was considered to be in daylight
3402 # time, there_and_back is not.
3403 self.assertEqual(there_and_back.dst(), ZERO)
3404 # They're the same times in UTC.
3405 self.assertEqual(there_and_back.astimezone(utc),
3406 dt.astimezone(utc))
3407 else:
3408 # We're not in the redundant hour.
3409 self.assertEqual(dt, there_and_back)
3410
3411 # Because we have a redundant spelling when DST begins, there is
3412 # (unforunately) an hour when DST ends that can't be spelled at all in
3413 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
3414 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
3415 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
3416 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
3417 # expressed in local time. Nevertheless, we want conversion back
3418 # from UTC to mimic the local clock's "repeat an hour" behavior.
3419 nexthour_utc = asutc + HOUR
3420 nexthour_tz = nexthour_utc.astimezone(tz)
3421 if dt.date() == dstoff.date() and dt.hour == 0:
3422 # We're in the hour before the last DST hour. The last DST hour
3423 # is ineffable. We want the conversion back to repeat 1:MM.
3424 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3425 nexthour_utc += HOUR
3426 nexthour_tz = nexthour_utc.astimezone(tz)
3427 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3428 else:
3429 self.assertEqual(nexthour_tz - dt, HOUR)
3430
3431 # Check a time that's outside DST.
3432 def checkoutside(self, dt, tz, utc):
3433 self.assertEqual(dt.dst(), ZERO)
3434
3435 # Conversion to our own timezone is always an identity.
3436 self.assertEqual(dt.astimezone(tz), dt)
3437
3438 # Converting to UTC and back is an identity too.
3439 asutc = dt.astimezone(utc)
3440 there_and_back = asutc.astimezone(tz)
3441 self.assertEqual(dt, there_and_back)
3442
3443 def convert_between_tz_and_utc(self, tz, utc):
3444 dston = self.dston.replace(tzinfo=tz)
3445 # Because 1:MM on the day DST ends is taken as being standard time,
3446 # there is no spelling in tz for the last hour of daylight time.
3447 # For purposes of the test, the last hour of DST is 0:MM, which is
3448 # taken as being daylight time (and 1:MM is taken as being standard
3449 # time).
3450 dstoff = self.dstoff.replace(tzinfo=tz)
3451 for delta in (timedelta(weeks=13),
3452 DAY,
3453 HOUR,
3454 timedelta(minutes=1),
3455 timedelta(microseconds=1)):
3456
3457 self.checkinside(dston, tz, utc, dston, dstoff)
3458 for during in dston + delta, dstoff - delta:
3459 self.checkinside(during, tz, utc, dston, dstoff)
3460
3461 self.checkoutside(dstoff, tz, utc)
3462 for outside in dston - delta, dstoff + delta:
3463 self.checkoutside(outside, tz, utc)
3464
3465 def test_easy(self):
3466 # Despite the name of this test, the endcases are excruciating.
3467 self.convert_between_tz_and_utc(Eastern, utc_real)
3468 self.convert_between_tz_and_utc(Pacific, utc_real)
3469 self.convert_between_tz_and_utc(Eastern, utc_fake)
3470 self.convert_between_tz_and_utc(Pacific, utc_fake)
3471 # The next is really dancing near the edge. It works because
3472 # Pacific and Eastern are far enough apart that their "problem
3473 # hours" don't overlap.
3474 self.convert_between_tz_and_utc(Eastern, Pacific)
3475 self.convert_between_tz_and_utc(Pacific, Eastern)
3476 # OTOH, these fail! Don't enable them. The difficulty is that
3477 # the edge case tests assume that every hour is representable in
3478 # the "utc" class. This is always true for a fixed-offset tzinfo
3479 # class (lke utc_real and utc_fake), but not for Eastern or Central.
3480 # For these adjacent DST-aware time zones, the range of time offsets
3481 # tested ends up creating hours in the one that aren't representable
3482 # in the other. For the same reason, we would see failures in the
3483 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
3484 # offset deltas in convert_between_tz_and_utc().
3485 #
3486 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
3487 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
3488
3489 def test_tricky(self):
3490 # 22:00 on day before daylight starts.
3491 fourback = self.dston - timedelta(hours=4)
3492 ninewest = FixedOffset(-9*60, "-0900", 0)
3493 fourback = fourback.replace(tzinfo=ninewest)
3494 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
3495 # 2", we should get the 3 spelling.
3496 # If we plug 22:00 the day before into Eastern, it "looks like std
3497 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
3498 # to 22:00 lands on 2:00, which makes no sense in local time (the
3499 # local clock jumps from 1 to 3). The point here is to make sure we
3500 # get the 3 spelling.
3501 expected = self.dston.replace(hour=3)
3502 got = fourback.astimezone(Eastern).replace(tzinfo=None)
3503 self.assertEqual(expected, got)
3504
3505 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
3506 # case we want the 1:00 spelling.
3507 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
3508 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
3509 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
3510 # spelling.
3511 expected = self.dston.replace(hour=1)
3512 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
3513 self.assertEqual(expected, got)
3514
3515 # Now on the day DST ends, we want "repeat an hour" behavior.
3516 # UTC 4:MM 5:MM 6:MM 7:MM checking these
3517 # EST 23:MM 0:MM 1:MM 2:MM
3518 # EDT 0:MM 1:MM 2:MM 3:MM
3519 # wall 0:MM 1:MM 1:MM 2:MM against these
3520 for utc in utc_real, utc_fake:
3521 for tz in Eastern, Pacific:
3522 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
3523 # Convert that to UTC.
3524 first_std_hour -= tz.utcoffset(None)
3525 # Adjust for possibly fake UTC.
3526 asutc = first_std_hour + utc.utcoffset(None)
3527 # First UTC hour to convert; this is 4:00 when utc=utc_real &
3528 # tz=Eastern.
3529 asutcbase = asutc.replace(tzinfo=utc)
3530 for tzhour in (0, 1, 1, 2):
3531 expectedbase = self.dstoff.replace(hour=tzhour)
3532 for minute in 0, 30, 59:
3533 expected = expectedbase.replace(minute=minute)
3534 asutc = asutcbase.replace(minute=minute)
3535 astz = asutc.astimezone(tz)
3536 self.assertEqual(astz.replace(tzinfo=None), expected)
3537 asutcbase += HOUR
3538
3539
3540 def test_bogus_dst(self):
3541 class ok(tzinfo):
3542 def utcoffset(self, dt): return HOUR
3543 def dst(self, dt): return HOUR
3544
3545 now = self.theclass.now().replace(tzinfo=utc_real)
3546 # Doesn't blow up.
3547 now.astimezone(ok())
3548
3549 # Does blow up.
3550 class notok(ok):
3551 def dst(self, dt): return None
3552 self.assertRaises(ValueError, now.astimezone, notok())
3553
3554 # Sometimes blow up. In the following, tzinfo.dst()
3555 # implementation may return None or not None depending on
3556 # whether DST is assumed to be in effect. In this situation,
3557 # a ValueError should be raised by astimezone().
3558 class tricky_notok(ok):
3559 def dst(self, dt):
3560 if dt.year == 2000:
3561 return None
3562 else:
3563 return 10*HOUR
3564 dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
3565 self.assertRaises(ValueError, dt.astimezone, tricky_notok())
3566
3567 def test_fromutc(self):
3568 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
3569 now = datetime.utcnow().replace(tzinfo=utc_real)
3570 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3571 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
3572 enow = Eastern.fromutc(now) # doesn't blow up
3573 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
3574 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
3575 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
3576
3577 # Always converts UTC to standard time.
3578 class FauxUSTimeZone(USTimeZone):
3579 def fromutc(self, dt):
3580 return dt + self.stdoffset
3581 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
3582
3583 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
3584 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
3585 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
3586
3587 # Check around DST start.
3588 start = self.dston.replace(hour=4, tzinfo=Eastern)
3589 fstart = start.replace(tzinfo=FEastern)
3590 for wall in 23, 0, 1, 3, 4, 5:
3591 expected = start.replace(hour=wall)
3592 if wall == 23:
3593 expected -= timedelta(days=1)
3594 got = Eastern.fromutc(start)
3595 self.assertEqual(expected, got)
3596
3597 expected = fstart + FEastern.stdoffset
3598 got = FEastern.fromutc(fstart)
3599 self.assertEqual(expected, got)
3600
3601 # Ensure astimezone() calls fromutc() too.
3602 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3603 self.assertEqual(expected, got)
3604
3605 start += HOUR
3606 fstart += HOUR
3607
3608 # Check around DST end.
3609 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
3610 fstart = start.replace(tzinfo=FEastern)
3611 for wall in 0, 1, 1, 2, 3, 4:
3612 expected = start.replace(hour=wall)
3613 got = Eastern.fromutc(start)
3614 self.assertEqual(expected, got)
3615
3616 expected = fstart + FEastern.stdoffset
3617 got = FEastern.fromutc(fstart)
3618 self.assertEqual(expected, got)
3619
3620 # Ensure astimezone() calls fromutc() too.
3621 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3622 self.assertEqual(expected, got)
3623
3624 start += HOUR
3625 fstart += HOUR
3626
3627
3628#############################################################################
3629# oddballs
3630
3631class Oddballs(unittest.TestCase):
3632
3633 def test_bug_1028306(self):
3634 # Trying to compare a date to a datetime should act like a mixed-
3635 # type comparison, despite that datetime is a subclass of date.
3636 as_date = date.today()
3637 as_datetime = datetime.combine(as_date, time())
3638 self.assertTrue(as_date != as_datetime)
3639 self.assertTrue(as_datetime != as_date)
3640 self.assertTrue(not as_date == as_datetime)
3641 self.assertTrue(not as_datetime == as_date)
3642 self.assertRaises(TypeError, lambda: as_date < as_datetime)
3643 self.assertRaises(TypeError, lambda: 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
3651 # Neverthelss, comparison should work with the base-class (date)
3652 # projection if use of a date method is forced.
3653 self.assertEqual(as_date.__eq__(as_datetime), True)
3654 different_day = (as_date.day + 1) % 20 + 1
3655 as_different = as_datetime.replace(day= different_day)
3656 self.assertEqual(as_date.__eq__(as_different), False)
3657
3658 # And date should compare with other subclasses of date. If a
3659 # subclass wants to stop this, it's up to the subclass to do so.
3660 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
3661 self.assertEqual(as_date, date_sc)
3662 self.assertEqual(date_sc, as_date)
3663
3664 # Ditto for datetimes.
3665 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
3666 as_date.day, 0, 0, 0)
3667 self.assertEqual(as_datetime, datetime_sc)
3668 self.assertEqual(datetime_sc, as_datetime)
3669
3670def test_main():
3671 support.run_unittest(__name__)
3672
3673if __name__ == "__main__":
3674 test_main()