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