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