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