blob: 1343f345a8206a213dd974ae063ab89469f2d4ed [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
2028 def test_roundtrip(self):
2029 pass
2030
2031class SubclassTime(time):
2032 sub_var = 1
2033
2034class TestTime(HarmlessMixedComparison, unittest.TestCase):
2035
2036 theclass = time
2037
2038 def test_basic_attributes(self):
2039 t = self.theclass(12, 0)
2040 self.assertEqual(t.hour, 12)
2041 self.assertEqual(t.minute, 0)
2042 self.assertEqual(t.second, 0)
2043 self.assertEqual(t.microsecond, 0)
2044
2045 def test_basic_attributes_nonzero(self):
2046 # Make sure all attributes are non-zero so bugs in
2047 # bit-shifting access show up.
2048 t = self.theclass(12, 59, 59, 8000)
2049 self.assertEqual(t.hour, 12)
2050 self.assertEqual(t.minute, 59)
2051 self.assertEqual(t.second, 59)
2052 self.assertEqual(t.microsecond, 8000)
2053
2054 def test_roundtrip(self):
2055 t = self.theclass(1, 2, 3, 4)
2056
2057 # Verify t -> string -> time identity.
2058 s = repr(t)
2059 self.assertTrue(s.startswith('datetime.'))
2060 s = s[9:]
2061 t2 = eval(s)
2062 self.assertEqual(t, t2)
2063
2064 # Verify identity via reconstructing from pieces.
2065 t2 = self.theclass(t.hour, t.minute, t.second,
2066 t.microsecond)
2067 self.assertEqual(t, t2)
2068
2069 def test_comparing(self):
2070 args = [1, 2, 3, 4]
2071 t1 = self.theclass(*args)
2072 t2 = self.theclass(*args)
2073 self.assertEqual(t1, t2)
2074 self.assertTrue(t1 <= t2)
2075 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002076 self.assertFalse(t1 != t2)
2077 self.assertFalse(t1 < t2)
2078 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002079
2080 for i in range(len(args)):
2081 newargs = args[:]
2082 newargs[i] = args[i] + 1
2083 t2 = self.theclass(*newargs) # this is larger than t1
2084 self.assertTrue(t1 < t2)
2085 self.assertTrue(t2 > t1)
2086 self.assertTrue(t1 <= t2)
2087 self.assertTrue(t2 >= t1)
2088 self.assertTrue(t1 != t2)
2089 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002090 self.assertFalse(t1 == t2)
2091 self.assertFalse(t2 == t1)
2092 self.assertFalse(t1 > t2)
2093 self.assertFalse(t2 < t1)
2094 self.assertFalse(t1 >= t2)
2095 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002096
2097 for badarg in OTHERSTUFF:
2098 self.assertEqual(t1 == badarg, False)
2099 self.assertEqual(t1 != badarg, True)
2100 self.assertEqual(badarg == t1, False)
2101 self.assertEqual(badarg != t1, True)
2102
2103 self.assertRaises(TypeError, lambda: t1 <= badarg)
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: badarg <= t1)
2108 self.assertRaises(TypeError, lambda: badarg < t1)
2109 self.assertRaises(TypeError, lambda: badarg > t1)
2110 self.assertRaises(TypeError, lambda: badarg >= t1)
2111
2112 def test_bad_constructor_arguments(self):
2113 # bad hours
2114 self.theclass(0, 0) # no exception
2115 self.theclass(23, 0) # no exception
2116 self.assertRaises(ValueError, self.theclass, -1, 0)
2117 self.assertRaises(ValueError, self.theclass, 24, 0)
2118 # bad minutes
2119 self.theclass(23, 0) # no exception
2120 self.theclass(23, 59) # no exception
2121 self.assertRaises(ValueError, self.theclass, 23, -1)
2122 self.assertRaises(ValueError, self.theclass, 23, 60)
2123 # bad seconds
2124 self.theclass(23, 59, 0) # no exception
2125 self.theclass(23, 59, 59) # no exception
2126 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
2127 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
2128 # bad microseconds
2129 self.theclass(23, 59, 59, 0) # no exception
2130 self.theclass(23, 59, 59, 999999) # no exception
2131 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
2132 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
2133
2134 def test_hash_equality(self):
2135 d = self.theclass(23, 30, 17)
2136 e = self.theclass(23, 30, 17)
2137 self.assertEqual(d, e)
2138 self.assertEqual(hash(d), hash(e))
2139
2140 dic = {d: 1}
2141 dic[e] = 2
2142 self.assertEqual(len(dic), 1)
2143 self.assertEqual(dic[d], 2)
2144 self.assertEqual(dic[e], 2)
2145
2146 d = self.theclass(0, 5, 17)
2147 e = self.theclass(0, 5, 17)
2148 self.assertEqual(d, e)
2149 self.assertEqual(hash(d), hash(e))
2150
2151 dic = {d: 1}
2152 dic[e] = 2
2153 self.assertEqual(len(dic), 1)
2154 self.assertEqual(dic[d], 2)
2155 self.assertEqual(dic[e], 2)
2156
2157 def test_isoformat(self):
2158 t = self.theclass(4, 5, 1, 123)
2159 self.assertEqual(t.isoformat(), "04:05:01.000123")
2160 self.assertEqual(t.isoformat(), str(t))
2161
2162 t = self.theclass()
2163 self.assertEqual(t.isoformat(), "00:00:00")
2164 self.assertEqual(t.isoformat(), str(t))
2165
2166 t = self.theclass(microsecond=1)
2167 self.assertEqual(t.isoformat(), "00:00:00.000001")
2168 self.assertEqual(t.isoformat(), str(t))
2169
2170 t = self.theclass(microsecond=10)
2171 self.assertEqual(t.isoformat(), "00:00:00.000010")
2172 self.assertEqual(t.isoformat(), str(t))
2173
2174 t = self.theclass(microsecond=100)
2175 self.assertEqual(t.isoformat(), "00:00:00.000100")
2176 self.assertEqual(t.isoformat(), str(t))
2177
2178 t = self.theclass(microsecond=1000)
2179 self.assertEqual(t.isoformat(), "00:00:00.001000")
2180 self.assertEqual(t.isoformat(), str(t))
2181
2182 t = self.theclass(microsecond=10000)
2183 self.assertEqual(t.isoformat(), "00:00:00.010000")
2184 self.assertEqual(t.isoformat(), str(t))
2185
2186 t = self.theclass(microsecond=100000)
2187 self.assertEqual(t.isoformat(), "00:00:00.100000")
2188 self.assertEqual(t.isoformat(), str(t))
2189
2190 def test_1653736(self):
2191 # verify it doesn't accept extra keyword arguments
2192 t = self.theclass(second=1)
2193 self.assertRaises(TypeError, t.isoformat, foo=3)
2194
2195 def test_strftime(self):
2196 t = self.theclass(1, 2, 3, 4)
2197 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
2198 # A naive object replaces %z and %Z with empty strings.
2199 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
2200
2201 def test_format(self):
2202 t = self.theclass(1, 2, 3, 4)
2203 self.assertEqual(t.__format__(''), str(t))
2204
2205 # check that a derived class's __str__() gets called
2206 class A(self.theclass):
2207 def __str__(self):
2208 return 'A'
2209 a = A(1, 2, 3, 4)
2210 self.assertEqual(a.__format__(''), 'A')
2211
2212 # check that a derived class's strftime gets called
2213 class B(self.theclass):
2214 def strftime(self, format_spec):
2215 return 'B'
2216 b = B(1, 2, 3, 4)
2217 self.assertEqual(b.__format__(''), str(t))
2218
2219 for fmt in ['%H %M %S',
2220 ]:
2221 self.assertEqual(t.__format__(fmt), t.strftime(fmt))
2222 self.assertEqual(a.__format__(fmt), t.strftime(fmt))
2223 self.assertEqual(b.__format__(fmt), 'B')
2224
2225 def test_str(self):
2226 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
2227 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
2228 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
2229 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
2230 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
2231
2232 def test_repr(self):
2233 name = 'datetime.' + self.theclass.__name__
2234 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
2235 "%s(1, 2, 3, 4)" % name)
2236 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
2237 "%s(10, 2, 3, 4000)" % name)
2238 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
2239 "%s(0, 2, 3, 400000)" % name)
2240 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
2241 "%s(12, 2, 3)" % name)
2242 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
2243 "%s(23, 15)" % name)
2244
2245 def test_resolution_info(self):
2246 self.assertIsInstance(self.theclass.min, self.theclass)
2247 self.assertIsInstance(self.theclass.max, self.theclass)
2248 self.assertIsInstance(self.theclass.resolution, timedelta)
2249 self.assertTrue(self.theclass.max > self.theclass.min)
2250
2251 def test_pickling(self):
2252 args = 20, 59, 16, 64**2
2253 orig = self.theclass(*args)
2254 for pickler, unpickler, proto in pickle_choices:
2255 green = pickler.dumps(orig, proto)
2256 derived = unpickler.loads(green)
2257 self.assertEqual(orig, derived)
2258
2259 def test_pickling_subclass_time(self):
2260 args = 20, 59, 16, 64**2
2261 orig = SubclassTime(*args)
2262 for pickler, unpickler, proto in pickle_choices:
2263 green = pickler.dumps(orig, proto)
2264 derived = unpickler.loads(green)
2265 self.assertEqual(orig, derived)
2266
2267 def test_bool(self):
2268 cls = self.theclass
2269 self.assertTrue(cls(1))
2270 self.assertTrue(cls(0, 1))
2271 self.assertTrue(cls(0, 0, 1))
2272 self.assertTrue(cls(0, 0, 0, 1))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002273 self.assertFalse(cls(0))
2274 self.assertFalse(cls())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002275
2276 def test_replace(self):
2277 cls = self.theclass
2278 args = [1, 2, 3, 4]
2279 base = cls(*args)
2280 self.assertEqual(base, base.replace())
2281
2282 i = 0
2283 for name, newval in (("hour", 5),
2284 ("minute", 6),
2285 ("second", 7),
2286 ("microsecond", 8)):
2287 newargs = args[:]
2288 newargs[i] = newval
2289 expected = cls(*newargs)
2290 got = base.replace(**{name: newval})
2291 self.assertEqual(expected, got)
2292 i += 1
2293
2294 # Out of bounds.
2295 base = cls(1)
2296 self.assertRaises(ValueError, base.replace, hour=24)
2297 self.assertRaises(ValueError, base.replace, minute=-1)
2298 self.assertRaises(ValueError, base.replace, second=100)
2299 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2300
2301 def test_subclass_time(self):
2302
2303 class C(self.theclass):
2304 theAnswer = 42
2305
2306 def __new__(cls, *args, **kws):
2307 temp = kws.copy()
2308 extra = temp.pop('extra')
2309 result = self.theclass.__new__(cls, *args, **temp)
2310 result.extra = extra
2311 return result
2312
2313 def newmeth(self, start):
2314 return start + self.hour + self.second
2315
2316 args = 4, 5, 6
2317
2318 dt1 = self.theclass(*args)
2319 dt2 = C(*args, **{'extra': 7})
2320
2321 self.assertEqual(dt2.__class__, C)
2322 self.assertEqual(dt2.theAnswer, 42)
2323 self.assertEqual(dt2.extra, 7)
2324 self.assertEqual(dt1.isoformat(), dt2.isoformat())
2325 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2326
2327 def test_backdoor_resistance(self):
2328 # see TestDate.test_backdoor_resistance().
2329 base = '2:59.0'
2330 for hour_byte in ' ', '9', chr(24), '\xff':
2331 self.assertRaises(TypeError, self.theclass,
2332 hour_byte + base[1:])
2333
2334# A mixin for classes with a tzinfo= argument. Subclasses must define
2335# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
2336# must be legit (which is true for time and datetime).
2337class TZInfoBase:
2338
2339 def test_argument_passing(self):
2340 cls = self.theclass
2341 # A datetime passes itself on, a time passes None.
2342 class introspective(tzinfo):
2343 def tzname(self, dt): return dt and "real" or "none"
2344 def utcoffset(self, dt):
2345 return timedelta(minutes = dt and 42 or -42)
2346 dst = utcoffset
2347
2348 obj = cls(1, 2, 3, tzinfo=introspective())
2349
2350 expected = cls is time and "none" or "real"
2351 self.assertEqual(obj.tzname(), expected)
2352
2353 expected = timedelta(minutes=(cls is time and -42 or 42))
2354 self.assertEqual(obj.utcoffset(), expected)
2355 self.assertEqual(obj.dst(), expected)
2356
2357 def test_bad_tzinfo_classes(self):
2358 cls = self.theclass
2359 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
2360
2361 class NiceTry(object):
2362 def __init__(self): pass
2363 def utcoffset(self, dt): pass
2364 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
2365
2366 class BetterTry(tzinfo):
2367 def __init__(self): pass
2368 def utcoffset(self, dt): pass
2369 b = BetterTry()
2370 t = cls(1, 1, 1, tzinfo=b)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002371 self.assertIs(t.tzinfo, b)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002372
2373 def test_utc_offset_out_of_bounds(self):
2374 class Edgy(tzinfo):
2375 def __init__(self, offset):
2376 self.offset = timedelta(minutes=offset)
2377 def utcoffset(self, dt):
2378 return self.offset
2379
2380 cls = self.theclass
2381 for offset, legit in ((-1440, False),
2382 (-1439, True),
2383 (1439, True),
2384 (1440, False)):
2385 if cls is time:
2386 t = cls(1, 2, 3, tzinfo=Edgy(offset))
2387 elif cls is datetime:
2388 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
2389 else:
2390 assert 0, "impossible"
2391 if legit:
2392 aofs = abs(offset)
2393 h, m = divmod(aofs, 60)
2394 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
2395 if isinstance(t, datetime):
2396 t = t.timetz()
2397 self.assertEqual(str(t), "01:02:03" + tag)
2398 else:
2399 self.assertRaises(ValueError, str, t)
2400
2401 def test_tzinfo_classes(self):
2402 cls = self.theclass
2403 class C1(tzinfo):
2404 def utcoffset(self, dt): return None
2405 def dst(self, dt): return None
2406 def tzname(self, dt): return None
2407 for t in (cls(1, 1, 1),
2408 cls(1, 1, 1, tzinfo=None),
2409 cls(1, 1, 1, tzinfo=C1())):
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002410 self.assertIsNone(t.utcoffset())
2411 self.assertIsNone(t.dst())
2412 self.assertIsNone(t.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002413
2414 class C3(tzinfo):
2415 def utcoffset(self, dt): return timedelta(minutes=-1439)
2416 def dst(self, dt): return timedelta(minutes=1439)
2417 def tzname(self, dt): return "aname"
2418 t = cls(1, 1, 1, tzinfo=C3())
2419 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
2420 self.assertEqual(t.dst(), timedelta(minutes=1439))
2421 self.assertEqual(t.tzname(), "aname")
2422
2423 # Wrong types.
2424 class C4(tzinfo):
2425 def utcoffset(self, dt): return "aname"
2426 def dst(self, dt): return 7
2427 def tzname(self, dt): return 0
2428 t = cls(1, 1, 1, tzinfo=C4())
2429 self.assertRaises(TypeError, t.utcoffset)
2430 self.assertRaises(TypeError, t.dst)
2431 self.assertRaises(TypeError, t.tzname)
2432
2433 # Offset out of range.
2434 class C6(tzinfo):
2435 def utcoffset(self, dt): return timedelta(hours=-24)
2436 def dst(self, dt): return timedelta(hours=24)
2437 t = cls(1, 1, 1, tzinfo=C6())
2438 self.assertRaises(ValueError, t.utcoffset)
2439 self.assertRaises(ValueError, t.dst)
2440
2441 # Not a whole number of minutes.
2442 class C7(tzinfo):
2443 def utcoffset(self, dt): return timedelta(seconds=61)
2444 def dst(self, dt): return timedelta(microseconds=-81)
2445 t = cls(1, 1, 1, tzinfo=C7())
2446 self.assertRaises(ValueError, t.utcoffset)
2447 self.assertRaises(ValueError, t.dst)
2448
2449 def test_aware_compare(self):
2450 cls = self.theclass
2451
2452 # Ensure that utcoffset() gets ignored if the comparands have
2453 # the same tzinfo member.
2454 class OperandDependentOffset(tzinfo):
2455 def utcoffset(self, t):
2456 if t.minute < 10:
2457 # d0 and d1 equal after adjustment
2458 return timedelta(minutes=t.minute)
2459 else:
2460 # d2 off in the weeds
2461 return timedelta(minutes=59)
2462
2463 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
2464 d0 = base.replace(minute=3)
2465 d1 = base.replace(minute=9)
2466 d2 = base.replace(minute=11)
2467 for x in d0, d1, d2:
2468 for y in d0, d1, d2:
2469 for op in lt, le, gt, ge, eq, ne:
2470 got = op(x, y)
2471 expected = op(x.minute, y.minute)
2472 self.assertEqual(got, expected)
2473
2474 # However, if they're different members, uctoffset is not ignored.
2475 # Note that a time can't actually have an operand-depedent offset,
2476 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
2477 # so skip this test for time.
2478 if cls is not time:
2479 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2480 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2481 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2482 for x in d0, d1, d2:
2483 for y in d0, d1, d2:
2484 got = (x > y) - (x < y)
2485 if (x is d0 or x is d1) and (y is d0 or y is d1):
2486 expected = 0
2487 elif x is y is d2:
2488 expected = 0
2489 elif x is d2:
2490 expected = -1
2491 else:
2492 assert y is d2
2493 expected = 1
2494 self.assertEqual(got, expected)
2495
2496
2497# Testing time objects with a non-None tzinfo.
2498class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
2499 theclass = time
2500
2501 def test_empty(self):
2502 t = self.theclass()
2503 self.assertEqual(t.hour, 0)
2504 self.assertEqual(t.minute, 0)
2505 self.assertEqual(t.second, 0)
2506 self.assertEqual(t.microsecond, 0)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002507 self.assertIsNone(t.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002508
2509 def test_zones(self):
2510 est = FixedOffset(-300, "EST", 1)
2511 utc = FixedOffset(0, "UTC", -2)
2512 met = FixedOffset(60, "MET", 3)
2513 t1 = time( 7, 47, tzinfo=est)
2514 t2 = time(12, 47, tzinfo=utc)
2515 t3 = time(13, 47, tzinfo=met)
2516 t4 = time(microsecond=40)
2517 t5 = time(microsecond=40, tzinfo=utc)
2518
2519 self.assertEqual(t1.tzinfo, est)
2520 self.assertEqual(t2.tzinfo, utc)
2521 self.assertEqual(t3.tzinfo, met)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002522 self.assertIsNone(t4.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002523 self.assertEqual(t5.tzinfo, utc)
2524
2525 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2526 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2527 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002528 self.assertIsNone(t4.utcoffset())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002529 self.assertRaises(TypeError, t1.utcoffset, "no args")
2530
2531 self.assertEqual(t1.tzname(), "EST")
2532 self.assertEqual(t2.tzname(), "UTC")
2533 self.assertEqual(t3.tzname(), "MET")
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002534 self.assertIsNone(t4.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002535 self.assertRaises(TypeError, t1.tzname, "no args")
2536
2537 self.assertEqual(t1.dst(), timedelta(minutes=1))
2538 self.assertEqual(t2.dst(), timedelta(minutes=-2))
2539 self.assertEqual(t3.dst(), timedelta(minutes=3))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002540 self.assertIsNone(t4.dst())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002541 self.assertRaises(TypeError, t1.dst, "no args")
2542
2543 self.assertEqual(hash(t1), hash(t2))
2544 self.assertEqual(hash(t1), hash(t3))
2545 self.assertEqual(hash(t2), hash(t3))
2546
2547 self.assertEqual(t1, t2)
2548 self.assertEqual(t1, t3)
2549 self.assertEqual(t2, t3)
Alexander Belopolsky08313822012-06-15 20:19:47 -04002550 self.assertNotEqual(t4, t5) # mixed tz-aware & naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002551 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
2552 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
2553
2554 self.assertEqual(str(t1), "07:47:00-05:00")
2555 self.assertEqual(str(t2), "12:47:00+00:00")
2556 self.assertEqual(str(t3), "13:47:00+01:00")
2557 self.assertEqual(str(t4), "00:00:00.000040")
2558 self.assertEqual(str(t5), "00:00:00.000040+00:00")
2559
2560 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
2561 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
2562 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
2563 self.assertEqual(t4.isoformat(), "00:00:00.000040")
2564 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
2565
2566 d = 'datetime.time'
2567 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
2568 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
2569 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
2570 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
2571 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
2572
2573 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
2574 "07:47:00 %Z=EST %z=-0500")
2575 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
2576 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
2577
2578 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
2579 t1 = time(23, 59, tzinfo=yuck)
2580 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
2581 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
2582
2583 # Check that an invalid tzname result raises an exception.
2584 class Badtzname(tzinfo):
Alexander Belopolskye239d232010-12-08 23:31:48 +00002585 tz = 42
2586 def tzname(self, dt): return self.tz
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002587 t = time(2, 3, 4, tzinfo=Badtzname())
2588 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
2589 self.assertRaises(TypeError, t.strftime, "%Z")
2590
Alexander Belopolskye239d232010-12-08 23:31:48 +00002591 # Issue #6697:
2592 if '_Fast' in str(type(self)):
2593 Badtzname.tz = '\ud800'
2594 self.assertRaises(ValueError, t.strftime, "%Z")
2595
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002596 def test_hash_edge_cases(self):
2597 # Offsets that overflow a basic time.
2598 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2599 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2600 self.assertEqual(hash(t1), hash(t2))
2601
2602 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2603 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2604 self.assertEqual(hash(t1), hash(t2))
2605
2606 def test_pickling(self):
2607 # Try one without a tzinfo.
2608 args = 20, 59, 16, 64**2
2609 orig = self.theclass(*args)
2610 for pickler, unpickler, proto in pickle_choices:
2611 green = pickler.dumps(orig, proto)
2612 derived = unpickler.loads(green)
2613 self.assertEqual(orig, derived)
2614
2615 # Try one with a tzinfo.
2616 tinfo = PicklableFixedOffset(-300, 'cookie')
2617 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
2618 for pickler, unpickler, proto in pickle_choices:
2619 green = pickler.dumps(orig, proto)
2620 derived = unpickler.loads(green)
2621 self.assertEqual(orig, derived)
2622 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
2623 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2624 self.assertEqual(derived.tzname(), 'cookie')
2625
2626 def test_more_bool(self):
2627 # Test cases with non-None tzinfo.
2628 cls = self.theclass
2629
2630 t = cls(0, tzinfo=FixedOffset(-300, ""))
2631 self.assertTrue(t)
2632
2633 t = cls(5, tzinfo=FixedOffset(-300, ""))
2634 self.assertTrue(t)
2635
2636 t = cls(5, tzinfo=FixedOffset(300, ""))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002637 self.assertFalse(t)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002638
2639 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002640 self.assertFalse(t)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002641
2642 # Mostly ensuring this doesn't overflow internally.
2643 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
2644 self.assertTrue(t)
2645
2646 # But this should yield a value error -- the utcoffset is bogus.
2647 t = cls(0, tzinfo=FixedOffset(24*60, ""))
2648 self.assertRaises(ValueError, lambda: bool(t))
2649
2650 # Likewise.
2651 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
2652 self.assertRaises(ValueError, lambda: bool(t))
2653
2654 def test_replace(self):
2655 cls = self.theclass
2656 z100 = FixedOffset(100, "+100")
2657 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2658 args = [1, 2, 3, 4, z100]
2659 base = cls(*args)
2660 self.assertEqual(base, base.replace())
2661
2662 i = 0
2663 for name, newval in (("hour", 5),
2664 ("minute", 6),
2665 ("second", 7),
2666 ("microsecond", 8),
2667 ("tzinfo", zm200)):
2668 newargs = args[:]
2669 newargs[i] = newval
2670 expected = cls(*newargs)
2671 got = base.replace(**{name: newval})
2672 self.assertEqual(expected, got)
2673 i += 1
2674
2675 # Ensure we can get rid of a tzinfo.
2676 self.assertEqual(base.tzname(), "+100")
2677 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002678 self.assertIsNone(base2.tzinfo)
2679 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002680
2681 # Ensure we can add one.
2682 base3 = base2.replace(tzinfo=z100)
2683 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002684 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002685
2686 # Out of bounds.
2687 base = cls(1)
2688 self.assertRaises(ValueError, base.replace, hour=24)
2689 self.assertRaises(ValueError, base.replace, minute=-1)
2690 self.assertRaises(ValueError, base.replace, second=100)
2691 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2692
2693 def test_mixed_compare(self):
2694 t1 = time(1, 2, 3)
2695 t2 = time(1, 2, 3)
2696 self.assertEqual(t1, t2)
2697 t2 = t2.replace(tzinfo=None)
2698 self.assertEqual(t1, t2)
2699 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2700 self.assertEqual(t1, t2)
2701 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04002702 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002703
2704 # In time w/ identical tzinfo objects, utcoffset is ignored.
2705 class Varies(tzinfo):
2706 def __init__(self):
2707 self.offset = timedelta(minutes=22)
2708 def utcoffset(self, t):
2709 self.offset += timedelta(minutes=1)
2710 return self.offset
2711
2712 v = Varies()
2713 t1 = t2.replace(tzinfo=v)
2714 t2 = t2.replace(tzinfo=v)
2715 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2716 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2717 self.assertEqual(t1, t2)
2718
2719 # But if they're not identical, it isn't ignored.
2720 t2 = t2.replace(tzinfo=Varies())
2721 self.assertTrue(t1 < t2) # t1's offset counter still going up
2722
2723 def test_subclass_timetz(self):
2724
2725 class C(self.theclass):
2726 theAnswer = 42
2727
2728 def __new__(cls, *args, **kws):
2729 temp = kws.copy()
2730 extra = temp.pop('extra')
2731 result = self.theclass.__new__(cls, *args, **temp)
2732 result.extra = extra
2733 return result
2734
2735 def newmeth(self, start):
2736 return start + self.hour + self.second
2737
2738 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2739
2740 dt1 = self.theclass(*args)
2741 dt2 = C(*args, **{'extra': 7})
2742
2743 self.assertEqual(dt2.__class__, C)
2744 self.assertEqual(dt2.theAnswer, 42)
2745 self.assertEqual(dt2.extra, 7)
2746 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2747 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2748
2749
2750# Testing datetime objects with a non-None tzinfo.
2751
2752class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
2753 theclass = datetime
2754
2755 def test_trivial(self):
2756 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2757 self.assertEqual(dt.year, 1)
2758 self.assertEqual(dt.month, 2)
2759 self.assertEqual(dt.day, 3)
2760 self.assertEqual(dt.hour, 4)
2761 self.assertEqual(dt.minute, 5)
2762 self.assertEqual(dt.second, 6)
2763 self.assertEqual(dt.microsecond, 7)
2764 self.assertEqual(dt.tzinfo, None)
2765
2766 def test_even_more_compare(self):
2767 # The test_compare() and test_more_compare() inherited from TestDate
2768 # and TestDateTime covered non-tzinfo cases.
2769
2770 # Smallest possible after UTC adjustment.
2771 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2772 # Largest possible after UTC adjustment.
2773 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2774 tzinfo=FixedOffset(-1439, ""))
2775
2776 # Make sure those compare correctly, and w/o overflow.
2777 self.assertTrue(t1 < t2)
2778 self.assertTrue(t1 != t2)
2779 self.assertTrue(t2 > t1)
2780
2781 self.assertEqual(t1, t1)
2782 self.assertEqual(t2, t2)
2783
2784 # Equal afer adjustment.
2785 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2786 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2787 self.assertEqual(t1, t2)
2788
2789 # Change t1 not to subtract a minute, and t1 should be larger.
2790 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2791 self.assertTrue(t1 > t2)
2792
2793 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2794 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2795 self.assertTrue(t1 < t2)
2796
2797 # Back to the original t1, but make seconds resolve it.
2798 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2799 second=1)
2800 self.assertTrue(t1 > t2)
2801
2802 # Likewise, but make microseconds resolve it.
2803 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2804 microsecond=1)
2805 self.assertTrue(t1 > t2)
2806
Alexander Belopolsky08313822012-06-15 20:19:47 -04002807 # Make t2 naive and it should differ.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002808 t2 = self.theclass.min
Alexander Belopolsky08313822012-06-15 20:19:47 -04002809 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002810 self.assertEqual(t2, t2)
2811
2812 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2813 class Naive(tzinfo):
2814 def utcoffset(self, dt): return None
2815 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
Alexander Belopolsky08313822012-06-15 20:19:47 -04002816 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002817 self.assertEqual(t2, t2)
2818
2819 # OTOH, it's OK to compare two of these mixing the two ways of being
2820 # naive.
2821 t1 = self.theclass(5, 6, 7)
2822 self.assertEqual(t1, t2)
2823
2824 # Try a bogus uctoffset.
2825 class Bogus(tzinfo):
2826 def utcoffset(self, dt):
2827 return timedelta(minutes=1440) # out of bounds
2828 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2829 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
2830 self.assertRaises(ValueError, lambda: t1 == t2)
2831
2832 def test_pickling(self):
2833 # Try one without a tzinfo.
2834 args = 6, 7, 23, 20, 59, 1, 64**2
2835 orig = self.theclass(*args)
2836 for pickler, unpickler, proto in pickle_choices:
2837 green = pickler.dumps(orig, proto)
2838 derived = unpickler.loads(green)
2839 self.assertEqual(orig, derived)
2840
2841 # Try one with a tzinfo.
2842 tinfo = PicklableFixedOffset(-300, 'cookie')
2843 orig = self.theclass(*args, **{'tzinfo': tinfo})
2844 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
2845 for pickler, unpickler, proto in pickle_choices:
2846 green = pickler.dumps(orig, proto)
2847 derived = unpickler.loads(green)
2848 self.assertEqual(orig, derived)
2849 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
2850 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2851 self.assertEqual(derived.tzname(), 'cookie')
2852
2853 def test_extreme_hashes(self):
2854 # If an attempt is made to hash these via subtracting the offset
2855 # then hashing a datetime object, OverflowError results. The
2856 # Python implementation used to blow up here.
2857 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2858 hash(t)
2859 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2860 tzinfo=FixedOffset(-1439, ""))
2861 hash(t)
2862
2863 # OTOH, an OOB offset should blow up.
2864 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2865 self.assertRaises(ValueError, hash, t)
2866
2867 def test_zones(self):
2868 est = FixedOffset(-300, "EST")
2869 utc = FixedOffset(0, "UTC")
2870 met = FixedOffset(60, "MET")
2871 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2872 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2873 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
2874 self.assertEqual(t1.tzinfo, est)
2875 self.assertEqual(t2.tzinfo, utc)
2876 self.assertEqual(t3.tzinfo, met)
2877 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2878 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2879 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
2880 self.assertEqual(t1.tzname(), "EST")
2881 self.assertEqual(t2.tzname(), "UTC")
2882 self.assertEqual(t3.tzname(), "MET")
2883 self.assertEqual(hash(t1), hash(t2))
2884 self.assertEqual(hash(t1), hash(t3))
2885 self.assertEqual(hash(t2), hash(t3))
2886 self.assertEqual(t1, t2)
2887 self.assertEqual(t1, t3)
2888 self.assertEqual(t2, t3)
2889 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2890 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2891 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
2892 d = 'datetime.datetime(2002, 3, 19, '
2893 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2894 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2895 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2896
2897 def test_combine(self):
2898 met = FixedOffset(60, "MET")
2899 d = date(2002, 3, 4)
2900 tz = time(18, 45, 3, 1234, tzinfo=met)
2901 dt = datetime.combine(d, tz)
2902 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
2903 tzinfo=met))
2904
2905 def test_extract(self):
2906 met = FixedOffset(60, "MET")
2907 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2908 self.assertEqual(dt.date(), date(2002, 3, 4))
2909 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2910 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
2911
2912 def test_tz_aware_arithmetic(self):
2913 import random
2914
2915 now = self.theclass.now()
2916 tz55 = FixedOffset(-330, "west 5:30")
2917 timeaware = now.time().replace(tzinfo=tz55)
2918 nowaware = self.theclass.combine(now.date(), timeaware)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002919 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002920 self.assertEqual(nowaware.timetz(), timeaware)
2921
2922 # Can't mix aware and non-aware.
2923 self.assertRaises(TypeError, lambda: now - nowaware)
2924 self.assertRaises(TypeError, lambda: nowaware - now)
2925
2926 # And adding datetime's doesn't make sense, aware or not.
2927 self.assertRaises(TypeError, lambda: now + nowaware)
2928 self.assertRaises(TypeError, lambda: nowaware + now)
2929 self.assertRaises(TypeError, lambda: nowaware + nowaware)
2930
2931 # Subtracting should yield 0.
2932 self.assertEqual(now - now, timedelta(0))
2933 self.assertEqual(nowaware - nowaware, timedelta(0))
2934
2935 # Adding a delta should preserve tzinfo.
2936 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
2937 nowawareplus = nowaware + delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002938 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002939 nowawareplus2 = delta + nowaware
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002940 self.assertIs(nowawareplus2.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002941 self.assertEqual(nowawareplus, nowawareplus2)
2942
2943 # that - delta should be what we started with, and that - what we
2944 # started with should be delta.
2945 diff = nowawareplus - delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002946 self.assertIs(diff.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002947 self.assertEqual(nowaware, diff)
2948 self.assertRaises(TypeError, lambda: delta - nowawareplus)
2949 self.assertEqual(nowawareplus - nowaware, delta)
2950
2951 # Make up a random timezone.
2952 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
2953 # Attach it to nowawareplus.
2954 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002955 self.assertIs(nowawareplus.tzinfo, tzr)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002956 # Make sure the difference takes the timezone adjustments into account.
2957 got = nowaware - nowawareplus
2958 # Expected: (nowaware base - nowaware offset) -
2959 # (nowawareplus base - nowawareplus offset) =
2960 # (nowaware base - nowawareplus base) +
2961 # (nowawareplus offset - nowaware offset) =
2962 # -delta + nowawareplus offset - nowaware offset
2963 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
2964 self.assertEqual(got, expected)
2965
2966 # Try max possible difference.
2967 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
2968 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2969 tzinfo=FixedOffset(-1439, "max"))
2970 maxdiff = max - min
2971 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
2972 timedelta(minutes=2*1439))
2973 # Different tzinfo, but the same offset
2974 tza = timezone(HOUR, 'A')
2975 tzb = timezone(HOUR, 'B')
2976 delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
2977 self.assertEqual(delta, self.theclass.min - self.theclass.max)
2978
2979 def test_tzinfo_now(self):
2980 meth = self.theclass.now
2981 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2982 base = meth()
2983 # Try with and without naming the keyword.
2984 off42 = FixedOffset(42, "42")
2985 another = meth(off42)
2986 again = meth(tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002987 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002988 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
2989 # Bad argument with and w/o naming the keyword.
2990 self.assertRaises(TypeError, meth, 16)
2991 self.assertRaises(TypeError, meth, tzinfo=16)
2992 # Bad keyword name.
2993 self.assertRaises(TypeError, meth, tinfo=off42)
2994 # Too many args.
2995 self.assertRaises(TypeError, meth, off42, off42)
2996
2997 # We don't know which time zone we're in, and don't have a tzinfo
2998 # class to represent it, so seeing whether a tz argument actually
2999 # does a conversion is tricky.
3000 utc = FixedOffset(0, "utc", 0)
3001 for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
3002 timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
3003 for dummy in range(3):
3004 now = datetime.now(weirdtz)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003005 self.assertIs(now.tzinfo, weirdtz)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003006 utcnow = datetime.utcnow().replace(tzinfo=utc)
3007 now2 = utcnow.astimezone(weirdtz)
3008 if abs(now - now2) < timedelta(seconds=30):
3009 break
3010 # Else the code is broken, or more than 30 seconds passed between
3011 # calls; assuming the latter, just try again.
3012 else:
3013 # Three strikes and we're out.
3014 self.fail("utcnow(), now(tz), or astimezone() may be broken")
3015
3016 def test_tzinfo_fromtimestamp(self):
3017 import time
3018 meth = self.theclass.fromtimestamp
3019 ts = time.time()
3020 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3021 base = meth(ts)
3022 # Try with and without naming the keyword.
3023 off42 = FixedOffset(42, "42")
3024 another = meth(ts, off42)
3025 again = meth(ts, tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003026 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003027 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3028 # Bad argument with and w/o naming the keyword.
3029 self.assertRaises(TypeError, meth, ts, 16)
3030 self.assertRaises(TypeError, meth, ts, tzinfo=16)
3031 # Bad keyword name.
3032 self.assertRaises(TypeError, meth, ts, tinfo=off42)
3033 # Too many args.
3034 self.assertRaises(TypeError, meth, ts, off42, off42)
3035 # Too few args.
3036 self.assertRaises(TypeError, meth)
3037
3038 # Try to make sure tz= actually does some conversion.
3039 timestamp = 1000000000
3040 utcdatetime = datetime.utcfromtimestamp(timestamp)
3041 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
3042 # But on some flavor of Mac, it's nowhere near that. So we can't have
3043 # any idea here what time that actually is, we can only test that
3044 # relative changes match.
3045 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
3046 tz = FixedOffset(utcoffset, "tz", 0)
3047 expected = utcdatetime + utcoffset
3048 got = datetime.fromtimestamp(timestamp, tz)
3049 self.assertEqual(expected, got.replace(tzinfo=None))
3050
3051 def test_tzinfo_utcnow(self):
3052 meth = self.theclass.utcnow
3053 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3054 base = meth()
3055 # Try with and without naming the keyword; for whatever reason,
3056 # utcnow() doesn't accept a tzinfo argument.
3057 off42 = FixedOffset(42, "42")
3058 self.assertRaises(TypeError, meth, off42)
3059 self.assertRaises(TypeError, meth, tzinfo=off42)
3060
3061 def test_tzinfo_utcfromtimestamp(self):
3062 import time
3063 meth = self.theclass.utcfromtimestamp
3064 ts = time.time()
3065 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3066 base = meth(ts)
3067 # Try with and without naming the keyword; for whatever reason,
3068 # utcfromtimestamp() doesn't accept a tzinfo argument.
3069 off42 = FixedOffset(42, "42")
3070 self.assertRaises(TypeError, meth, ts, off42)
3071 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
3072
3073 def test_tzinfo_timetuple(self):
3074 # TestDateTime tested most of this. datetime adds a twist to the
3075 # DST flag.
3076 class DST(tzinfo):
3077 def __init__(self, dstvalue):
3078 if isinstance(dstvalue, int):
3079 dstvalue = timedelta(minutes=dstvalue)
3080 self.dstvalue = dstvalue
3081 def dst(self, dt):
3082 return self.dstvalue
3083
3084 cls = self.theclass
3085 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
3086 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
3087 t = d.timetuple()
3088 self.assertEqual(1, t.tm_year)
3089 self.assertEqual(1, t.tm_mon)
3090 self.assertEqual(1, t.tm_mday)
3091 self.assertEqual(10, t.tm_hour)
3092 self.assertEqual(20, t.tm_min)
3093 self.assertEqual(30, t.tm_sec)
3094 self.assertEqual(0, t.tm_wday)
3095 self.assertEqual(1, t.tm_yday)
3096 self.assertEqual(flag, t.tm_isdst)
3097
3098 # dst() returns wrong type.
3099 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
3100
3101 # dst() at the edge.
3102 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
3103 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
3104
3105 # dst() out of range.
3106 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
3107 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
3108
3109 def test_utctimetuple(self):
3110 class DST(tzinfo):
3111 def __init__(self, dstvalue=0):
3112 if isinstance(dstvalue, int):
3113 dstvalue = timedelta(minutes=dstvalue)
3114 self.dstvalue = dstvalue
3115 def dst(self, dt):
3116 return self.dstvalue
3117
3118 cls = self.theclass
3119 # This can't work: DST didn't implement utcoffset.
3120 self.assertRaises(NotImplementedError,
3121 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
3122
3123 class UOFS(DST):
3124 def __init__(self, uofs, dofs=None):
3125 DST.__init__(self, dofs)
3126 self.uofs = timedelta(minutes=uofs)
3127 def utcoffset(self, dt):
3128 return self.uofs
3129
3130 for dstvalue in -33, 33, 0, None:
3131 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
3132 t = d.utctimetuple()
3133 self.assertEqual(d.year, t.tm_year)
3134 self.assertEqual(d.month, t.tm_mon)
3135 self.assertEqual(d.day, t.tm_mday)
3136 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
3137 self.assertEqual(13, t.tm_min)
3138 self.assertEqual(d.second, t.tm_sec)
3139 self.assertEqual(d.weekday(), t.tm_wday)
3140 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
3141 t.tm_yday)
3142 # Ensure tm_isdst is 0 regardless of what dst() says: DST
3143 # is never in effect for a UTC time.
3144 self.assertEqual(0, t.tm_isdst)
3145
3146 # For naive datetime, utctimetuple == timetuple except for isdst
3147 d = cls(1, 2, 3, 10, 20, 30, 40)
3148 t = d.utctimetuple()
3149 self.assertEqual(t[:-1], d.timetuple()[:-1])
3150 self.assertEqual(0, t.tm_isdst)
3151 # Same if utcoffset is None
3152 class NOFS(DST):
3153 def utcoffset(self, dt):
3154 return None
3155 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
3156 t = d.utctimetuple()
3157 self.assertEqual(t[:-1], d.timetuple()[:-1])
3158 self.assertEqual(0, t.tm_isdst)
3159 # Check that bad tzinfo is detected
3160 class BOFS(DST):
3161 def utcoffset(self, dt):
3162 return "EST"
3163 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
3164 self.assertRaises(TypeError, d.utctimetuple)
3165
3166 # Check that utctimetuple() is the same as
3167 # astimezone(utc).timetuple()
3168 d = cls(2010, 11, 13, 14, 15, 16, 171819)
3169 for tz in [timezone.min, timezone.utc, timezone.max]:
3170 dtz = d.replace(tzinfo=tz)
3171 self.assertEqual(dtz.utctimetuple()[:-1],
3172 dtz.astimezone(timezone.utc).timetuple()[:-1])
3173 # At the edges, UTC adjustment can produce years out-of-range
3174 # for a datetime object. Ensure that an OverflowError is
3175 # raised.
3176 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
3177 # That goes back 1 minute less than a full day.
3178 self.assertRaises(OverflowError, tiny.utctimetuple)
3179
3180 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
3181 # That goes forward 1 minute less than a full day.
3182 self.assertRaises(OverflowError, huge.utctimetuple)
3183 # More overflow cases
3184 tiny = cls.min.replace(tzinfo=timezone(MINUTE))
3185 self.assertRaises(OverflowError, tiny.utctimetuple)
3186 huge = cls.max.replace(tzinfo=timezone(-MINUTE))
3187 self.assertRaises(OverflowError, huge.utctimetuple)
3188
3189 def test_tzinfo_isoformat(self):
3190 zero = FixedOffset(0, "+00:00")
3191 plus = FixedOffset(220, "+03:40")
3192 minus = FixedOffset(-231, "-03:51")
3193 unknown = FixedOffset(None, "")
3194
3195 cls = self.theclass
3196 datestr = '0001-02-03'
3197 for ofs in None, zero, plus, minus, unknown:
3198 for us in 0, 987001:
3199 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
3200 timestr = '04:05:59' + (us and '.987001' or '')
3201 ofsstr = ofs is not None and d.tzname() or ''
3202 tailstr = timestr + ofsstr
3203 iso = d.isoformat()
3204 self.assertEqual(iso, datestr + 'T' + tailstr)
3205 self.assertEqual(iso, d.isoformat('T'))
3206 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
3207 self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
3208 self.assertEqual(str(d), datestr + ' ' + tailstr)
3209
3210 def test_replace(self):
3211 cls = self.theclass
3212 z100 = FixedOffset(100, "+100")
3213 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3214 args = [1, 2, 3, 4, 5, 6, 7, z100]
3215 base = cls(*args)
3216 self.assertEqual(base, base.replace())
3217
3218 i = 0
3219 for name, newval in (("year", 2),
3220 ("month", 3),
3221 ("day", 4),
3222 ("hour", 5),
3223 ("minute", 6),
3224 ("second", 7),
3225 ("microsecond", 8),
3226 ("tzinfo", zm200)):
3227 newargs = args[:]
3228 newargs[i] = newval
3229 expected = cls(*newargs)
3230 got = base.replace(**{name: newval})
3231 self.assertEqual(expected, got)
3232 i += 1
3233
3234 # Ensure we can get rid of a tzinfo.
3235 self.assertEqual(base.tzname(), "+100")
3236 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003237 self.assertIsNone(base2.tzinfo)
3238 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003239
3240 # Ensure we can add one.
3241 base3 = base2.replace(tzinfo=z100)
3242 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003243 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003244
3245 # Out of bounds.
3246 base = cls(2000, 2, 29)
3247 self.assertRaises(ValueError, base.replace, year=2001)
3248
3249 def test_more_astimezone(self):
3250 # The inherited test_astimezone covered some trivial and error cases.
3251 fnone = FixedOffset(None, "None")
3252 f44m = FixedOffset(44, "44")
3253 fm5h = FixedOffset(-timedelta(hours=5), "m300")
3254
3255 dt = self.theclass.now(tz=f44m)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003256 self.assertIs(dt.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003257 # Replacing with degenerate tzinfo raises an exception.
3258 self.assertRaises(ValueError, dt.astimezone, fnone)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003259 # Replacing with same tzinfo makes no change.
3260 x = dt.astimezone(dt.tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003261 self.assertIs(x.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003262 self.assertEqual(x.date(), dt.date())
3263 self.assertEqual(x.time(), dt.time())
3264
3265 # Replacing with different tzinfo does adjust.
3266 got = dt.astimezone(fm5h)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003267 self.assertIs(got.tzinfo, fm5h)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003268 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
3269 expected = dt - dt.utcoffset() # in effect, convert to UTC
3270 expected += fm5h.utcoffset(dt) # and from there to local time
3271 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
3272 self.assertEqual(got.date(), expected.date())
3273 self.assertEqual(got.time(), expected.time())
3274 self.assertEqual(got.timetz(), expected.timetz())
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003275 self.assertIs(got.tzinfo, expected.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003276 self.assertEqual(got, expected)
3277
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003278 @support.run_with_tz('UTC')
3279 def test_astimezone_default_utc(self):
3280 dt = self.theclass.now(timezone.utc)
3281 self.assertEqual(dt.astimezone(None), dt)
3282 self.assertEqual(dt.astimezone(), dt)
3283
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003284 # Note that offset in TZ variable has the opposite sign to that
3285 # produced by %z directive.
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003286 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
3287 def test_astimezone_default_eastern(self):
3288 dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
3289 local = dt.astimezone()
3290 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003291 self.assertEqual(local.strftime("%z %Z"), "-0500 EST")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003292 dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc)
3293 local = dt.astimezone()
3294 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003295 self.assertEqual(local.strftime("%z %Z"), "-0400 EDT")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003296
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003297 def test_aware_subtract(self):
3298 cls = self.theclass
3299
3300 # Ensure that utcoffset() is ignored when the operands have the
3301 # same tzinfo member.
3302 class OperandDependentOffset(tzinfo):
3303 def utcoffset(self, t):
3304 if t.minute < 10:
3305 # d0 and d1 equal after adjustment
3306 return timedelta(minutes=t.minute)
3307 else:
3308 # d2 off in the weeds
3309 return timedelta(minutes=59)
3310
3311 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
3312 d0 = base.replace(minute=3)
3313 d1 = base.replace(minute=9)
3314 d2 = base.replace(minute=11)
3315 for x in d0, d1, d2:
3316 for y in d0, d1, d2:
3317 got = x - y
3318 expected = timedelta(minutes=x.minute - y.minute)
3319 self.assertEqual(got, expected)
3320
3321 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
3322 # ignored.
3323 base = cls(8, 9, 10, 11, 12, 13, 14)
3324 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
3325 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
3326 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
3327 for x in d0, d1, d2:
3328 for y in d0, d1, d2:
3329 got = x - y
3330 if (x is d0 or x is d1) and (y is d0 or y is d1):
3331 expected = timedelta(0)
3332 elif x is y is d2:
3333 expected = timedelta(0)
3334 elif x is d2:
3335 expected = timedelta(minutes=(11-59)-0)
3336 else:
3337 assert y is d2
3338 expected = timedelta(minutes=0-(11-59))
3339 self.assertEqual(got, expected)
3340
3341 def test_mixed_compare(self):
3342 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
3343 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
3344 self.assertEqual(t1, t2)
3345 t2 = t2.replace(tzinfo=None)
3346 self.assertEqual(t1, t2)
3347 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3348 self.assertEqual(t1, t2)
3349 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04003350 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003351
3352 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
3353 class Varies(tzinfo):
3354 def __init__(self):
3355 self.offset = timedelta(minutes=22)
3356 def utcoffset(self, t):
3357 self.offset += timedelta(minutes=1)
3358 return self.offset
3359
3360 v = Varies()
3361 t1 = t2.replace(tzinfo=v)
3362 t2 = t2.replace(tzinfo=v)
3363 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3364 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3365 self.assertEqual(t1, t2)
3366
3367 # But if they're not identical, it isn't ignored.
3368 t2 = t2.replace(tzinfo=Varies())
3369 self.assertTrue(t1 < t2) # t1's offset counter still going up
3370
3371 def test_subclass_datetimetz(self):
3372
3373 class C(self.theclass):
3374 theAnswer = 42
3375
3376 def __new__(cls, *args, **kws):
3377 temp = kws.copy()
3378 extra = temp.pop('extra')
3379 result = self.theclass.__new__(cls, *args, **temp)
3380 result.extra = extra
3381 return result
3382
3383 def newmeth(self, start):
3384 return start + self.hour + self.year
3385
3386 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3387
3388 dt1 = self.theclass(*args)
3389 dt2 = C(*args, **{'extra': 7})
3390
3391 self.assertEqual(dt2.__class__, C)
3392 self.assertEqual(dt2.theAnswer, 42)
3393 self.assertEqual(dt2.extra, 7)
3394 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3395 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
3396
3397# Pain to set up DST-aware tzinfo classes.
3398
3399def first_sunday_on_or_after(dt):
3400 days_to_go = 6 - dt.weekday()
3401 if days_to_go:
3402 dt += timedelta(days_to_go)
3403 return dt
3404
3405ZERO = timedelta(0)
3406MINUTE = timedelta(minutes=1)
3407HOUR = timedelta(hours=1)
3408DAY = timedelta(days=1)
3409# In the US, DST starts at 2am (standard time) on the first Sunday in April.
3410DSTSTART = datetime(1, 4, 1, 2)
3411# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
3412# which is the first Sunday on or after Oct 25. Because we view 1:MM as
3413# being standard time on that day, there is no spelling in local time of
3414# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
3415DSTEND = datetime(1, 10, 25, 1)
3416
3417class USTimeZone(tzinfo):
3418
3419 def __init__(self, hours, reprname, stdname, dstname):
3420 self.stdoffset = timedelta(hours=hours)
3421 self.reprname = reprname
3422 self.stdname = stdname
3423 self.dstname = dstname
3424
3425 def __repr__(self):
3426 return self.reprname
3427
3428 def tzname(self, dt):
3429 if self.dst(dt):
3430 return self.dstname
3431 else:
3432 return self.stdname
3433
3434 def utcoffset(self, dt):
3435 return self.stdoffset + self.dst(dt)
3436
3437 def dst(self, dt):
3438 if dt is None or dt.tzinfo is None:
3439 # An exception instead may be sensible here, in one or more of
3440 # the cases.
3441 return ZERO
3442 assert dt.tzinfo is self
3443
3444 # Find first Sunday in April.
3445 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
3446 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
3447
3448 # Find last Sunday in October.
3449 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
3450 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
3451
3452 # Can't compare naive to aware objects, so strip the timezone from
3453 # dt first.
3454 if start <= dt.replace(tzinfo=None) < end:
3455 return HOUR
3456 else:
3457 return ZERO
3458
3459Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
3460Central = USTimeZone(-6, "Central", "CST", "CDT")
3461Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
3462Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
3463utc_real = FixedOffset(0, "UTC", 0)
3464# For better test coverage, we want another flavor of UTC that's west of
3465# the Eastern and Pacific timezones.
3466utc_fake = FixedOffset(-12*60, "UTCfake", 0)
3467
3468class TestTimezoneConversions(unittest.TestCase):
3469 # The DST switch times for 2002, in std time.
3470 dston = datetime(2002, 4, 7, 2)
3471 dstoff = datetime(2002, 10, 27, 1)
3472
3473 theclass = datetime
3474
3475 # Check a time that's inside DST.
3476 def checkinside(self, dt, tz, utc, dston, dstoff):
3477 self.assertEqual(dt.dst(), HOUR)
3478
3479 # Conversion to our own timezone is always an identity.
3480 self.assertEqual(dt.astimezone(tz), dt)
3481
3482 asutc = dt.astimezone(utc)
3483 there_and_back = asutc.astimezone(tz)
3484
3485 # Conversion to UTC and back isn't always an identity here,
3486 # because there are redundant spellings (in local time) of
3487 # UTC time when DST begins: the clock jumps from 1:59:59
3488 # to 3:00:00, and a local time of 2:MM:SS doesn't really
3489 # make sense then. The classes above treat 2:MM:SS as
3490 # daylight time then (it's "after 2am"), really an alias
3491 # for 1:MM:SS standard time. The latter form is what
3492 # conversion back from UTC produces.
3493 if dt.date() == dston.date() and dt.hour == 2:
3494 # We're in the redundant hour, and coming back from
3495 # UTC gives the 1:MM:SS standard-time spelling.
3496 self.assertEqual(there_and_back + HOUR, dt)
3497 # Although during was considered to be in daylight
3498 # time, there_and_back is not.
3499 self.assertEqual(there_and_back.dst(), ZERO)
3500 # They're the same times in UTC.
3501 self.assertEqual(there_and_back.astimezone(utc),
3502 dt.astimezone(utc))
3503 else:
3504 # We're not in the redundant hour.
3505 self.assertEqual(dt, there_and_back)
3506
3507 # Because we have a redundant spelling when DST begins, there is
Ezio Melotti3b3499b2011-03-16 11:35:38 +02003508 # (unfortunately) an hour when DST ends that can't be spelled at all in
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003509 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
3510 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
3511 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
3512 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
3513 # expressed in local time. Nevertheless, we want conversion back
3514 # from UTC to mimic the local clock's "repeat an hour" behavior.
3515 nexthour_utc = asutc + HOUR
3516 nexthour_tz = nexthour_utc.astimezone(tz)
3517 if dt.date() == dstoff.date() and dt.hour == 0:
3518 # We're in the hour before the last DST hour. The last DST hour
3519 # is ineffable. We want the conversion back to repeat 1:MM.
3520 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3521 nexthour_utc += HOUR
3522 nexthour_tz = nexthour_utc.astimezone(tz)
3523 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3524 else:
3525 self.assertEqual(nexthour_tz - dt, HOUR)
3526
3527 # Check a time that's outside DST.
3528 def checkoutside(self, dt, tz, utc):
3529 self.assertEqual(dt.dst(), ZERO)
3530
3531 # Conversion to our own timezone is always an identity.
3532 self.assertEqual(dt.astimezone(tz), dt)
3533
3534 # Converting to UTC and back is an identity too.
3535 asutc = dt.astimezone(utc)
3536 there_and_back = asutc.astimezone(tz)
3537 self.assertEqual(dt, there_and_back)
3538
3539 def convert_between_tz_and_utc(self, tz, utc):
3540 dston = self.dston.replace(tzinfo=tz)
3541 # Because 1:MM on the day DST ends is taken as being standard time,
3542 # there is no spelling in tz for the last hour of daylight time.
3543 # For purposes of the test, the last hour of DST is 0:MM, which is
3544 # taken as being daylight time (and 1:MM is taken as being standard
3545 # time).
3546 dstoff = self.dstoff.replace(tzinfo=tz)
3547 for delta in (timedelta(weeks=13),
3548 DAY,
3549 HOUR,
3550 timedelta(minutes=1),
3551 timedelta(microseconds=1)):
3552
3553 self.checkinside(dston, tz, utc, dston, dstoff)
3554 for during in dston + delta, dstoff - delta:
3555 self.checkinside(during, tz, utc, dston, dstoff)
3556
3557 self.checkoutside(dstoff, tz, utc)
3558 for outside in dston - delta, dstoff + delta:
3559 self.checkoutside(outside, tz, utc)
3560
3561 def test_easy(self):
3562 # Despite the name of this test, the endcases are excruciating.
3563 self.convert_between_tz_and_utc(Eastern, utc_real)
3564 self.convert_between_tz_and_utc(Pacific, utc_real)
3565 self.convert_between_tz_and_utc(Eastern, utc_fake)
3566 self.convert_between_tz_and_utc(Pacific, utc_fake)
3567 # The next is really dancing near the edge. It works because
3568 # Pacific and Eastern are far enough apart that their "problem
3569 # hours" don't overlap.
3570 self.convert_between_tz_and_utc(Eastern, Pacific)
3571 self.convert_between_tz_and_utc(Pacific, Eastern)
3572 # OTOH, these fail! Don't enable them. The difficulty is that
3573 # the edge case tests assume that every hour is representable in
3574 # the "utc" class. This is always true for a fixed-offset tzinfo
3575 # class (lke utc_real and utc_fake), but not for Eastern or Central.
3576 # For these adjacent DST-aware time zones, the range of time offsets
3577 # tested ends up creating hours in the one that aren't representable
3578 # in the other. For the same reason, we would see failures in the
3579 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
3580 # offset deltas in convert_between_tz_and_utc().
3581 #
3582 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
3583 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
3584
3585 def test_tricky(self):
3586 # 22:00 on day before daylight starts.
3587 fourback = self.dston - timedelta(hours=4)
3588 ninewest = FixedOffset(-9*60, "-0900", 0)
3589 fourback = fourback.replace(tzinfo=ninewest)
3590 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
3591 # 2", we should get the 3 spelling.
3592 # If we plug 22:00 the day before into Eastern, it "looks like std
3593 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
3594 # to 22:00 lands on 2:00, which makes no sense in local time (the
3595 # local clock jumps from 1 to 3). The point here is to make sure we
3596 # get the 3 spelling.
3597 expected = self.dston.replace(hour=3)
3598 got = fourback.astimezone(Eastern).replace(tzinfo=None)
3599 self.assertEqual(expected, got)
3600
3601 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
3602 # case we want the 1:00 spelling.
3603 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
3604 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
3605 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
3606 # spelling.
3607 expected = self.dston.replace(hour=1)
3608 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
3609 self.assertEqual(expected, got)
3610
3611 # Now on the day DST ends, we want "repeat an hour" behavior.
3612 # UTC 4:MM 5:MM 6:MM 7:MM checking these
3613 # EST 23:MM 0:MM 1:MM 2:MM
3614 # EDT 0:MM 1:MM 2:MM 3:MM
3615 # wall 0:MM 1:MM 1:MM 2:MM against these
3616 for utc in utc_real, utc_fake:
3617 for tz in Eastern, Pacific:
3618 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
3619 # Convert that to UTC.
3620 first_std_hour -= tz.utcoffset(None)
3621 # Adjust for possibly fake UTC.
3622 asutc = first_std_hour + utc.utcoffset(None)
3623 # First UTC hour to convert; this is 4:00 when utc=utc_real &
3624 # tz=Eastern.
3625 asutcbase = asutc.replace(tzinfo=utc)
3626 for tzhour in (0, 1, 1, 2):
3627 expectedbase = self.dstoff.replace(hour=tzhour)
3628 for minute in 0, 30, 59:
3629 expected = expectedbase.replace(minute=minute)
3630 asutc = asutcbase.replace(minute=minute)
3631 astz = asutc.astimezone(tz)
3632 self.assertEqual(astz.replace(tzinfo=None), expected)
3633 asutcbase += HOUR
3634
3635
3636 def test_bogus_dst(self):
3637 class ok(tzinfo):
3638 def utcoffset(self, dt): return HOUR
3639 def dst(self, dt): return HOUR
3640
3641 now = self.theclass.now().replace(tzinfo=utc_real)
3642 # Doesn't blow up.
3643 now.astimezone(ok())
3644
3645 # Does blow up.
3646 class notok(ok):
3647 def dst(self, dt): return None
3648 self.assertRaises(ValueError, now.astimezone, notok())
3649
3650 # Sometimes blow up. In the following, tzinfo.dst()
3651 # implementation may return None or not None depending on
3652 # whether DST is assumed to be in effect. In this situation,
3653 # a ValueError should be raised by astimezone().
3654 class tricky_notok(ok):
3655 def dst(self, dt):
3656 if dt.year == 2000:
3657 return None
3658 else:
3659 return 10*HOUR
3660 dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
3661 self.assertRaises(ValueError, dt.astimezone, tricky_notok())
3662
3663 def test_fromutc(self):
3664 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
3665 now = datetime.utcnow().replace(tzinfo=utc_real)
3666 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3667 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
3668 enow = Eastern.fromutc(now) # doesn't blow up
3669 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
3670 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
3671 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
3672
3673 # Always converts UTC to standard time.
3674 class FauxUSTimeZone(USTimeZone):
3675 def fromutc(self, dt):
3676 return dt + self.stdoffset
3677 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
3678
3679 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
3680 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
3681 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
3682
3683 # Check around DST start.
3684 start = self.dston.replace(hour=4, tzinfo=Eastern)
3685 fstart = start.replace(tzinfo=FEastern)
3686 for wall in 23, 0, 1, 3, 4, 5:
3687 expected = start.replace(hour=wall)
3688 if wall == 23:
3689 expected -= timedelta(days=1)
3690 got = Eastern.fromutc(start)
3691 self.assertEqual(expected, got)
3692
3693 expected = fstart + FEastern.stdoffset
3694 got = FEastern.fromutc(fstart)
3695 self.assertEqual(expected, got)
3696
3697 # Ensure astimezone() calls fromutc() too.
3698 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3699 self.assertEqual(expected, got)
3700
3701 start += HOUR
3702 fstart += HOUR
3703
3704 # Check around DST end.
3705 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
3706 fstart = start.replace(tzinfo=FEastern)
3707 for wall in 0, 1, 1, 2, 3, 4:
3708 expected = start.replace(hour=wall)
3709 got = Eastern.fromutc(start)
3710 self.assertEqual(expected, got)
3711
3712 expected = fstart + FEastern.stdoffset
3713 got = FEastern.fromutc(fstart)
3714 self.assertEqual(expected, got)
3715
3716 # Ensure astimezone() calls fromutc() too.
3717 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3718 self.assertEqual(expected, got)
3719
3720 start += HOUR
3721 fstart += HOUR
3722
3723
3724#############################################################################
3725# oddballs
3726
3727class Oddballs(unittest.TestCase):
3728
3729 def test_bug_1028306(self):
3730 # Trying to compare a date to a datetime should act like a mixed-
3731 # type comparison, despite that datetime is a subclass of date.
3732 as_date = date.today()
3733 as_datetime = datetime.combine(as_date, time())
3734 self.assertTrue(as_date != as_datetime)
3735 self.assertTrue(as_datetime != as_date)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003736 self.assertFalse(as_date == as_datetime)
3737 self.assertFalse(as_datetime == as_date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003738 self.assertRaises(TypeError, lambda: as_date < as_datetime)
3739 self.assertRaises(TypeError, lambda: as_datetime < as_date)
3740 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
3741 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
3742 self.assertRaises(TypeError, lambda: as_date > as_datetime)
3743 self.assertRaises(TypeError, lambda: as_datetime > as_date)
3744 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
3745 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
3746
3747 # Neverthelss, comparison should work with the base-class (date)
3748 # projection if use of a date method is forced.
3749 self.assertEqual(as_date.__eq__(as_datetime), True)
3750 different_day = (as_date.day + 1) % 20 + 1
3751 as_different = as_datetime.replace(day= different_day)
3752 self.assertEqual(as_date.__eq__(as_different), False)
3753
3754 # And date should compare with other subclasses of date. If a
3755 # subclass wants to stop this, it's up to the subclass to do so.
3756 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
3757 self.assertEqual(as_date, date_sc)
3758 self.assertEqual(date_sc, as_date)
3759
3760 # Ditto for datetimes.
3761 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
3762 as_date.day, 0, 0, 0)
3763 self.assertEqual(as_datetime, datetime_sc)
3764 self.assertEqual(datetime_sc, as_datetime)
3765
3766def test_main():
3767 support.run_unittest(__name__)
3768
3769if __name__ == "__main__":
3770 test_main()