blob: a942d4de81b8314e4b9583b6cca01735561ca1c4 [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
Victor Stinner511491a2015-09-18 14:42:05 +0200653 eq(td(milliseconds=0.5/1000), td(microseconds=0))
654 eq(td(milliseconds=-0.5/1000), td(microseconds=-0))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000655 eq(td(milliseconds=0.6/1000), td(microseconds=1))
656 eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
Victor Stinner511491a2015-09-18 14:42:05 +0200657 eq(td(milliseconds=1.5/1000), td(microseconds=2))
658 eq(td(milliseconds=-1.5/1000), td(microseconds=-2))
659 eq(td(seconds=0.5/10**6), td(microseconds=0))
660 eq(td(seconds=-0.5/10**6), td(microseconds=-0))
661 eq(td(seconds=1/2**7), td(microseconds=7812))
662 eq(td(seconds=-1/2**7), td(microseconds=-7812))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000663
664 # Rounding due to contributions from more than one field.
665 us_per_hour = 3600e6
666 us_per_day = us_per_hour * 24
667 eq(td(days=.4/us_per_day), td(0))
668 eq(td(hours=.2/us_per_hour), td(0))
669 eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
670
671 eq(td(days=-.4/us_per_day), td(0))
672 eq(td(hours=-.2/us_per_hour), td(0))
673 eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
674
Alexander Belopolsky790d2692013-08-04 14:51:35 -0400675 # Test for a patch in Issue 8860
676 eq(td(microseconds=0.5), 0.5*td(microseconds=1.0))
677 eq(td(microseconds=0.5)//td.resolution, 0.5*td.resolution//td.resolution)
678
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000679 def test_massive_normalization(self):
680 td = timedelta(microseconds=-1)
681 self.assertEqual((td.days, td.seconds, td.microseconds),
682 (-1, 24*3600-1, 999999))
683
684 def test_bool(self):
685 self.assertTrue(timedelta(1))
686 self.assertTrue(timedelta(0, 1))
687 self.assertTrue(timedelta(0, 0, 1))
688 self.assertTrue(timedelta(microseconds=1))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200689 self.assertFalse(timedelta(0))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000690
691 def test_subclass_timedelta(self):
692
693 class T(timedelta):
694 @staticmethod
695 def from_td(td):
696 return T(td.days, td.seconds, td.microseconds)
697
698 def as_hours(self):
699 sum = (self.days * 24 +
700 self.seconds / 3600.0 +
701 self.microseconds / 3600e6)
702 return round(sum)
703
704 t1 = T(days=1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200705 self.assertIs(type(t1), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000706 self.assertEqual(t1.as_hours(), 24)
707
708 t2 = T(days=-1, seconds=-3600)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200709 self.assertIs(type(t2), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000710 self.assertEqual(t2.as_hours(), -25)
711
712 t3 = t1 + t2
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200713 self.assertIs(type(t3), timedelta)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000714 t4 = T.from_td(t3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200715 self.assertIs(type(t4), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000716 self.assertEqual(t3.days, t4.days)
717 self.assertEqual(t3.seconds, t4.seconds)
718 self.assertEqual(t3.microseconds, t4.microseconds)
719 self.assertEqual(str(t3), str(t4))
720 self.assertEqual(t4.as_hours(), -1)
721
722 def test_division(self):
723 t = timedelta(hours=1, minutes=24, seconds=19)
724 second = timedelta(seconds=1)
725 self.assertEqual(t / second, 5059.0)
726 self.assertEqual(t // second, 5059)
727
728 t = timedelta(minutes=2, seconds=30)
729 minute = timedelta(minutes=1)
730 self.assertEqual(t / minute, 2.5)
731 self.assertEqual(t // minute, 2)
732
733 zerotd = timedelta(0)
734 self.assertRaises(ZeroDivisionError, truediv, t, zerotd)
735 self.assertRaises(ZeroDivisionError, floordiv, t, zerotd)
736
737 # self.assertRaises(TypeError, truediv, t, 2)
738 # note: floor division of a timedelta by an integer *is*
739 # currently permitted.
740
741 def test_remainder(self):
742 t = timedelta(minutes=2, seconds=30)
743 minute = timedelta(minutes=1)
744 r = t % minute
745 self.assertEqual(r, timedelta(seconds=30))
746
747 t = timedelta(minutes=-2, seconds=30)
748 r = t % minute
749 self.assertEqual(r, timedelta(seconds=30))
750
751 zerotd = timedelta(0)
752 self.assertRaises(ZeroDivisionError, mod, t, zerotd)
753
754 self.assertRaises(TypeError, mod, t, 10)
755
756 def test_divmod(self):
757 t = timedelta(minutes=2, seconds=30)
758 minute = timedelta(minutes=1)
759 q, r = divmod(t, minute)
760 self.assertEqual(q, 2)
761 self.assertEqual(r, timedelta(seconds=30))
762
763 t = timedelta(minutes=-2, seconds=30)
764 q, r = divmod(t, minute)
765 self.assertEqual(q, -2)
766 self.assertEqual(r, timedelta(seconds=30))
767
768 zerotd = timedelta(0)
769 self.assertRaises(ZeroDivisionError, divmod, t, zerotd)
770
771 self.assertRaises(TypeError, divmod, t, 10)
772
773
774#############################################################################
775# date tests
776
777class TestDateOnly(unittest.TestCase):
778 # Tests here won't pass if also run on datetime objects, so don't
779 # subclass this to test datetimes too.
780
781 def test_delta_non_days_ignored(self):
782 dt = date(2000, 1, 2)
783 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
784 microseconds=5)
785 days = timedelta(delta.days)
786 self.assertEqual(days, timedelta(1))
787
788 dt2 = dt + delta
789 self.assertEqual(dt2, dt + days)
790
791 dt2 = delta + dt
792 self.assertEqual(dt2, dt + days)
793
794 dt2 = dt - delta
795 self.assertEqual(dt2, dt - days)
796
797 delta = -delta
798 days = timedelta(delta.days)
799 self.assertEqual(days, timedelta(-2))
800
801 dt2 = dt + delta
802 self.assertEqual(dt2, dt + days)
803
804 dt2 = delta + dt
805 self.assertEqual(dt2, dt + days)
806
807 dt2 = dt - delta
808 self.assertEqual(dt2, dt - days)
809
810class SubclassDate(date):
811 sub_var = 1
812
813class TestDate(HarmlessMixedComparison, unittest.TestCase):
814 # Tests here should pass for both dates and datetimes, except for a
815 # few tests that TestDateTime overrides.
816
817 theclass = date
818
819 def test_basic_attributes(self):
820 dt = self.theclass(2002, 3, 1)
821 self.assertEqual(dt.year, 2002)
822 self.assertEqual(dt.month, 3)
823 self.assertEqual(dt.day, 1)
824
825 def test_roundtrip(self):
826 for dt in (self.theclass(1, 2, 3),
827 self.theclass.today()):
828 # Verify dt -> string -> date identity.
829 s = repr(dt)
830 self.assertTrue(s.startswith('datetime.'))
831 s = s[9:]
832 dt2 = eval(s)
833 self.assertEqual(dt, dt2)
834
835 # Verify identity via reconstructing from pieces.
836 dt2 = self.theclass(dt.year, dt.month, dt.day)
837 self.assertEqual(dt, dt2)
838
839 def test_ordinal_conversions(self):
840 # Check some fixed values.
841 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
842 (1, 12, 31, 365),
843 (2, 1, 1, 366),
844 # first example from "Calendrical Calculations"
845 (1945, 11, 12, 710347)]:
846 d = self.theclass(y, m, d)
847 self.assertEqual(n, d.toordinal())
848 fromord = self.theclass.fromordinal(n)
849 self.assertEqual(d, fromord)
850 if hasattr(fromord, "hour"):
851 # if we're checking something fancier than a date, verify
852 # the extra fields have been zeroed out
853 self.assertEqual(fromord.hour, 0)
854 self.assertEqual(fromord.minute, 0)
855 self.assertEqual(fromord.second, 0)
856 self.assertEqual(fromord.microsecond, 0)
857
858 # Check first and last days of year spottily across the whole
859 # range of years supported.
860 for year in range(MINYEAR, MAXYEAR+1, 7):
861 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
862 d = self.theclass(year, 1, 1)
863 n = d.toordinal()
864 d2 = self.theclass.fromordinal(n)
865 self.assertEqual(d, d2)
866 # Verify that moving back a day gets to the end of year-1.
867 if year > 1:
868 d = self.theclass.fromordinal(n-1)
869 d2 = self.theclass(year-1, 12, 31)
870 self.assertEqual(d, d2)
871 self.assertEqual(d2.toordinal(), n-1)
872
873 # Test every day in a leap-year and a non-leap year.
874 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
875 for year, isleap in (2000, True), (2002, False):
876 n = self.theclass(year, 1, 1).toordinal()
877 for month, maxday in zip(range(1, 13), dim):
878 if month == 2 and isleap:
879 maxday += 1
880 for day in range(1, maxday+1):
881 d = self.theclass(year, month, day)
882 self.assertEqual(d.toordinal(), n)
883 self.assertEqual(d, self.theclass.fromordinal(n))
884 n += 1
885
886 def test_extreme_ordinals(self):
887 a = self.theclass.min
888 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
889 aord = a.toordinal()
890 b = a.fromordinal(aord)
891 self.assertEqual(a, b)
892
893 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
894
895 b = a + timedelta(days=1)
896 self.assertEqual(b.toordinal(), aord + 1)
897 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
898
899 a = self.theclass.max
900 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
901 aord = a.toordinal()
902 b = a.fromordinal(aord)
903 self.assertEqual(a, b)
904
905 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
906
907 b = a - timedelta(days=1)
908 self.assertEqual(b.toordinal(), aord - 1)
909 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
910
911 def test_bad_constructor_arguments(self):
912 # bad years
913 self.theclass(MINYEAR, 1, 1) # no exception
914 self.theclass(MAXYEAR, 1, 1) # no exception
915 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
916 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
917 # bad months
918 self.theclass(2000, 1, 1) # no exception
919 self.theclass(2000, 12, 1) # no exception
920 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
921 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
922 # bad days
923 self.theclass(2000, 2, 29) # no exception
924 self.theclass(2004, 2, 29) # no exception
925 self.theclass(2400, 2, 29) # no exception
926 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
927 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
928 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
929 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
930 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
931 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
932
933 def test_hash_equality(self):
934 d = self.theclass(2000, 12, 31)
935 # same thing
936 e = self.theclass(2000, 12, 31)
937 self.assertEqual(d, e)
938 self.assertEqual(hash(d), hash(e))
939
940 dic = {d: 1}
941 dic[e] = 2
942 self.assertEqual(len(dic), 1)
943 self.assertEqual(dic[d], 2)
944 self.assertEqual(dic[e], 2)
945
946 d = self.theclass(2001, 1, 1)
947 # same thing
948 e = self.theclass(2001, 1, 1)
949 self.assertEqual(d, e)
950 self.assertEqual(hash(d), hash(e))
951
952 dic = {d: 1}
953 dic[e] = 2
954 self.assertEqual(len(dic), 1)
955 self.assertEqual(dic[d], 2)
956 self.assertEqual(dic[e], 2)
957
958 def test_computations(self):
959 a = self.theclass(2002, 1, 31)
960 b = self.theclass(1956, 1, 31)
961 c = self.theclass(2001,2,1)
962
963 diff = a-b
964 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
965 self.assertEqual(diff.seconds, 0)
966 self.assertEqual(diff.microseconds, 0)
967
968 day = timedelta(1)
969 week = timedelta(7)
970 a = self.theclass(2002, 3, 2)
971 self.assertEqual(a + day, self.theclass(2002, 3, 3))
972 self.assertEqual(day + a, self.theclass(2002, 3, 3))
973 self.assertEqual(a - day, self.theclass(2002, 3, 1))
974 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
975 self.assertEqual(a + week, self.theclass(2002, 3, 9))
976 self.assertEqual(a - week, self.theclass(2002, 2, 23))
977 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
978 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
979 self.assertEqual((a + week) - a, week)
980 self.assertEqual((a + day) - a, day)
981 self.assertEqual((a - week) - a, -week)
982 self.assertEqual((a - day) - a, -day)
983 self.assertEqual(a - (a + week), -week)
984 self.assertEqual(a - (a + day), -day)
985 self.assertEqual(a - (a - week), week)
986 self.assertEqual(a - (a - day), day)
987 self.assertEqual(c - (c - day), day)
988
989 # Add/sub ints or floats should be illegal
990 for i in 1, 1.0:
991 self.assertRaises(TypeError, lambda: a+i)
992 self.assertRaises(TypeError, lambda: a-i)
993 self.assertRaises(TypeError, lambda: i+a)
994 self.assertRaises(TypeError, lambda: i-a)
995
996 # delta - date is senseless.
997 self.assertRaises(TypeError, lambda: day - a)
998 # mixing date and (delta or date) via * or // is senseless
999 self.assertRaises(TypeError, lambda: day * a)
1000 self.assertRaises(TypeError, lambda: a * day)
1001 self.assertRaises(TypeError, lambda: day // a)
1002 self.assertRaises(TypeError, lambda: a // day)
1003 self.assertRaises(TypeError, lambda: a * a)
1004 self.assertRaises(TypeError, lambda: a // a)
1005 # date + date is senseless
1006 self.assertRaises(TypeError, lambda: a + a)
1007
1008 def test_overflow(self):
1009 tiny = self.theclass.resolution
1010
1011 for delta in [tiny, timedelta(1), timedelta(2)]:
1012 dt = self.theclass.min + delta
1013 dt -= delta # no problem
1014 self.assertRaises(OverflowError, dt.__sub__, delta)
1015 self.assertRaises(OverflowError, dt.__add__, -delta)
1016
1017 dt = self.theclass.max - delta
1018 dt += delta # no problem
1019 self.assertRaises(OverflowError, dt.__add__, delta)
1020 self.assertRaises(OverflowError, dt.__sub__, -delta)
1021
1022 def test_fromtimestamp(self):
1023 import time
1024
1025 # Try an arbitrary fixed value.
1026 year, month, day = 1999, 9, 19
1027 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
1028 d = self.theclass.fromtimestamp(ts)
1029 self.assertEqual(d.year, year)
1030 self.assertEqual(d.month, month)
1031 self.assertEqual(d.day, day)
1032
1033 def test_insane_fromtimestamp(self):
1034 # It's possible that some platform maps time_t to double,
1035 # and that this test will fail there. This test should
1036 # exempt such platforms (provided they return reasonable
1037 # results!).
1038 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001039 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001040 insane)
1041
1042 def test_today(self):
1043 import time
1044
1045 # We claim that today() is like fromtimestamp(time.time()), so
1046 # prove it.
1047 for dummy in range(3):
1048 today = self.theclass.today()
1049 ts = time.time()
1050 todayagain = self.theclass.fromtimestamp(ts)
1051 if today == todayagain:
1052 break
1053 # There are several legit reasons that could fail:
1054 # 1. It recently became midnight, between the today() and the
1055 # time() calls.
1056 # 2. The platform time() has such fine resolution that we'll
1057 # never get the same value twice.
1058 # 3. The platform time() has poor resolution, and we just
1059 # happened to call today() right before a resolution quantum
1060 # boundary.
1061 # 4. The system clock got fiddled between calls.
1062 # In any case, wait a little while and try again.
1063 time.sleep(0.1)
1064
1065 # It worked or it didn't. If it didn't, assume it's reason #2, and
1066 # let the test pass if they're within half a second of each other.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001067 if today != todayagain:
1068 self.assertAlmostEqual(todayagain, today,
1069 delta=timedelta(seconds=0.5))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001070
1071 def test_weekday(self):
1072 for i in range(7):
1073 # March 4, 2002 is a Monday
1074 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
1075 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
1076 # January 2, 1956 is a Monday
1077 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
1078 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
1079
1080 def test_isocalendar(self):
1081 # Check examples from
1082 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1083 for i in range(7):
1084 d = self.theclass(2003, 12, 22+i)
1085 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
1086 d = self.theclass(2003, 12, 29) + timedelta(i)
1087 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
1088 d = self.theclass(2004, 1, 5+i)
1089 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
1090 d = self.theclass(2009, 12, 21+i)
1091 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
1092 d = self.theclass(2009, 12, 28) + timedelta(i)
1093 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
1094 d = self.theclass(2010, 1, 4+i)
1095 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
1096
1097 def test_iso_long_years(self):
1098 # Calculate long ISO years and compare to table from
1099 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1100 ISO_LONG_YEARS_TABLE = """
1101 4 32 60 88
1102 9 37 65 93
1103 15 43 71 99
1104 20 48 76
1105 26 54 82
1106
1107 105 133 161 189
1108 111 139 167 195
1109 116 144 172
1110 122 150 178
1111 128 156 184
1112
1113 201 229 257 285
1114 207 235 263 291
1115 212 240 268 296
1116 218 246 274
1117 224 252 280
1118
1119 303 331 359 387
1120 308 336 364 392
1121 314 342 370 398
1122 320 348 376
1123 325 353 381
1124 """
1125 iso_long_years = sorted(map(int, ISO_LONG_YEARS_TABLE.split()))
1126 L = []
1127 for i in range(400):
1128 d = self.theclass(2000+i, 12, 31)
1129 d1 = self.theclass(1600+i, 12, 31)
1130 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
1131 if d.isocalendar()[1] == 53:
1132 L.append(i)
1133 self.assertEqual(L, iso_long_years)
1134
1135 def test_isoformat(self):
1136 t = self.theclass(2, 3, 2)
1137 self.assertEqual(t.isoformat(), "0002-03-02")
1138
1139 def test_ctime(self):
1140 t = self.theclass(2002, 3, 2)
1141 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
1142
1143 def test_strftime(self):
1144 t = self.theclass(2005, 3, 2)
1145 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
1146 self.assertEqual(t.strftime(""), "") # SF bug #761337
1147 self.assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784
1148
1149 self.assertRaises(TypeError, t.strftime) # needs an arg
1150 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
1151 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
1152
1153 # test that unicode input is allowed (issue 2782)
1154 self.assertEqual(t.strftime("%m"), "03")
1155
1156 # A naive object replaces %z and %Z w/ empty strings.
1157 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1158
1159 #make sure that invalid format specifiers are handled correctly
1160 #self.assertRaises(ValueError, t.strftime, "%e")
1161 #self.assertRaises(ValueError, t.strftime, "%")
1162 #self.assertRaises(ValueError, t.strftime, "%#")
1163
1164 #oh well, some systems just ignore those invalid ones.
1165 #at least, excercise them to make sure that no crashes
1166 #are generated
1167 for f in ["%e", "%", "%#"]:
1168 try:
1169 t.strftime(f)
1170 except ValueError:
1171 pass
1172
1173 #check that this standard extension works
1174 t.strftime("%f")
1175
1176
1177 def test_format(self):
1178 dt = self.theclass(2007, 9, 10)
1179 self.assertEqual(dt.__format__(''), str(dt))
1180
1181 # check that a derived class's __str__() gets called
1182 class A(self.theclass):
1183 def __str__(self):
1184 return 'A'
1185 a = A(2007, 9, 10)
1186 self.assertEqual(a.__format__(''), 'A')
1187
1188 # check that a derived class's strftime gets called
1189 class B(self.theclass):
1190 def strftime(self, format_spec):
1191 return 'B'
1192 b = B(2007, 9, 10)
1193 self.assertEqual(b.__format__(''), str(dt))
1194
1195 for fmt in ["m:%m d:%d y:%y",
1196 "m:%m d:%d y:%y H:%H M:%M S:%S",
1197 "%z %Z",
1198 ]:
1199 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1200 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1201 self.assertEqual(b.__format__(fmt), 'B')
1202
1203 def test_resolution_info(self):
1204 # XXX: Should min and max respect subclassing?
1205 if issubclass(self.theclass, datetime):
1206 expected_class = datetime
1207 else:
1208 expected_class = date
1209 self.assertIsInstance(self.theclass.min, expected_class)
1210 self.assertIsInstance(self.theclass.max, expected_class)
1211 self.assertIsInstance(self.theclass.resolution, timedelta)
1212 self.assertTrue(self.theclass.max > self.theclass.min)
1213
1214 def test_extreme_timedelta(self):
1215 big = self.theclass.max - self.theclass.min
1216 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
1217 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
1218 # n == 315537897599999999 ~= 2**58.13
1219 justasbig = timedelta(0, 0, n)
1220 self.assertEqual(big, justasbig)
1221 self.assertEqual(self.theclass.min + big, self.theclass.max)
1222 self.assertEqual(self.theclass.max - big, self.theclass.min)
1223
1224 def test_timetuple(self):
1225 for i in range(7):
1226 # January 2, 1956 is a Monday (0)
1227 d = self.theclass(1956, 1, 2+i)
1228 t = d.timetuple()
1229 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
1230 # February 1, 1956 is a Wednesday (2)
1231 d = self.theclass(1956, 2, 1+i)
1232 t = d.timetuple()
1233 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
1234 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
1235 # of the year.
1236 d = self.theclass(1956, 3, 1+i)
1237 t = d.timetuple()
1238 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
1239 self.assertEqual(t.tm_year, 1956)
1240 self.assertEqual(t.tm_mon, 3)
1241 self.assertEqual(t.tm_mday, 1+i)
1242 self.assertEqual(t.tm_hour, 0)
1243 self.assertEqual(t.tm_min, 0)
1244 self.assertEqual(t.tm_sec, 0)
1245 self.assertEqual(t.tm_wday, (3+i)%7)
1246 self.assertEqual(t.tm_yday, 61+i)
1247 self.assertEqual(t.tm_isdst, -1)
1248
1249 def test_pickling(self):
1250 args = 6, 7, 23
1251 orig = self.theclass(*args)
1252 for pickler, unpickler, proto in pickle_choices:
1253 green = pickler.dumps(orig, proto)
1254 derived = unpickler.loads(green)
1255 self.assertEqual(orig, derived)
1256
1257 def test_compare(self):
1258 t1 = self.theclass(2, 3, 4)
1259 t2 = self.theclass(2, 3, 4)
1260 self.assertEqual(t1, t2)
1261 self.assertTrue(t1 <= t2)
1262 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001263 self.assertFalse(t1 != t2)
1264 self.assertFalse(t1 < t2)
1265 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001266
1267 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
1268 t2 = self.theclass(*args) # this is larger than t1
1269 self.assertTrue(t1 < t2)
1270 self.assertTrue(t2 > t1)
1271 self.assertTrue(t1 <= t2)
1272 self.assertTrue(t2 >= t1)
1273 self.assertTrue(t1 != t2)
1274 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001275 self.assertFalse(t1 == t2)
1276 self.assertFalse(t2 == t1)
1277 self.assertFalse(t1 > t2)
1278 self.assertFalse(t2 < t1)
1279 self.assertFalse(t1 >= t2)
1280 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001281
1282 for badarg in OTHERSTUFF:
1283 self.assertEqual(t1 == badarg, False)
1284 self.assertEqual(t1 != badarg, True)
1285 self.assertEqual(badarg == t1, False)
1286 self.assertEqual(badarg != t1, True)
1287
1288 self.assertRaises(TypeError, lambda: t1 < badarg)
1289 self.assertRaises(TypeError, lambda: t1 > badarg)
1290 self.assertRaises(TypeError, lambda: t1 >= badarg)
1291 self.assertRaises(TypeError, lambda: badarg <= t1)
1292 self.assertRaises(TypeError, lambda: badarg < t1)
1293 self.assertRaises(TypeError, lambda: badarg > t1)
1294 self.assertRaises(TypeError, lambda: badarg >= t1)
1295
1296 def test_mixed_compare(self):
1297 our = self.theclass(2000, 4, 5)
1298
1299 # Our class can be compared for equality to other classes
1300 self.assertEqual(our == 1, False)
1301 self.assertEqual(1 == our, False)
1302 self.assertEqual(our != 1, True)
1303 self.assertEqual(1 != our, True)
1304
1305 # But the ordering is undefined
1306 self.assertRaises(TypeError, lambda: our < 1)
1307 self.assertRaises(TypeError, lambda: 1 < our)
1308
1309 # Repeat those tests with a different class
1310
1311 class SomeClass:
1312 pass
1313
1314 their = SomeClass()
1315 self.assertEqual(our == their, False)
1316 self.assertEqual(their == our, False)
1317 self.assertEqual(our != their, True)
1318 self.assertEqual(their != our, True)
1319 self.assertRaises(TypeError, lambda: our < their)
1320 self.assertRaises(TypeError, lambda: their < our)
1321
1322 # However, if the other class explicitly defines ordering
1323 # relative to our class, it is allowed to do so
1324
1325 class LargerThanAnything:
1326 def __lt__(self, other):
1327 return False
1328 def __le__(self, other):
1329 return isinstance(other, LargerThanAnything)
1330 def __eq__(self, other):
1331 return isinstance(other, LargerThanAnything)
1332 def __ne__(self, other):
1333 return not isinstance(other, LargerThanAnything)
1334 def __gt__(self, other):
1335 return not isinstance(other, LargerThanAnything)
1336 def __ge__(self, other):
1337 return True
1338
1339 their = LargerThanAnything()
1340 self.assertEqual(our == their, False)
1341 self.assertEqual(their == our, False)
1342 self.assertEqual(our != their, True)
1343 self.assertEqual(their != our, True)
1344 self.assertEqual(our < their, True)
1345 self.assertEqual(their < our, False)
1346
1347 def test_bool(self):
1348 # All dates are considered true.
1349 self.assertTrue(self.theclass.min)
1350 self.assertTrue(self.theclass.max)
1351
Alexander Belopolsky89da3492011-05-02 13:14:24 -04001352 def test_strftime_y2k(self):
1353 for y in (1, 49, 70, 99, 100, 999, 1000, 1970):
Florent Xicluna49ce0682011-11-01 12:56:14 +01001354 d = self.theclass(y, 1, 1)
1355 # Issue 13305: For years < 1000, the value is not always
1356 # padded to 4 digits across platforms. The C standard
1357 # assumes year >= 1900, so it does not specify the number
1358 # of digits.
1359 if d.strftime("%Y") != '%04d' % y:
1360 # Year 42 returns '42', not padded
1361 self.assertEqual(d.strftime("%Y"), '%d' % y)
1362 # '0042' is obtained anyway
1363 self.assertEqual(d.strftime("%4Y"), '%04d' % y)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001364
1365 def test_replace(self):
1366 cls = self.theclass
1367 args = [1, 2, 3]
1368 base = cls(*args)
1369 self.assertEqual(base, base.replace())
1370
1371 i = 0
1372 for name, newval in (("year", 2),
1373 ("month", 3),
1374 ("day", 4)):
1375 newargs = args[:]
1376 newargs[i] = newval
1377 expected = cls(*newargs)
1378 got = base.replace(**{name: newval})
1379 self.assertEqual(expected, got)
1380 i += 1
1381
1382 # Out of bounds.
1383 base = cls(2000, 2, 29)
1384 self.assertRaises(ValueError, base.replace, year=2001)
1385
1386 def test_subclass_date(self):
1387
1388 class C(self.theclass):
1389 theAnswer = 42
1390
1391 def __new__(cls, *args, **kws):
1392 temp = kws.copy()
1393 extra = temp.pop('extra')
1394 result = self.theclass.__new__(cls, *args, **temp)
1395 result.extra = extra
1396 return result
1397
1398 def newmeth(self, start):
1399 return start + self.year + self.month
1400
1401 args = 2003, 4, 14
1402
1403 dt1 = self.theclass(*args)
1404 dt2 = C(*args, **{'extra': 7})
1405
1406 self.assertEqual(dt2.__class__, C)
1407 self.assertEqual(dt2.theAnswer, 42)
1408 self.assertEqual(dt2.extra, 7)
1409 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1410 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1411
1412 def test_pickling_subclass_date(self):
1413
1414 args = 6, 7, 23
1415 orig = SubclassDate(*args)
1416 for pickler, unpickler, proto in pickle_choices:
1417 green = pickler.dumps(orig, proto)
1418 derived = unpickler.loads(green)
1419 self.assertEqual(orig, derived)
1420
1421 def test_backdoor_resistance(self):
1422 # For fast unpickling, the constructor accepts a pickle byte string.
1423 # This is a low-overhead backdoor. A user can (by intent or
1424 # mistake) pass a string directly, which (if it's the right length)
1425 # will get treated like a pickle, and bypass the normal sanity
1426 # checks in the constructor. This can create insane objects.
1427 # The constructor doesn't want to burn the time to validate all
1428 # fields, but does check the month field. This stops, e.g.,
1429 # datetime.datetime('1995-03-25') from yielding an insane object.
1430 base = b'1995-03-25'
1431 if not issubclass(self.theclass, datetime):
1432 base = base[:4]
1433 for month_byte in b'9', b'\0', b'\r', b'\xff':
1434 self.assertRaises(TypeError, self.theclass,
1435 base[:2] + month_byte + base[3:])
1436 # Good bytes, but bad tzinfo:
1437 self.assertRaises(TypeError, self.theclass,
1438 bytes([1] * len(base)), 'EST')
1439
1440 for ord_byte in range(1, 13):
1441 # This shouldn't blow up because of the month byte alone. If
1442 # the implementation changes to do more-careful checking, it may
1443 # blow up because other fields are insane.
1444 self.theclass(base[:2] + bytes([ord_byte]) + base[3:])
1445
1446#############################################################################
1447# datetime tests
1448
1449class SubclassDatetime(datetime):
1450 sub_var = 1
1451
1452class TestDateTime(TestDate):
1453
1454 theclass = datetime
1455
1456 def test_basic_attributes(self):
1457 dt = self.theclass(2002, 3, 1, 12, 0)
1458 self.assertEqual(dt.year, 2002)
1459 self.assertEqual(dt.month, 3)
1460 self.assertEqual(dt.day, 1)
1461 self.assertEqual(dt.hour, 12)
1462 self.assertEqual(dt.minute, 0)
1463 self.assertEqual(dt.second, 0)
1464 self.assertEqual(dt.microsecond, 0)
1465
1466 def test_basic_attributes_nonzero(self):
1467 # Make sure all attributes are non-zero so bugs in
1468 # bit-shifting access show up.
1469 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1470 self.assertEqual(dt.year, 2002)
1471 self.assertEqual(dt.month, 3)
1472 self.assertEqual(dt.day, 1)
1473 self.assertEqual(dt.hour, 12)
1474 self.assertEqual(dt.minute, 59)
1475 self.assertEqual(dt.second, 59)
1476 self.assertEqual(dt.microsecond, 8000)
1477
1478 def test_roundtrip(self):
1479 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1480 self.theclass.now()):
1481 # Verify dt -> string -> datetime identity.
1482 s = repr(dt)
1483 self.assertTrue(s.startswith('datetime.'))
1484 s = s[9:]
1485 dt2 = eval(s)
1486 self.assertEqual(dt, dt2)
1487
1488 # Verify identity via reconstructing from pieces.
1489 dt2 = self.theclass(dt.year, dt.month, dt.day,
1490 dt.hour, dt.minute, dt.second,
1491 dt.microsecond)
1492 self.assertEqual(dt, dt2)
1493
1494 def test_isoformat(self):
1495 t = self.theclass(2, 3, 2, 4, 5, 1, 123)
1496 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123")
1497 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123")
1498 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123")
1499 self.assertEqual(t.isoformat('\x00'), "0002-03-02\x0004:05:01.000123")
1500 # str is ISO format with the separator forced to a blank.
1501 self.assertEqual(str(t), "0002-03-02 04:05:01.000123")
1502
1503 t = self.theclass(2, 3, 2)
1504 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1505 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1506 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1507 # str is ISO format with the separator forced to a blank.
1508 self.assertEqual(str(t), "0002-03-02 00:00:00")
1509
1510 def test_format(self):
1511 dt = self.theclass(2007, 9, 10, 4, 5, 1, 123)
1512 self.assertEqual(dt.__format__(''), str(dt))
1513
1514 # check that a derived class's __str__() gets called
1515 class A(self.theclass):
1516 def __str__(self):
1517 return 'A'
1518 a = A(2007, 9, 10, 4, 5, 1, 123)
1519 self.assertEqual(a.__format__(''), 'A')
1520
1521 # check that a derived class's strftime gets called
1522 class B(self.theclass):
1523 def strftime(self, format_spec):
1524 return 'B'
1525 b = B(2007, 9, 10, 4, 5, 1, 123)
1526 self.assertEqual(b.__format__(''), str(dt))
1527
1528 for fmt in ["m:%m d:%d y:%y",
1529 "m:%m d:%d y:%y H:%H M:%M S:%S",
1530 "%z %Z",
1531 ]:
1532 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1533 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1534 self.assertEqual(b.__format__(fmt), 'B')
1535
1536 def test_more_ctime(self):
1537 # Test fields that TestDate doesn't touch.
1538 import time
1539
1540 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1541 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1542 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1543 # out. The difference is that t.ctime() produces " 2" for the day,
1544 # but platform ctime() produces "02" for the day. According to
1545 # C99, t.ctime() is correct here.
1546 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1547
1548 # So test a case where that difference doesn't matter.
1549 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1550 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1551
1552 def test_tz_independent_comparing(self):
1553 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1554 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1555 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1556 self.assertEqual(dt1, dt3)
1557 self.assertTrue(dt2 > dt3)
1558
1559 # Make sure comparison doesn't forget microseconds, and isn't done
1560 # via comparing a float timestamp (an IEEE double doesn't have enough
1561 # precision to span microsecond resolution across years 1 thru 9999,
1562 # so comparing via timestamp necessarily calls some distinct values
1563 # equal).
1564 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1565 us = timedelta(microseconds=1)
1566 dt2 = dt1 + us
1567 self.assertEqual(dt2 - dt1, us)
1568 self.assertTrue(dt1 < dt2)
1569
1570 def test_strftime_with_bad_tzname_replace(self):
1571 # verify ok if tzinfo.tzname().replace() returns a non-string
1572 class MyTzInfo(FixedOffset):
1573 def tzname(self, dt):
1574 class MyStr(str):
1575 def replace(self, *args):
1576 return None
1577 return MyStr('name')
1578 t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
1579 self.assertRaises(TypeError, t.strftime, '%Z')
1580
1581 def test_bad_constructor_arguments(self):
1582 # bad years
1583 self.theclass(MINYEAR, 1, 1) # no exception
1584 self.theclass(MAXYEAR, 1, 1) # no exception
1585 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1586 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1587 # bad months
1588 self.theclass(2000, 1, 1) # no exception
1589 self.theclass(2000, 12, 1) # no exception
1590 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1591 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1592 # bad days
1593 self.theclass(2000, 2, 29) # no exception
1594 self.theclass(2004, 2, 29) # no exception
1595 self.theclass(2400, 2, 29) # no exception
1596 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1597 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1598 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1599 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1600 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1601 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1602 # bad hours
1603 self.theclass(2000, 1, 31, 0) # no exception
1604 self.theclass(2000, 1, 31, 23) # no exception
1605 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1606 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1607 # bad minutes
1608 self.theclass(2000, 1, 31, 23, 0) # no exception
1609 self.theclass(2000, 1, 31, 23, 59) # no exception
1610 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1611 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1612 # bad seconds
1613 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1614 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1615 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1616 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1617 # bad microseconds
1618 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1619 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1620 self.assertRaises(ValueError, self.theclass,
1621 2000, 1, 31, 23, 59, 59, -1)
1622 self.assertRaises(ValueError, self.theclass,
1623 2000, 1, 31, 23, 59, 59,
1624 1000000)
1625
1626 def test_hash_equality(self):
1627 d = self.theclass(2000, 12, 31, 23, 30, 17)
1628 e = self.theclass(2000, 12, 31, 23, 30, 17)
1629 self.assertEqual(d, e)
1630 self.assertEqual(hash(d), hash(e))
1631
1632 dic = {d: 1}
1633 dic[e] = 2
1634 self.assertEqual(len(dic), 1)
1635 self.assertEqual(dic[d], 2)
1636 self.assertEqual(dic[e], 2)
1637
1638 d = self.theclass(2001, 1, 1, 0, 5, 17)
1639 e = self.theclass(2001, 1, 1, 0, 5, 17)
1640 self.assertEqual(d, e)
1641 self.assertEqual(hash(d), hash(e))
1642
1643 dic = {d: 1}
1644 dic[e] = 2
1645 self.assertEqual(len(dic), 1)
1646 self.assertEqual(dic[d], 2)
1647 self.assertEqual(dic[e], 2)
1648
1649 def test_computations(self):
1650 a = self.theclass(2002, 1, 31)
1651 b = self.theclass(1956, 1, 31)
1652 diff = a-b
1653 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1654 self.assertEqual(diff.seconds, 0)
1655 self.assertEqual(diff.microseconds, 0)
1656 a = self.theclass(2002, 3, 2, 17, 6)
1657 millisec = timedelta(0, 0, 1000)
1658 hour = timedelta(0, 3600)
1659 day = timedelta(1)
1660 week = timedelta(7)
1661 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1662 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1663 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1664 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1665 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1666 self.assertEqual(a - hour, a + -hour)
1667 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1668 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1669 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1670 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1671 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1672 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1673 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1674 self.assertEqual((a + week) - a, week)
1675 self.assertEqual((a + day) - a, day)
1676 self.assertEqual((a + hour) - a, hour)
1677 self.assertEqual((a + millisec) - a, millisec)
1678 self.assertEqual((a - week) - a, -week)
1679 self.assertEqual((a - day) - a, -day)
1680 self.assertEqual((a - hour) - a, -hour)
1681 self.assertEqual((a - millisec) - a, -millisec)
1682 self.assertEqual(a - (a + week), -week)
1683 self.assertEqual(a - (a + day), -day)
1684 self.assertEqual(a - (a + hour), -hour)
1685 self.assertEqual(a - (a + millisec), -millisec)
1686 self.assertEqual(a - (a - week), week)
1687 self.assertEqual(a - (a - day), day)
1688 self.assertEqual(a - (a - hour), hour)
1689 self.assertEqual(a - (a - millisec), millisec)
1690 self.assertEqual(a + (week + day + hour + millisec),
1691 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1692 self.assertEqual(a + (week + day + hour + millisec),
1693 (((a + week) + day) + hour) + millisec)
1694 self.assertEqual(a - (week + day + hour + millisec),
1695 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1696 self.assertEqual(a - (week + day + hour + millisec),
1697 (((a - week) - day) - hour) - millisec)
1698 # Add/sub ints or floats should be illegal
1699 for i in 1, 1.0:
1700 self.assertRaises(TypeError, lambda: a+i)
1701 self.assertRaises(TypeError, lambda: a-i)
1702 self.assertRaises(TypeError, lambda: i+a)
1703 self.assertRaises(TypeError, lambda: i-a)
1704
1705 # delta - datetime is senseless.
1706 self.assertRaises(TypeError, lambda: day - a)
1707 # mixing datetime and (delta or datetime) via * or // is senseless
1708 self.assertRaises(TypeError, lambda: day * a)
1709 self.assertRaises(TypeError, lambda: a * day)
1710 self.assertRaises(TypeError, lambda: day // a)
1711 self.assertRaises(TypeError, lambda: a // day)
1712 self.assertRaises(TypeError, lambda: a * a)
1713 self.assertRaises(TypeError, lambda: a // a)
1714 # datetime + datetime is senseless
1715 self.assertRaises(TypeError, lambda: a + a)
1716
1717 def test_pickling(self):
1718 args = 6, 7, 23, 20, 59, 1, 64**2
1719 orig = self.theclass(*args)
1720 for pickler, unpickler, proto in pickle_choices:
1721 green = pickler.dumps(orig, proto)
1722 derived = unpickler.loads(green)
1723 self.assertEqual(orig, derived)
1724
1725 def test_more_pickling(self):
1726 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
Serhiy Storchakabad12572014-12-15 14:03:42 +02001727 for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1728 s = pickle.dumps(a, proto)
1729 b = pickle.loads(s)
1730 self.assertEqual(b.year, 2003)
1731 self.assertEqual(b.month, 2)
1732 self.assertEqual(b.day, 7)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001733
1734 def test_pickling_subclass_datetime(self):
1735 args = 6, 7, 23, 20, 59, 1, 64**2
1736 orig = SubclassDatetime(*args)
1737 for pickler, unpickler, proto in pickle_choices:
1738 green = pickler.dumps(orig, proto)
1739 derived = unpickler.loads(green)
1740 self.assertEqual(orig, derived)
1741
1742 def test_more_compare(self):
1743 # The test_compare() inherited from TestDate covers the error cases.
1744 # We just want to test lexicographic ordering on the members datetime
1745 # has that date lacks.
1746 args = [2000, 11, 29, 20, 58, 16, 999998]
1747 t1 = self.theclass(*args)
1748 t2 = self.theclass(*args)
1749 self.assertEqual(t1, t2)
1750 self.assertTrue(t1 <= t2)
1751 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001752 self.assertFalse(t1 != t2)
1753 self.assertFalse(t1 < t2)
1754 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001755
1756 for i in range(len(args)):
1757 newargs = args[:]
1758 newargs[i] = args[i] + 1
1759 t2 = self.theclass(*newargs) # this is larger than t1
1760 self.assertTrue(t1 < t2)
1761 self.assertTrue(t2 > t1)
1762 self.assertTrue(t1 <= t2)
1763 self.assertTrue(t2 >= t1)
1764 self.assertTrue(t1 != t2)
1765 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001766 self.assertFalse(t1 == t2)
1767 self.assertFalse(t2 == t1)
1768 self.assertFalse(t1 > t2)
1769 self.assertFalse(t2 < t1)
1770 self.assertFalse(t1 >= t2)
1771 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001772
1773
1774 # A helper for timestamp constructor tests.
1775 def verify_field_equality(self, expected, got):
1776 self.assertEqual(expected.tm_year, got.year)
1777 self.assertEqual(expected.tm_mon, got.month)
1778 self.assertEqual(expected.tm_mday, got.day)
1779 self.assertEqual(expected.tm_hour, got.hour)
1780 self.assertEqual(expected.tm_min, got.minute)
1781 self.assertEqual(expected.tm_sec, got.second)
1782
1783 def test_fromtimestamp(self):
1784 import time
1785
1786 ts = time.time()
1787 expected = time.localtime(ts)
1788 got = self.theclass.fromtimestamp(ts)
1789 self.verify_field_equality(expected, got)
1790
1791 def test_utcfromtimestamp(self):
1792 import time
1793
1794 ts = time.time()
1795 expected = time.gmtime(ts)
1796 got = self.theclass.utcfromtimestamp(ts)
1797 self.verify_field_equality(expected, got)
1798
Alexander Belopolskya4415142012-06-08 12:33:09 -04001799 # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in
1800 # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0).
1801 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
1802 def test_timestamp_naive(self):
1803 t = self.theclass(1970, 1, 1)
1804 self.assertEqual(t.timestamp(), 18000.0)
1805 t = self.theclass(1970, 1, 1, 1, 2, 3, 4)
1806 self.assertEqual(t.timestamp(),
1807 18000.0 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky0c687e52012-06-08 12:58:31 -04001808 # Missing hour may produce platform-dependent result
Alexander Belopolskya4415142012-06-08 12:33:09 -04001809 t = self.theclass(2012, 3, 11, 2, 30)
Alexander Belopolsky0c687e52012-06-08 12:58:31 -04001810 self.assertIn(self.theclass.fromtimestamp(t.timestamp()),
Alexander Belopolskyf6f56182012-06-08 13:00:27 -04001811 [t - timedelta(hours=1), t + timedelta(hours=1)])
Alexander Belopolskya4415142012-06-08 12:33:09 -04001812 # Ambiguous hour defaults to DST
1813 t = self.theclass(2012, 11, 4, 1, 30)
1814 self.assertEqual(self.theclass.fromtimestamp(t.timestamp()), t)
1815
1816 # Timestamp may raise an overflow error on some platforms
1817 for t in [self.theclass(1,1,1), self.theclass(9999,12,12)]:
1818 try:
1819 s = t.timestamp()
1820 except OverflowError:
1821 pass
1822 else:
1823 self.assertEqual(self.theclass.fromtimestamp(s), t)
1824
1825 def test_timestamp_aware(self):
1826 t = self.theclass(1970, 1, 1, tzinfo=timezone.utc)
1827 self.assertEqual(t.timestamp(), 0.0)
1828 t = self.theclass(1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone.utc)
1829 self.assertEqual(t.timestamp(),
1830 3600 + 2*60 + 3 + 4*1e-6)
1831 t = self.theclass(1970, 1, 1, 1, 2, 3, 4,
1832 tzinfo=timezone(timedelta(hours=-5), 'EST'))
1833 self.assertEqual(t.timestamp(),
1834 18000 + 3600 + 2*60 + 3 + 4*1e-6)
Victor Stinner511491a2015-09-18 14:42:05 +02001835
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001836 def test_microsecond_rounding(self):
Alexander Belopolsky3e62f782010-09-21 16:30:56 +00001837 for fts in [self.theclass.fromtimestamp,
1838 self.theclass.utcfromtimestamp]:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001839 zero = fts(0)
1840 self.assertEqual(zero.second, 0)
1841 self.assertEqual(zero.microsecond, 0)
Victor Stinner511491a2015-09-18 14:42:05 +02001842 one = fts(1e-6)
Victor Stinner8050ca92012-03-14 00:17:05 +01001843 try:
1844 minus_one = fts(-1e-6)
1845 except OSError:
1846 # localtime(-1) and gmtime(-1) is not supported on Windows
1847 pass
1848 else:
1849 self.assertEqual(minus_one.second, 59)
1850 self.assertEqual(minus_one.microsecond, 999999)
Victor Stinner5d272cc2012-03-13 13:35:55 +01001851
Victor Stinner8050ca92012-03-14 00:17:05 +01001852 t = fts(-1e-8)
Victor Stinner511491a2015-09-18 14:42:05 +02001853 self.assertEqual(t, zero)
Victor Stinner8050ca92012-03-14 00:17:05 +01001854 t = fts(-9e-7)
1855 self.assertEqual(t, minus_one)
1856 t = fts(-1e-7)
Victor Stinner511491a2015-09-18 14:42:05 +02001857 self.assertEqual(t, zero)
1858 t = fts(-1/2**7)
1859 self.assertEqual(t.second, 59)
1860 self.assertEqual(t.microsecond, 992188)
Victor Stinner5d272cc2012-03-13 13:35:55 +01001861
1862 t = fts(1e-7)
1863 self.assertEqual(t, zero)
1864 t = fts(9e-7)
Victor Stinner511491a2015-09-18 14:42:05 +02001865 self.assertEqual(t, one)
Victor Stinner5d272cc2012-03-13 13:35:55 +01001866 t = fts(0.99999949)
1867 self.assertEqual(t.second, 0)
1868 self.assertEqual(t.microsecond, 999999)
1869 t = fts(0.9999999)
Victor Stinner511491a2015-09-18 14:42:05 +02001870 self.assertEqual(t.second, 1)
1871 self.assertEqual(t.microsecond, 0)
1872 t = fts(1/2**7)
Victor Stinner5d272cc2012-03-13 13:35:55 +01001873 self.assertEqual(t.second, 0)
Victor Stinner511491a2015-09-18 14:42:05 +02001874 self.assertEqual(t.microsecond, 7812)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001875
1876 def test_insane_fromtimestamp(self):
1877 # It's possible that some platform maps time_t to double,
1878 # and that this test will fail there. This test should
1879 # exempt such platforms (provided they return reasonable
1880 # results!).
1881 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001882 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001883 insane)
1884
1885 def test_insane_utcfromtimestamp(self):
1886 # It's possible that some platform maps time_t to double,
1887 # and that this test will fail there. This test should
1888 # exempt such platforms (provided they return reasonable
1889 # results!).
1890 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001891 self.assertRaises(OverflowError, self.theclass.utcfromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001892 insane)
1893 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
1894 def test_negative_float_fromtimestamp(self):
1895 # The result is tz-dependent; at least test that this doesn't
1896 # fail (like it did before bug 1646728 was fixed).
1897 self.theclass.fromtimestamp(-1.05)
1898
1899 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
1900 def test_negative_float_utcfromtimestamp(self):
1901 d = self.theclass.utcfromtimestamp(-1.05)
1902 self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
1903
1904 def test_utcnow(self):
1905 import time
1906
1907 # Call it a success if utcnow() and utcfromtimestamp() are within
1908 # a second of each other.
1909 tolerance = timedelta(seconds=1)
1910 for dummy in range(3):
1911 from_now = self.theclass.utcnow()
1912 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1913 if abs(from_timestamp - from_now) <= tolerance:
1914 break
1915 # Else try again a few times.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001916 self.assertLessEqual(abs(from_timestamp - from_now), tolerance)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001917
1918 def test_strptime(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001919 string = '2004-12-01 13:02:47.197'
1920 format = '%Y-%m-%d %H:%M:%S.%f'
1921 expected = _strptime._strptime_datetime(self.theclass, string, format)
1922 got = self.theclass.strptime(string, format)
1923 self.assertEqual(expected, got)
1924 self.assertIs(type(expected), self.theclass)
1925 self.assertIs(type(got), self.theclass)
1926
1927 strptime = self.theclass.strptime
1928 self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
1929 self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
1930 # Only local timezone and UTC are supported
1931 for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
1932 (-_time.timezone, _time.tzname[0])):
1933 if tzseconds < 0:
1934 sign = '-'
1935 seconds = -tzseconds
1936 else:
1937 sign ='+'
1938 seconds = tzseconds
1939 hours, minutes = divmod(seconds//60, 60)
1940 dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
1941 dt = strptime(dtstr, "%z %Z")
1942 self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
1943 self.assertEqual(dt.tzname(), tzname)
1944 # Can produce inconsistent datetime
1945 dtstr, fmt = "+1234 UTC", "%z %Z"
1946 dt = strptime(dtstr, fmt)
1947 self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE)
1948 self.assertEqual(dt.tzname(), 'UTC')
1949 # yet will roundtrip
1950 self.assertEqual(dt.strftime(fmt), dtstr)
1951
1952 # Produce naive datetime if no %z is provided
1953 self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
1954
1955 with self.assertRaises(ValueError): strptime("-2400", "%z")
1956 with self.assertRaises(ValueError): strptime("-000", "%z")
1957
1958 def test_more_timetuple(self):
1959 # This tests fields beyond those tested by the TestDate.test_timetuple.
1960 t = self.theclass(2004, 12, 31, 6, 22, 33)
1961 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
1962 self.assertEqual(t.timetuple(),
1963 (t.year, t.month, t.day,
1964 t.hour, t.minute, t.second,
1965 t.weekday(),
1966 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
1967 -1))
1968 tt = t.timetuple()
1969 self.assertEqual(tt.tm_year, t.year)
1970 self.assertEqual(tt.tm_mon, t.month)
1971 self.assertEqual(tt.tm_mday, t.day)
1972 self.assertEqual(tt.tm_hour, t.hour)
1973 self.assertEqual(tt.tm_min, t.minute)
1974 self.assertEqual(tt.tm_sec, t.second)
1975 self.assertEqual(tt.tm_wday, t.weekday())
1976 self.assertEqual(tt.tm_yday, t.toordinal() -
1977 date(t.year, 1, 1).toordinal() + 1)
1978 self.assertEqual(tt.tm_isdst, -1)
1979
1980 def test_more_strftime(self):
1981 # This tests fields beyond those tested by the TestDate.test_strftime.
1982 t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
1983 self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
1984 "12 31 04 000047 33 22 06 366")
1985
1986 def test_extract(self):
1987 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1988 self.assertEqual(dt.date(), date(2002, 3, 4))
1989 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
1990
1991 def test_combine(self):
1992 d = date(2002, 3, 4)
1993 t = time(18, 45, 3, 1234)
1994 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1995 combine = self.theclass.combine
1996 dt = combine(d, t)
1997 self.assertEqual(dt, expected)
1998
1999 dt = combine(time=t, date=d)
2000 self.assertEqual(dt, expected)
2001
2002 self.assertEqual(d, dt.date())
2003 self.assertEqual(t, dt.time())
2004 self.assertEqual(dt, combine(dt.date(), dt.time()))
2005
2006 self.assertRaises(TypeError, combine) # need an arg
2007 self.assertRaises(TypeError, combine, d) # need two args
2008 self.assertRaises(TypeError, combine, t, d) # args reversed
2009 self.assertRaises(TypeError, combine, d, t, 1) # too many args
2010 self.assertRaises(TypeError, combine, "date", "time") # wrong types
2011 self.assertRaises(TypeError, combine, d, "time") # wrong type
2012 self.assertRaises(TypeError, combine, "date", t) # wrong type
2013
2014 def test_replace(self):
2015 cls = self.theclass
2016 args = [1, 2, 3, 4, 5, 6, 7]
2017 base = cls(*args)
2018 self.assertEqual(base, base.replace())
2019
2020 i = 0
2021 for name, newval in (("year", 2),
2022 ("month", 3),
2023 ("day", 4),
2024 ("hour", 5),
2025 ("minute", 6),
2026 ("second", 7),
2027 ("microsecond", 8)):
2028 newargs = args[:]
2029 newargs[i] = newval
2030 expected = cls(*newargs)
2031 got = base.replace(**{name: newval})
2032 self.assertEqual(expected, got)
2033 i += 1
2034
2035 # Out of bounds.
2036 base = cls(2000, 2, 29)
2037 self.assertRaises(ValueError, base.replace, year=2001)
2038
2039 def test_astimezone(self):
2040 # Pretty boring! The TZ test is more interesting here. astimezone()
2041 # simply can't be applied to a naive object.
2042 dt = self.theclass.now()
2043 f = FixedOffset(44, "")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04002044 self.assertRaises(ValueError, dt.astimezone) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002045 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
2046 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
2047 self.assertRaises(ValueError, dt.astimezone, f) # naive
2048 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
2049
2050 class Bogus(tzinfo):
2051 def utcoffset(self, dt): return None
2052 def dst(self, dt): return timedelta(0)
2053 bog = Bogus()
2054 self.assertRaises(ValueError, dt.astimezone, bog) # naive
2055 self.assertRaises(ValueError,
2056 dt.replace(tzinfo=bog).astimezone, f)
2057
2058 class AlsoBogus(tzinfo):
2059 def utcoffset(self, dt): return timedelta(0)
2060 def dst(self, dt): return None
2061 alsobog = AlsoBogus()
2062 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
2063
2064 def test_subclass_datetime(self):
2065
2066 class C(self.theclass):
2067 theAnswer = 42
2068
2069 def __new__(cls, *args, **kws):
2070 temp = kws.copy()
2071 extra = temp.pop('extra')
2072 result = self.theclass.__new__(cls, *args, **temp)
2073 result.extra = extra
2074 return result
2075
2076 def newmeth(self, start):
2077 return start + self.year + self.month + self.second
2078
2079 args = 2003, 4, 14, 12, 13, 41
2080
2081 dt1 = self.theclass(*args)
2082 dt2 = C(*args, **{'extra': 7})
2083
2084 self.assertEqual(dt2.__class__, C)
2085 self.assertEqual(dt2.theAnswer, 42)
2086 self.assertEqual(dt2.extra, 7)
2087 self.assertEqual(dt1.toordinal(), dt2.toordinal())
2088 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
2089 dt1.second - 7)
2090
2091class TestSubclassDateTime(TestDateTime):
2092 theclass = SubclassDatetime
2093 # Override tests not designed for subclass
Zachary Ware9fe6d862013-12-08 00:20:35 -06002094 @unittest.skip('not appropriate for subclasses')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002095 def test_roundtrip(self):
2096 pass
2097
2098class SubclassTime(time):
2099 sub_var = 1
2100
2101class TestTime(HarmlessMixedComparison, unittest.TestCase):
2102
2103 theclass = time
2104
2105 def test_basic_attributes(self):
2106 t = self.theclass(12, 0)
2107 self.assertEqual(t.hour, 12)
2108 self.assertEqual(t.minute, 0)
2109 self.assertEqual(t.second, 0)
2110 self.assertEqual(t.microsecond, 0)
2111
2112 def test_basic_attributes_nonzero(self):
2113 # Make sure all attributes are non-zero so bugs in
2114 # bit-shifting access show up.
2115 t = self.theclass(12, 59, 59, 8000)
2116 self.assertEqual(t.hour, 12)
2117 self.assertEqual(t.minute, 59)
2118 self.assertEqual(t.second, 59)
2119 self.assertEqual(t.microsecond, 8000)
2120
2121 def test_roundtrip(self):
2122 t = self.theclass(1, 2, 3, 4)
2123
2124 # Verify t -> string -> time identity.
2125 s = repr(t)
2126 self.assertTrue(s.startswith('datetime.'))
2127 s = s[9:]
2128 t2 = eval(s)
2129 self.assertEqual(t, t2)
2130
2131 # Verify identity via reconstructing from pieces.
2132 t2 = self.theclass(t.hour, t.minute, t.second,
2133 t.microsecond)
2134 self.assertEqual(t, t2)
2135
2136 def test_comparing(self):
2137 args = [1, 2, 3, 4]
2138 t1 = self.theclass(*args)
2139 t2 = self.theclass(*args)
2140 self.assertEqual(t1, t2)
2141 self.assertTrue(t1 <= t2)
2142 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002143 self.assertFalse(t1 != t2)
2144 self.assertFalse(t1 < t2)
2145 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002146
2147 for i in range(len(args)):
2148 newargs = args[:]
2149 newargs[i] = args[i] + 1
2150 t2 = self.theclass(*newargs) # this is larger than t1
2151 self.assertTrue(t1 < t2)
2152 self.assertTrue(t2 > t1)
2153 self.assertTrue(t1 <= t2)
2154 self.assertTrue(t2 >= t1)
2155 self.assertTrue(t1 != t2)
2156 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002157 self.assertFalse(t1 == t2)
2158 self.assertFalse(t2 == t1)
2159 self.assertFalse(t1 > t2)
2160 self.assertFalse(t2 < t1)
2161 self.assertFalse(t1 >= t2)
2162 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002163
2164 for badarg in OTHERSTUFF:
2165 self.assertEqual(t1 == badarg, False)
2166 self.assertEqual(t1 != badarg, True)
2167 self.assertEqual(badarg == t1, False)
2168 self.assertEqual(badarg != t1, True)
2169
2170 self.assertRaises(TypeError, lambda: t1 <= badarg)
2171 self.assertRaises(TypeError, lambda: t1 < badarg)
2172 self.assertRaises(TypeError, lambda: t1 > badarg)
2173 self.assertRaises(TypeError, lambda: t1 >= badarg)
2174 self.assertRaises(TypeError, lambda: badarg <= t1)
2175 self.assertRaises(TypeError, lambda: badarg < t1)
2176 self.assertRaises(TypeError, lambda: badarg > t1)
2177 self.assertRaises(TypeError, lambda: badarg >= t1)
2178
2179 def test_bad_constructor_arguments(self):
2180 # bad hours
2181 self.theclass(0, 0) # no exception
2182 self.theclass(23, 0) # no exception
2183 self.assertRaises(ValueError, self.theclass, -1, 0)
2184 self.assertRaises(ValueError, self.theclass, 24, 0)
2185 # bad minutes
2186 self.theclass(23, 0) # no exception
2187 self.theclass(23, 59) # no exception
2188 self.assertRaises(ValueError, self.theclass, 23, -1)
2189 self.assertRaises(ValueError, self.theclass, 23, 60)
2190 # bad seconds
2191 self.theclass(23, 59, 0) # no exception
2192 self.theclass(23, 59, 59) # no exception
2193 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
2194 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
2195 # bad microseconds
2196 self.theclass(23, 59, 59, 0) # no exception
2197 self.theclass(23, 59, 59, 999999) # no exception
2198 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
2199 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
2200
2201 def test_hash_equality(self):
2202 d = self.theclass(23, 30, 17)
2203 e = self.theclass(23, 30, 17)
2204 self.assertEqual(d, e)
2205 self.assertEqual(hash(d), hash(e))
2206
2207 dic = {d: 1}
2208 dic[e] = 2
2209 self.assertEqual(len(dic), 1)
2210 self.assertEqual(dic[d], 2)
2211 self.assertEqual(dic[e], 2)
2212
2213 d = self.theclass(0, 5, 17)
2214 e = self.theclass(0, 5, 17)
2215 self.assertEqual(d, e)
2216 self.assertEqual(hash(d), hash(e))
2217
2218 dic = {d: 1}
2219 dic[e] = 2
2220 self.assertEqual(len(dic), 1)
2221 self.assertEqual(dic[d], 2)
2222 self.assertEqual(dic[e], 2)
2223
2224 def test_isoformat(self):
2225 t = self.theclass(4, 5, 1, 123)
2226 self.assertEqual(t.isoformat(), "04:05:01.000123")
2227 self.assertEqual(t.isoformat(), str(t))
2228
2229 t = self.theclass()
2230 self.assertEqual(t.isoformat(), "00:00:00")
2231 self.assertEqual(t.isoformat(), str(t))
2232
2233 t = self.theclass(microsecond=1)
2234 self.assertEqual(t.isoformat(), "00:00:00.000001")
2235 self.assertEqual(t.isoformat(), str(t))
2236
2237 t = self.theclass(microsecond=10)
2238 self.assertEqual(t.isoformat(), "00:00:00.000010")
2239 self.assertEqual(t.isoformat(), str(t))
2240
2241 t = self.theclass(microsecond=100)
2242 self.assertEqual(t.isoformat(), "00:00:00.000100")
2243 self.assertEqual(t.isoformat(), str(t))
2244
2245 t = self.theclass(microsecond=1000)
2246 self.assertEqual(t.isoformat(), "00:00:00.001000")
2247 self.assertEqual(t.isoformat(), str(t))
2248
2249 t = self.theclass(microsecond=10000)
2250 self.assertEqual(t.isoformat(), "00:00:00.010000")
2251 self.assertEqual(t.isoformat(), str(t))
2252
2253 t = self.theclass(microsecond=100000)
2254 self.assertEqual(t.isoformat(), "00:00:00.100000")
2255 self.assertEqual(t.isoformat(), str(t))
2256
2257 def test_1653736(self):
2258 # verify it doesn't accept extra keyword arguments
2259 t = self.theclass(second=1)
2260 self.assertRaises(TypeError, t.isoformat, foo=3)
2261
2262 def test_strftime(self):
2263 t = self.theclass(1, 2, 3, 4)
2264 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
2265 # A naive object replaces %z and %Z with empty strings.
2266 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
2267
2268 def test_format(self):
2269 t = self.theclass(1, 2, 3, 4)
2270 self.assertEqual(t.__format__(''), str(t))
2271
2272 # check that a derived class's __str__() gets called
2273 class A(self.theclass):
2274 def __str__(self):
2275 return 'A'
2276 a = A(1, 2, 3, 4)
2277 self.assertEqual(a.__format__(''), 'A')
2278
2279 # check that a derived class's strftime gets called
2280 class B(self.theclass):
2281 def strftime(self, format_spec):
2282 return 'B'
2283 b = B(1, 2, 3, 4)
2284 self.assertEqual(b.__format__(''), str(t))
2285
2286 for fmt in ['%H %M %S',
2287 ]:
2288 self.assertEqual(t.__format__(fmt), t.strftime(fmt))
2289 self.assertEqual(a.__format__(fmt), t.strftime(fmt))
2290 self.assertEqual(b.__format__(fmt), 'B')
2291
2292 def test_str(self):
2293 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
2294 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
2295 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
2296 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
2297 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
2298
2299 def test_repr(self):
2300 name = 'datetime.' + self.theclass.__name__
2301 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
2302 "%s(1, 2, 3, 4)" % name)
2303 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
2304 "%s(10, 2, 3, 4000)" % name)
2305 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
2306 "%s(0, 2, 3, 400000)" % name)
2307 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
2308 "%s(12, 2, 3)" % name)
2309 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
2310 "%s(23, 15)" % name)
2311
2312 def test_resolution_info(self):
2313 self.assertIsInstance(self.theclass.min, self.theclass)
2314 self.assertIsInstance(self.theclass.max, self.theclass)
2315 self.assertIsInstance(self.theclass.resolution, timedelta)
2316 self.assertTrue(self.theclass.max > self.theclass.min)
2317
2318 def test_pickling(self):
2319 args = 20, 59, 16, 64**2
2320 orig = self.theclass(*args)
2321 for pickler, unpickler, proto in pickle_choices:
2322 green = pickler.dumps(orig, proto)
2323 derived = unpickler.loads(green)
2324 self.assertEqual(orig, derived)
2325
2326 def test_pickling_subclass_time(self):
2327 args = 20, 59, 16, 64**2
2328 orig = SubclassTime(*args)
2329 for pickler, unpickler, proto in pickle_choices:
2330 green = pickler.dumps(orig, proto)
2331 derived = unpickler.loads(green)
2332 self.assertEqual(orig, derived)
2333
2334 def test_bool(self):
2335 cls = self.theclass
2336 self.assertTrue(cls(1))
2337 self.assertTrue(cls(0, 1))
2338 self.assertTrue(cls(0, 0, 1))
2339 self.assertTrue(cls(0, 0, 0, 1))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002340 self.assertFalse(cls(0))
2341 self.assertFalse(cls())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002342
2343 def test_replace(self):
2344 cls = self.theclass
2345 args = [1, 2, 3, 4]
2346 base = cls(*args)
2347 self.assertEqual(base, base.replace())
2348
2349 i = 0
2350 for name, newval in (("hour", 5),
2351 ("minute", 6),
2352 ("second", 7),
2353 ("microsecond", 8)):
2354 newargs = args[:]
2355 newargs[i] = newval
2356 expected = cls(*newargs)
2357 got = base.replace(**{name: newval})
2358 self.assertEqual(expected, got)
2359 i += 1
2360
2361 # Out of bounds.
2362 base = cls(1)
2363 self.assertRaises(ValueError, base.replace, hour=24)
2364 self.assertRaises(ValueError, base.replace, minute=-1)
2365 self.assertRaises(ValueError, base.replace, second=100)
2366 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2367
2368 def test_subclass_time(self):
2369
2370 class C(self.theclass):
2371 theAnswer = 42
2372
2373 def __new__(cls, *args, **kws):
2374 temp = kws.copy()
2375 extra = temp.pop('extra')
2376 result = self.theclass.__new__(cls, *args, **temp)
2377 result.extra = extra
2378 return result
2379
2380 def newmeth(self, start):
2381 return start + self.hour + self.second
2382
2383 args = 4, 5, 6
2384
2385 dt1 = self.theclass(*args)
2386 dt2 = C(*args, **{'extra': 7})
2387
2388 self.assertEqual(dt2.__class__, C)
2389 self.assertEqual(dt2.theAnswer, 42)
2390 self.assertEqual(dt2.extra, 7)
2391 self.assertEqual(dt1.isoformat(), dt2.isoformat())
2392 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2393
2394 def test_backdoor_resistance(self):
2395 # see TestDate.test_backdoor_resistance().
2396 base = '2:59.0'
2397 for hour_byte in ' ', '9', chr(24), '\xff':
2398 self.assertRaises(TypeError, self.theclass,
2399 hour_byte + base[1:])
2400
2401# A mixin for classes with a tzinfo= argument. Subclasses must define
2402# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
2403# must be legit (which is true for time and datetime).
2404class TZInfoBase:
2405
2406 def test_argument_passing(self):
2407 cls = self.theclass
2408 # A datetime passes itself on, a time passes None.
2409 class introspective(tzinfo):
2410 def tzname(self, dt): return dt and "real" or "none"
2411 def utcoffset(self, dt):
2412 return timedelta(minutes = dt and 42 or -42)
2413 dst = utcoffset
2414
2415 obj = cls(1, 2, 3, tzinfo=introspective())
2416
2417 expected = cls is time and "none" or "real"
2418 self.assertEqual(obj.tzname(), expected)
2419
2420 expected = timedelta(minutes=(cls is time and -42 or 42))
2421 self.assertEqual(obj.utcoffset(), expected)
2422 self.assertEqual(obj.dst(), expected)
2423
2424 def test_bad_tzinfo_classes(self):
2425 cls = self.theclass
2426 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
2427
2428 class NiceTry(object):
2429 def __init__(self): pass
2430 def utcoffset(self, dt): pass
2431 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
2432
2433 class BetterTry(tzinfo):
2434 def __init__(self): pass
2435 def utcoffset(self, dt): pass
2436 b = BetterTry()
2437 t = cls(1, 1, 1, tzinfo=b)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002438 self.assertIs(t.tzinfo, b)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002439
2440 def test_utc_offset_out_of_bounds(self):
2441 class Edgy(tzinfo):
2442 def __init__(self, offset):
2443 self.offset = timedelta(minutes=offset)
2444 def utcoffset(self, dt):
2445 return self.offset
2446
2447 cls = self.theclass
2448 for offset, legit in ((-1440, False),
2449 (-1439, True),
2450 (1439, True),
2451 (1440, False)):
2452 if cls is time:
2453 t = cls(1, 2, 3, tzinfo=Edgy(offset))
2454 elif cls is datetime:
2455 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
2456 else:
2457 assert 0, "impossible"
2458 if legit:
2459 aofs = abs(offset)
2460 h, m = divmod(aofs, 60)
2461 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
2462 if isinstance(t, datetime):
2463 t = t.timetz()
2464 self.assertEqual(str(t), "01:02:03" + tag)
2465 else:
2466 self.assertRaises(ValueError, str, t)
2467
2468 def test_tzinfo_classes(self):
2469 cls = self.theclass
2470 class C1(tzinfo):
2471 def utcoffset(self, dt): return None
2472 def dst(self, dt): return None
2473 def tzname(self, dt): return None
2474 for t in (cls(1, 1, 1),
2475 cls(1, 1, 1, tzinfo=None),
2476 cls(1, 1, 1, tzinfo=C1())):
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002477 self.assertIsNone(t.utcoffset())
2478 self.assertIsNone(t.dst())
2479 self.assertIsNone(t.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002480
2481 class C3(tzinfo):
2482 def utcoffset(self, dt): return timedelta(minutes=-1439)
2483 def dst(self, dt): return timedelta(minutes=1439)
2484 def tzname(self, dt): return "aname"
2485 t = cls(1, 1, 1, tzinfo=C3())
2486 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
2487 self.assertEqual(t.dst(), timedelta(minutes=1439))
2488 self.assertEqual(t.tzname(), "aname")
2489
2490 # Wrong types.
2491 class C4(tzinfo):
2492 def utcoffset(self, dt): return "aname"
2493 def dst(self, dt): return 7
2494 def tzname(self, dt): return 0
2495 t = cls(1, 1, 1, tzinfo=C4())
2496 self.assertRaises(TypeError, t.utcoffset)
2497 self.assertRaises(TypeError, t.dst)
2498 self.assertRaises(TypeError, t.tzname)
2499
2500 # Offset out of range.
2501 class C6(tzinfo):
2502 def utcoffset(self, dt): return timedelta(hours=-24)
2503 def dst(self, dt): return timedelta(hours=24)
2504 t = cls(1, 1, 1, tzinfo=C6())
2505 self.assertRaises(ValueError, t.utcoffset)
2506 self.assertRaises(ValueError, t.dst)
2507
2508 # Not a whole number of minutes.
2509 class C7(tzinfo):
2510 def utcoffset(self, dt): return timedelta(seconds=61)
2511 def dst(self, dt): return timedelta(microseconds=-81)
2512 t = cls(1, 1, 1, tzinfo=C7())
2513 self.assertRaises(ValueError, t.utcoffset)
2514 self.assertRaises(ValueError, t.dst)
2515
2516 def test_aware_compare(self):
2517 cls = self.theclass
2518
2519 # Ensure that utcoffset() gets ignored if the comparands have
2520 # the same tzinfo member.
2521 class OperandDependentOffset(tzinfo):
2522 def utcoffset(self, t):
2523 if t.minute < 10:
2524 # d0 and d1 equal after adjustment
2525 return timedelta(minutes=t.minute)
2526 else:
2527 # d2 off in the weeds
2528 return timedelta(minutes=59)
2529
2530 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
2531 d0 = base.replace(minute=3)
2532 d1 = base.replace(minute=9)
2533 d2 = base.replace(minute=11)
2534 for x in d0, d1, d2:
2535 for y in d0, d1, d2:
2536 for op in lt, le, gt, ge, eq, ne:
2537 got = op(x, y)
2538 expected = op(x.minute, y.minute)
2539 self.assertEqual(got, expected)
2540
2541 # However, if they're different members, uctoffset is not ignored.
2542 # Note that a time can't actually have an operand-depedent offset,
2543 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
2544 # so skip this test for time.
2545 if cls is not time:
2546 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2547 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2548 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2549 for x in d0, d1, d2:
2550 for y in d0, d1, d2:
2551 got = (x > y) - (x < y)
2552 if (x is d0 or x is d1) and (y is d0 or y is d1):
2553 expected = 0
2554 elif x is y is d2:
2555 expected = 0
2556 elif x is d2:
2557 expected = -1
2558 else:
2559 assert y is d2
2560 expected = 1
2561 self.assertEqual(got, expected)
2562
2563
2564# Testing time objects with a non-None tzinfo.
2565class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
2566 theclass = time
2567
2568 def test_empty(self):
2569 t = self.theclass()
2570 self.assertEqual(t.hour, 0)
2571 self.assertEqual(t.minute, 0)
2572 self.assertEqual(t.second, 0)
2573 self.assertEqual(t.microsecond, 0)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002574 self.assertIsNone(t.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002575
2576 def test_zones(self):
2577 est = FixedOffset(-300, "EST", 1)
2578 utc = FixedOffset(0, "UTC", -2)
2579 met = FixedOffset(60, "MET", 3)
2580 t1 = time( 7, 47, tzinfo=est)
2581 t2 = time(12, 47, tzinfo=utc)
2582 t3 = time(13, 47, tzinfo=met)
2583 t4 = time(microsecond=40)
2584 t5 = time(microsecond=40, tzinfo=utc)
2585
2586 self.assertEqual(t1.tzinfo, est)
2587 self.assertEqual(t2.tzinfo, utc)
2588 self.assertEqual(t3.tzinfo, met)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002589 self.assertIsNone(t4.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002590 self.assertEqual(t5.tzinfo, utc)
2591
2592 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2593 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2594 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002595 self.assertIsNone(t4.utcoffset())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002596 self.assertRaises(TypeError, t1.utcoffset, "no args")
2597
2598 self.assertEqual(t1.tzname(), "EST")
2599 self.assertEqual(t2.tzname(), "UTC")
2600 self.assertEqual(t3.tzname(), "MET")
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002601 self.assertIsNone(t4.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002602 self.assertRaises(TypeError, t1.tzname, "no args")
2603
2604 self.assertEqual(t1.dst(), timedelta(minutes=1))
2605 self.assertEqual(t2.dst(), timedelta(minutes=-2))
2606 self.assertEqual(t3.dst(), timedelta(minutes=3))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002607 self.assertIsNone(t4.dst())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002608 self.assertRaises(TypeError, t1.dst, "no args")
2609
2610 self.assertEqual(hash(t1), hash(t2))
2611 self.assertEqual(hash(t1), hash(t3))
2612 self.assertEqual(hash(t2), hash(t3))
2613
2614 self.assertEqual(t1, t2)
2615 self.assertEqual(t1, t3)
2616 self.assertEqual(t2, t3)
Alexander Belopolsky08313822012-06-15 20:19:47 -04002617 self.assertNotEqual(t4, t5) # mixed tz-aware & naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002618 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
2619 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
2620
2621 self.assertEqual(str(t1), "07:47:00-05:00")
2622 self.assertEqual(str(t2), "12:47:00+00:00")
2623 self.assertEqual(str(t3), "13:47:00+01:00")
2624 self.assertEqual(str(t4), "00:00:00.000040")
2625 self.assertEqual(str(t5), "00:00:00.000040+00:00")
2626
2627 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
2628 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
2629 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
2630 self.assertEqual(t4.isoformat(), "00:00:00.000040")
2631 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
2632
2633 d = 'datetime.time'
2634 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
2635 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
2636 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
2637 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
2638 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
2639
2640 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
2641 "07:47:00 %Z=EST %z=-0500")
2642 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
2643 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
2644
2645 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
2646 t1 = time(23, 59, tzinfo=yuck)
2647 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
2648 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
2649
2650 # Check that an invalid tzname result raises an exception.
2651 class Badtzname(tzinfo):
Alexander Belopolskye239d232010-12-08 23:31:48 +00002652 tz = 42
2653 def tzname(self, dt): return self.tz
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002654 t = time(2, 3, 4, tzinfo=Badtzname())
2655 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
2656 self.assertRaises(TypeError, t.strftime, "%Z")
2657
Alexander Belopolskye239d232010-12-08 23:31:48 +00002658 # Issue #6697:
2659 if '_Fast' in str(type(self)):
2660 Badtzname.tz = '\ud800'
2661 self.assertRaises(ValueError, t.strftime, "%Z")
2662
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002663 def test_hash_edge_cases(self):
2664 # Offsets that overflow a basic time.
2665 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2666 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2667 self.assertEqual(hash(t1), hash(t2))
2668
2669 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2670 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2671 self.assertEqual(hash(t1), hash(t2))
2672
2673 def test_pickling(self):
2674 # Try one without a tzinfo.
2675 args = 20, 59, 16, 64**2
2676 orig = self.theclass(*args)
2677 for pickler, unpickler, proto in pickle_choices:
2678 green = pickler.dumps(orig, proto)
2679 derived = unpickler.loads(green)
2680 self.assertEqual(orig, derived)
2681
2682 # Try one with a tzinfo.
2683 tinfo = PicklableFixedOffset(-300, 'cookie')
2684 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
2685 for pickler, unpickler, proto in pickle_choices:
2686 green = pickler.dumps(orig, proto)
2687 derived = unpickler.loads(green)
2688 self.assertEqual(orig, derived)
2689 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
2690 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2691 self.assertEqual(derived.tzname(), 'cookie')
2692
2693 def test_more_bool(self):
2694 # Test cases with non-None tzinfo.
2695 cls = self.theclass
2696
2697 t = cls(0, tzinfo=FixedOffset(-300, ""))
2698 self.assertTrue(t)
2699
2700 t = cls(5, tzinfo=FixedOffset(-300, ""))
2701 self.assertTrue(t)
2702
2703 t = cls(5, tzinfo=FixedOffset(300, ""))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002704 self.assertFalse(t)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002705
2706 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002707 self.assertFalse(t)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002708
2709 # Mostly ensuring this doesn't overflow internally.
2710 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
2711 self.assertTrue(t)
2712
2713 # But this should yield a value error -- the utcoffset is bogus.
2714 t = cls(0, tzinfo=FixedOffset(24*60, ""))
2715 self.assertRaises(ValueError, lambda: bool(t))
2716
2717 # Likewise.
2718 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
2719 self.assertRaises(ValueError, lambda: bool(t))
2720
2721 def test_replace(self):
2722 cls = self.theclass
2723 z100 = FixedOffset(100, "+100")
2724 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2725 args = [1, 2, 3, 4, z100]
2726 base = cls(*args)
2727 self.assertEqual(base, base.replace())
2728
2729 i = 0
2730 for name, newval in (("hour", 5),
2731 ("minute", 6),
2732 ("second", 7),
2733 ("microsecond", 8),
2734 ("tzinfo", zm200)):
2735 newargs = args[:]
2736 newargs[i] = newval
2737 expected = cls(*newargs)
2738 got = base.replace(**{name: newval})
2739 self.assertEqual(expected, got)
2740 i += 1
2741
2742 # Ensure we can get rid of a tzinfo.
2743 self.assertEqual(base.tzname(), "+100")
2744 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002745 self.assertIsNone(base2.tzinfo)
2746 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002747
2748 # Ensure we can add one.
2749 base3 = base2.replace(tzinfo=z100)
2750 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002751 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002752
2753 # Out of bounds.
2754 base = cls(1)
2755 self.assertRaises(ValueError, base.replace, hour=24)
2756 self.assertRaises(ValueError, base.replace, minute=-1)
2757 self.assertRaises(ValueError, base.replace, second=100)
2758 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2759
2760 def test_mixed_compare(self):
2761 t1 = time(1, 2, 3)
2762 t2 = time(1, 2, 3)
2763 self.assertEqual(t1, t2)
2764 t2 = t2.replace(tzinfo=None)
2765 self.assertEqual(t1, t2)
2766 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2767 self.assertEqual(t1, t2)
2768 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04002769 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002770
2771 # In time w/ identical tzinfo objects, utcoffset is ignored.
2772 class Varies(tzinfo):
2773 def __init__(self):
2774 self.offset = timedelta(minutes=22)
2775 def utcoffset(self, t):
2776 self.offset += timedelta(minutes=1)
2777 return self.offset
2778
2779 v = Varies()
2780 t1 = t2.replace(tzinfo=v)
2781 t2 = t2.replace(tzinfo=v)
2782 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2783 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2784 self.assertEqual(t1, t2)
2785
2786 # But if they're not identical, it isn't ignored.
2787 t2 = t2.replace(tzinfo=Varies())
2788 self.assertTrue(t1 < t2) # t1's offset counter still going up
2789
2790 def test_subclass_timetz(self):
2791
2792 class C(self.theclass):
2793 theAnswer = 42
2794
2795 def __new__(cls, *args, **kws):
2796 temp = kws.copy()
2797 extra = temp.pop('extra')
2798 result = self.theclass.__new__(cls, *args, **temp)
2799 result.extra = extra
2800 return result
2801
2802 def newmeth(self, start):
2803 return start + self.hour + self.second
2804
2805 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2806
2807 dt1 = self.theclass(*args)
2808 dt2 = C(*args, **{'extra': 7})
2809
2810 self.assertEqual(dt2.__class__, C)
2811 self.assertEqual(dt2.theAnswer, 42)
2812 self.assertEqual(dt2.extra, 7)
2813 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2814 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2815
2816
2817# Testing datetime objects with a non-None tzinfo.
2818
2819class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
2820 theclass = datetime
2821
2822 def test_trivial(self):
2823 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2824 self.assertEqual(dt.year, 1)
2825 self.assertEqual(dt.month, 2)
2826 self.assertEqual(dt.day, 3)
2827 self.assertEqual(dt.hour, 4)
2828 self.assertEqual(dt.minute, 5)
2829 self.assertEqual(dt.second, 6)
2830 self.assertEqual(dt.microsecond, 7)
2831 self.assertEqual(dt.tzinfo, None)
2832
2833 def test_even_more_compare(self):
2834 # The test_compare() and test_more_compare() inherited from TestDate
2835 # and TestDateTime covered non-tzinfo cases.
2836
2837 # Smallest possible after UTC adjustment.
2838 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2839 # Largest possible after UTC adjustment.
2840 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2841 tzinfo=FixedOffset(-1439, ""))
2842
2843 # Make sure those compare correctly, and w/o overflow.
2844 self.assertTrue(t1 < t2)
2845 self.assertTrue(t1 != t2)
2846 self.assertTrue(t2 > t1)
2847
2848 self.assertEqual(t1, t1)
2849 self.assertEqual(t2, t2)
2850
2851 # Equal afer adjustment.
2852 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2853 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2854 self.assertEqual(t1, t2)
2855
2856 # Change t1 not to subtract a minute, and t1 should be larger.
2857 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2858 self.assertTrue(t1 > t2)
2859
2860 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2861 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2862 self.assertTrue(t1 < t2)
2863
2864 # Back to the original t1, but make seconds resolve it.
2865 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2866 second=1)
2867 self.assertTrue(t1 > t2)
2868
2869 # Likewise, but make microseconds resolve it.
2870 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2871 microsecond=1)
2872 self.assertTrue(t1 > t2)
2873
Alexander Belopolsky08313822012-06-15 20:19:47 -04002874 # Make t2 naive and it should differ.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002875 t2 = self.theclass.min
Alexander Belopolsky08313822012-06-15 20:19:47 -04002876 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002877 self.assertEqual(t2, t2)
2878
2879 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2880 class Naive(tzinfo):
2881 def utcoffset(self, dt): return None
2882 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
Alexander Belopolsky08313822012-06-15 20:19:47 -04002883 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002884 self.assertEqual(t2, t2)
2885
2886 # OTOH, it's OK to compare two of these mixing the two ways of being
2887 # naive.
2888 t1 = self.theclass(5, 6, 7)
2889 self.assertEqual(t1, t2)
2890
2891 # Try a bogus uctoffset.
2892 class Bogus(tzinfo):
2893 def utcoffset(self, dt):
2894 return timedelta(minutes=1440) # out of bounds
2895 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2896 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
2897 self.assertRaises(ValueError, lambda: t1 == t2)
2898
2899 def test_pickling(self):
2900 # Try one without a tzinfo.
2901 args = 6, 7, 23, 20, 59, 1, 64**2
2902 orig = self.theclass(*args)
2903 for pickler, unpickler, proto in pickle_choices:
2904 green = pickler.dumps(orig, proto)
2905 derived = unpickler.loads(green)
2906 self.assertEqual(orig, derived)
2907
2908 # Try one with a tzinfo.
2909 tinfo = PicklableFixedOffset(-300, 'cookie')
2910 orig = self.theclass(*args, **{'tzinfo': tinfo})
2911 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
2912 for pickler, unpickler, proto in pickle_choices:
2913 green = pickler.dumps(orig, proto)
2914 derived = unpickler.loads(green)
2915 self.assertEqual(orig, derived)
2916 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
2917 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2918 self.assertEqual(derived.tzname(), 'cookie')
2919
2920 def test_extreme_hashes(self):
2921 # If an attempt is made to hash these via subtracting the offset
2922 # then hashing a datetime object, OverflowError results. The
2923 # Python implementation used to blow up here.
2924 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2925 hash(t)
2926 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2927 tzinfo=FixedOffset(-1439, ""))
2928 hash(t)
2929
2930 # OTOH, an OOB offset should blow up.
2931 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2932 self.assertRaises(ValueError, hash, t)
2933
2934 def test_zones(self):
2935 est = FixedOffset(-300, "EST")
2936 utc = FixedOffset(0, "UTC")
2937 met = FixedOffset(60, "MET")
2938 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2939 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2940 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
2941 self.assertEqual(t1.tzinfo, est)
2942 self.assertEqual(t2.tzinfo, utc)
2943 self.assertEqual(t3.tzinfo, met)
2944 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2945 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2946 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
2947 self.assertEqual(t1.tzname(), "EST")
2948 self.assertEqual(t2.tzname(), "UTC")
2949 self.assertEqual(t3.tzname(), "MET")
2950 self.assertEqual(hash(t1), hash(t2))
2951 self.assertEqual(hash(t1), hash(t3))
2952 self.assertEqual(hash(t2), hash(t3))
2953 self.assertEqual(t1, t2)
2954 self.assertEqual(t1, t3)
2955 self.assertEqual(t2, t3)
2956 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2957 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2958 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
2959 d = 'datetime.datetime(2002, 3, 19, '
2960 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2961 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2962 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2963
2964 def test_combine(self):
2965 met = FixedOffset(60, "MET")
2966 d = date(2002, 3, 4)
2967 tz = time(18, 45, 3, 1234, tzinfo=met)
2968 dt = datetime.combine(d, tz)
2969 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
2970 tzinfo=met))
2971
2972 def test_extract(self):
2973 met = FixedOffset(60, "MET")
2974 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2975 self.assertEqual(dt.date(), date(2002, 3, 4))
2976 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2977 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
2978
2979 def test_tz_aware_arithmetic(self):
2980 import random
2981
2982 now = self.theclass.now()
2983 tz55 = FixedOffset(-330, "west 5:30")
2984 timeaware = now.time().replace(tzinfo=tz55)
2985 nowaware = self.theclass.combine(now.date(), timeaware)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002986 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002987 self.assertEqual(nowaware.timetz(), timeaware)
2988
2989 # Can't mix aware and non-aware.
2990 self.assertRaises(TypeError, lambda: now - nowaware)
2991 self.assertRaises(TypeError, lambda: nowaware - now)
2992
2993 # And adding datetime's doesn't make sense, aware or not.
2994 self.assertRaises(TypeError, lambda: now + nowaware)
2995 self.assertRaises(TypeError, lambda: nowaware + now)
2996 self.assertRaises(TypeError, lambda: nowaware + nowaware)
2997
2998 # Subtracting should yield 0.
2999 self.assertEqual(now - now, timedelta(0))
3000 self.assertEqual(nowaware - nowaware, timedelta(0))
3001
3002 # Adding a delta should preserve tzinfo.
3003 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
3004 nowawareplus = nowaware + delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003005 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003006 nowawareplus2 = delta + nowaware
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003007 self.assertIs(nowawareplus2.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003008 self.assertEqual(nowawareplus, nowawareplus2)
3009
3010 # that - delta should be what we started with, and that - what we
3011 # started with should be delta.
3012 diff = nowawareplus - delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003013 self.assertIs(diff.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003014 self.assertEqual(nowaware, diff)
3015 self.assertRaises(TypeError, lambda: delta - nowawareplus)
3016 self.assertEqual(nowawareplus - nowaware, delta)
3017
3018 # Make up a random timezone.
3019 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
3020 # Attach it to nowawareplus.
3021 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003022 self.assertIs(nowawareplus.tzinfo, tzr)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003023 # Make sure the difference takes the timezone adjustments into account.
3024 got = nowaware - nowawareplus
3025 # Expected: (nowaware base - nowaware offset) -
3026 # (nowawareplus base - nowawareplus offset) =
3027 # (nowaware base - nowawareplus base) +
3028 # (nowawareplus offset - nowaware offset) =
3029 # -delta + nowawareplus offset - nowaware offset
3030 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
3031 self.assertEqual(got, expected)
3032
3033 # Try max possible difference.
3034 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
3035 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3036 tzinfo=FixedOffset(-1439, "max"))
3037 maxdiff = max - min
3038 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
3039 timedelta(minutes=2*1439))
3040 # Different tzinfo, but the same offset
3041 tza = timezone(HOUR, 'A')
3042 tzb = timezone(HOUR, 'B')
3043 delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
3044 self.assertEqual(delta, self.theclass.min - self.theclass.max)
3045
3046 def test_tzinfo_now(self):
3047 meth = self.theclass.now
3048 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3049 base = meth()
3050 # Try with and without naming the keyword.
3051 off42 = FixedOffset(42, "42")
3052 another = meth(off42)
3053 again = meth(tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003054 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003055 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3056 # Bad argument with and w/o naming the keyword.
3057 self.assertRaises(TypeError, meth, 16)
3058 self.assertRaises(TypeError, meth, tzinfo=16)
3059 # Bad keyword name.
3060 self.assertRaises(TypeError, meth, tinfo=off42)
3061 # Too many args.
3062 self.assertRaises(TypeError, meth, off42, off42)
3063
3064 # We don't know which time zone we're in, and don't have a tzinfo
3065 # class to represent it, so seeing whether a tz argument actually
3066 # does a conversion is tricky.
3067 utc = FixedOffset(0, "utc", 0)
3068 for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
3069 timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
3070 for dummy in range(3):
3071 now = datetime.now(weirdtz)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003072 self.assertIs(now.tzinfo, weirdtz)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003073 utcnow = datetime.utcnow().replace(tzinfo=utc)
3074 now2 = utcnow.astimezone(weirdtz)
3075 if abs(now - now2) < timedelta(seconds=30):
3076 break
3077 # Else the code is broken, or more than 30 seconds passed between
3078 # calls; assuming the latter, just try again.
3079 else:
3080 # Three strikes and we're out.
3081 self.fail("utcnow(), now(tz), or astimezone() may be broken")
3082
3083 def test_tzinfo_fromtimestamp(self):
3084 import time
3085 meth = self.theclass.fromtimestamp
3086 ts = time.time()
3087 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3088 base = meth(ts)
3089 # Try with and without naming the keyword.
3090 off42 = FixedOffset(42, "42")
3091 another = meth(ts, off42)
3092 again = meth(ts, tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003093 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003094 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3095 # Bad argument with and w/o naming the keyword.
3096 self.assertRaises(TypeError, meth, ts, 16)
3097 self.assertRaises(TypeError, meth, ts, tzinfo=16)
3098 # Bad keyword name.
3099 self.assertRaises(TypeError, meth, ts, tinfo=off42)
3100 # Too many args.
3101 self.assertRaises(TypeError, meth, ts, off42, off42)
3102 # Too few args.
3103 self.assertRaises(TypeError, meth)
3104
3105 # Try to make sure tz= actually does some conversion.
3106 timestamp = 1000000000
3107 utcdatetime = datetime.utcfromtimestamp(timestamp)
3108 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
3109 # But on some flavor of Mac, it's nowhere near that. So we can't have
3110 # any idea here what time that actually is, we can only test that
3111 # relative changes match.
3112 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
3113 tz = FixedOffset(utcoffset, "tz", 0)
3114 expected = utcdatetime + utcoffset
3115 got = datetime.fromtimestamp(timestamp, tz)
3116 self.assertEqual(expected, got.replace(tzinfo=None))
3117
3118 def test_tzinfo_utcnow(self):
3119 meth = self.theclass.utcnow
3120 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3121 base = meth()
3122 # Try with and without naming the keyword; for whatever reason,
3123 # utcnow() doesn't accept a tzinfo argument.
3124 off42 = FixedOffset(42, "42")
3125 self.assertRaises(TypeError, meth, off42)
3126 self.assertRaises(TypeError, meth, tzinfo=off42)
3127
3128 def test_tzinfo_utcfromtimestamp(self):
3129 import time
3130 meth = self.theclass.utcfromtimestamp
3131 ts = time.time()
3132 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3133 base = meth(ts)
3134 # Try with and without naming the keyword; for whatever reason,
3135 # utcfromtimestamp() doesn't accept a tzinfo argument.
3136 off42 = FixedOffset(42, "42")
3137 self.assertRaises(TypeError, meth, ts, off42)
3138 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
3139
3140 def test_tzinfo_timetuple(self):
3141 # TestDateTime tested most of this. datetime adds a twist to the
3142 # DST flag.
3143 class DST(tzinfo):
3144 def __init__(self, dstvalue):
3145 if isinstance(dstvalue, int):
3146 dstvalue = timedelta(minutes=dstvalue)
3147 self.dstvalue = dstvalue
3148 def dst(self, dt):
3149 return self.dstvalue
3150
3151 cls = self.theclass
3152 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
3153 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
3154 t = d.timetuple()
3155 self.assertEqual(1, t.tm_year)
3156 self.assertEqual(1, t.tm_mon)
3157 self.assertEqual(1, t.tm_mday)
3158 self.assertEqual(10, t.tm_hour)
3159 self.assertEqual(20, t.tm_min)
3160 self.assertEqual(30, t.tm_sec)
3161 self.assertEqual(0, t.tm_wday)
3162 self.assertEqual(1, t.tm_yday)
3163 self.assertEqual(flag, t.tm_isdst)
3164
3165 # dst() returns wrong type.
3166 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
3167
3168 # dst() at the edge.
3169 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
3170 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
3171
3172 # dst() out of range.
3173 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
3174 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
3175
3176 def test_utctimetuple(self):
3177 class DST(tzinfo):
3178 def __init__(self, dstvalue=0):
3179 if isinstance(dstvalue, int):
3180 dstvalue = timedelta(minutes=dstvalue)
3181 self.dstvalue = dstvalue
3182 def dst(self, dt):
3183 return self.dstvalue
3184
3185 cls = self.theclass
3186 # This can't work: DST didn't implement utcoffset.
3187 self.assertRaises(NotImplementedError,
3188 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
3189
3190 class UOFS(DST):
3191 def __init__(self, uofs, dofs=None):
3192 DST.__init__(self, dofs)
3193 self.uofs = timedelta(minutes=uofs)
3194 def utcoffset(self, dt):
3195 return self.uofs
3196
3197 for dstvalue in -33, 33, 0, None:
3198 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
3199 t = d.utctimetuple()
3200 self.assertEqual(d.year, t.tm_year)
3201 self.assertEqual(d.month, t.tm_mon)
3202 self.assertEqual(d.day, t.tm_mday)
3203 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
3204 self.assertEqual(13, t.tm_min)
3205 self.assertEqual(d.second, t.tm_sec)
3206 self.assertEqual(d.weekday(), t.tm_wday)
3207 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
3208 t.tm_yday)
3209 # Ensure tm_isdst is 0 regardless of what dst() says: DST
3210 # is never in effect for a UTC time.
3211 self.assertEqual(0, t.tm_isdst)
3212
3213 # For naive datetime, utctimetuple == timetuple except for isdst
3214 d = cls(1, 2, 3, 10, 20, 30, 40)
3215 t = d.utctimetuple()
3216 self.assertEqual(t[:-1], d.timetuple()[:-1])
3217 self.assertEqual(0, t.tm_isdst)
3218 # Same if utcoffset is None
3219 class NOFS(DST):
3220 def utcoffset(self, dt):
3221 return None
3222 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
3223 t = d.utctimetuple()
3224 self.assertEqual(t[:-1], d.timetuple()[:-1])
3225 self.assertEqual(0, t.tm_isdst)
3226 # Check that bad tzinfo is detected
3227 class BOFS(DST):
3228 def utcoffset(self, dt):
3229 return "EST"
3230 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
3231 self.assertRaises(TypeError, d.utctimetuple)
3232
3233 # Check that utctimetuple() is the same as
3234 # astimezone(utc).timetuple()
3235 d = cls(2010, 11, 13, 14, 15, 16, 171819)
3236 for tz in [timezone.min, timezone.utc, timezone.max]:
3237 dtz = d.replace(tzinfo=tz)
3238 self.assertEqual(dtz.utctimetuple()[:-1],
3239 dtz.astimezone(timezone.utc).timetuple()[:-1])
3240 # At the edges, UTC adjustment can produce years out-of-range
3241 # for a datetime object. Ensure that an OverflowError is
3242 # raised.
3243 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
3244 # That goes back 1 minute less than a full day.
3245 self.assertRaises(OverflowError, tiny.utctimetuple)
3246
3247 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
3248 # That goes forward 1 minute less than a full day.
3249 self.assertRaises(OverflowError, huge.utctimetuple)
3250 # More overflow cases
3251 tiny = cls.min.replace(tzinfo=timezone(MINUTE))
3252 self.assertRaises(OverflowError, tiny.utctimetuple)
3253 huge = cls.max.replace(tzinfo=timezone(-MINUTE))
3254 self.assertRaises(OverflowError, huge.utctimetuple)
3255
3256 def test_tzinfo_isoformat(self):
3257 zero = FixedOffset(0, "+00:00")
3258 plus = FixedOffset(220, "+03:40")
3259 minus = FixedOffset(-231, "-03:51")
3260 unknown = FixedOffset(None, "")
3261
3262 cls = self.theclass
3263 datestr = '0001-02-03'
3264 for ofs in None, zero, plus, minus, unknown:
3265 for us in 0, 987001:
3266 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
3267 timestr = '04:05:59' + (us and '.987001' or '')
3268 ofsstr = ofs is not None and d.tzname() or ''
3269 tailstr = timestr + ofsstr
3270 iso = d.isoformat()
3271 self.assertEqual(iso, datestr + 'T' + tailstr)
3272 self.assertEqual(iso, d.isoformat('T'))
3273 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
3274 self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
3275 self.assertEqual(str(d), datestr + ' ' + tailstr)
3276
3277 def test_replace(self):
3278 cls = self.theclass
3279 z100 = FixedOffset(100, "+100")
3280 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3281 args = [1, 2, 3, 4, 5, 6, 7, z100]
3282 base = cls(*args)
3283 self.assertEqual(base, base.replace())
3284
3285 i = 0
3286 for name, newval in (("year", 2),
3287 ("month", 3),
3288 ("day", 4),
3289 ("hour", 5),
3290 ("minute", 6),
3291 ("second", 7),
3292 ("microsecond", 8),
3293 ("tzinfo", zm200)):
3294 newargs = args[:]
3295 newargs[i] = newval
3296 expected = cls(*newargs)
3297 got = base.replace(**{name: newval})
3298 self.assertEqual(expected, got)
3299 i += 1
3300
3301 # Ensure we can get rid of a tzinfo.
3302 self.assertEqual(base.tzname(), "+100")
3303 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003304 self.assertIsNone(base2.tzinfo)
3305 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003306
3307 # Ensure we can add one.
3308 base3 = base2.replace(tzinfo=z100)
3309 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003310 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003311
3312 # Out of bounds.
3313 base = cls(2000, 2, 29)
3314 self.assertRaises(ValueError, base.replace, year=2001)
3315
3316 def test_more_astimezone(self):
3317 # The inherited test_astimezone covered some trivial and error cases.
3318 fnone = FixedOffset(None, "None")
3319 f44m = FixedOffset(44, "44")
3320 fm5h = FixedOffset(-timedelta(hours=5), "m300")
3321
3322 dt = self.theclass.now(tz=f44m)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003323 self.assertIs(dt.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003324 # Replacing with degenerate tzinfo raises an exception.
3325 self.assertRaises(ValueError, dt.astimezone, fnone)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003326 # Replacing with same tzinfo makes no change.
3327 x = dt.astimezone(dt.tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003328 self.assertIs(x.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003329 self.assertEqual(x.date(), dt.date())
3330 self.assertEqual(x.time(), dt.time())
3331
3332 # Replacing with different tzinfo does adjust.
3333 got = dt.astimezone(fm5h)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003334 self.assertIs(got.tzinfo, fm5h)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003335 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
3336 expected = dt - dt.utcoffset() # in effect, convert to UTC
3337 expected += fm5h.utcoffset(dt) # and from there to local time
3338 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
3339 self.assertEqual(got.date(), expected.date())
3340 self.assertEqual(got.time(), expected.time())
3341 self.assertEqual(got.timetz(), expected.timetz())
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003342 self.assertIs(got.tzinfo, expected.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003343 self.assertEqual(got, expected)
3344
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003345 @support.run_with_tz('UTC')
3346 def test_astimezone_default_utc(self):
3347 dt = self.theclass.now(timezone.utc)
3348 self.assertEqual(dt.astimezone(None), dt)
3349 self.assertEqual(dt.astimezone(), dt)
3350
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003351 # Note that offset in TZ variable has the opposite sign to that
3352 # produced by %z directive.
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003353 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
3354 def test_astimezone_default_eastern(self):
3355 dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
3356 local = dt.astimezone()
3357 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003358 self.assertEqual(local.strftime("%z %Z"), "-0500 EST")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003359 dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc)
3360 local = dt.astimezone()
3361 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003362 self.assertEqual(local.strftime("%z %Z"), "-0400 EDT")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003363
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003364 def test_aware_subtract(self):
3365 cls = self.theclass
3366
3367 # Ensure that utcoffset() is ignored when the operands have the
3368 # same tzinfo member.
3369 class OperandDependentOffset(tzinfo):
3370 def utcoffset(self, t):
3371 if t.minute < 10:
3372 # d0 and d1 equal after adjustment
3373 return timedelta(minutes=t.minute)
3374 else:
3375 # d2 off in the weeds
3376 return timedelta(minutes=59)
3377
3378 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
3379 d0 = base.replace(minute=3)
3380 d1 = base.replace(minute=9)
3381 d2 = base.replace(minute=11)
3382 for x in d0, d1, d2:
3383 for y in d0, d1, d2:
3384 got = x - y
3385 expected = timedelta(minutes=x.minute - y.minute)
3386 self.assertEqual(got, expected)
3387
3388 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
3389 # ignored.
3390 base = cls(8, 9, 10, 11, 12, 13, 14)
3391 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
3392 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
3393 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
3394 for x in d0, d1, d2:
3395 for y in d0, d1, d2:
3396 got = x - y
3397 if (x is d0 or x is d1) and (y is d0 or y is d1):
3398 expected = timedelta(0)
3399 elif x is y is d2:
3400 expected = timedelta(0)
3401 elif x is d2:
3402 expected = timedelta(minutes=(11-59)-0)
3403 else:
3404 assert y is d2
3405 expected = timedelta(minutes=0-(11-59))
3406 self.assertEqual(got, expected)
3407
3408 def test_mixed_compare(self):
3409 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
3410 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
3411 self.assertEqual(t1, t2)
3412 t2 = t2.replace(tzinfo=None)
3413 self.assertEqual(t1, t2)
3414 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3415 self.assertEqual(t1, t2)
3416 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04003417 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003418
3419 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
3420 class Varies(tzinfo):
3421 def __init__(self):
3422 self.offset = timedelta(minutes=22)
3423 def utcoffset(self, t):
3424 self.offset += timedelta(minutes=1)
3425 return self.offset
3426
3427 v = Varies()
3428 t1 = t2.replace(tzinfo=v)
3429 t2 = t2.replace(tzinfo=v)
3430 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3431 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3432 self.assertEqual(t1, t2)
3433
3434 # But if they're not identical, it isn't ignored.
3435 t2 = t2.replace(tzinfo=Varies())
3436 self.assertTrue(t1 < t2) # t1's offset counter still going up
3437
3438 def test_subclass_datetimetz(self):
3439
3440 class C(self.theclass):
3441 theAnswer = 42
3442
3443 def __new__(cls, *args, **kws):
3444 temp = kws.copy()
3445 extra = temp.pop('extra')
3446 result = self.theclass.__new__(cls, *args, **temp)
3447 result.extra = extra
3448 return result
3449
3450 def newmeth(self, start):
3451 return start + self.hour + self.year
3452
3453 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3454
3455 dt1 = self.theclass(*args)
3456 dt2 = C(*args, **{'extra': 7})
3457
3458 self.assertEqual(dt2.__class__, C)
3459 self.assertEqual(dt2.theAnswer, 42)
3460 self.assertEqual(dt2.extra, 7)
3461 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3462 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
3463
3464# Pain to set up DST-aware tzinfo classes.
3465
3466def first_sunday_on_or_after(dt):
3467 days_to_go = 6 - dt.weekday()
3468 if days_to_go:
3469 dt += timedelta(days_to_go)
3470 return dt
3471
3472ZERO = timedelta(0)
3473MINUTE = timedelta(minutes=1)
3474HOUR = timedelta(hours=1)
3475DAY = timedelta(days=1)
3476# In the US, DST starts at 2am (standard time) on the first Sunday in April.
3477DSTSTART = datetime(1, 4, 1, 2)
3478# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
3479# which is the first Sunday on or after Oct 25. Because we view 1:MM as
3480# being standard time on that day, there is no spelling in local time of
3481# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
3482DSTEND = datetime(1, 10, 25, 1)
3483
3484class USTimeZone(tzinfo):
3485
3486 def __init__(self, hours, reprname, stdname, dstname):
3487 self.stdoffset = timedelta(hours=hours)
3488 self.reprname = reprname
3489 self.stdname = stdname
3490 self.dstname = dstname
3491
3492 def __repr__(self):
3493 return self.reprname
3494
3495 def tzname(self, dt):
3496 if self.dst(dt):
3497 return self.dstname
3498 else:
3499 return self.stdname
3500
3501 def utcoffset(self, dt):
3502 return self.stdoffset + self.dst(dt)
3503
3504 def dst(self, dt):
3505 if dt is None or dt.tzinfo is None:
3506 # An exception instead may be sensible here, in one or more of
3507 # the cases.
3508 return ZERO
3509 assert dt.tzinfo is self
3510
3511 # Find first Sunday in April.
3512 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
3513 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
3514
3515 # Find last Sunday in October.
3516 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
3517 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
3518
3519 # Can't compare naive to aware objects, so strip the timezone from
3520 # dt first.
3521 if start <= dt.replace(tzinfo=None) < end:
3522 return HOUR
3523 else:
3524 return ZERO
3525
3526Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
3527Central = USTimeZone(-6, "Central", "CST", "CDT")
3528Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
3529Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
3530utc_real = FixedOffset(0, "UTC", 0)
3531# For better test coverage, we want another flavor of UTC that's west of
3532# the Eastern and Pacific timezones.
3533utc_fake = FixedOffset(-12*60, "UTCfake", 0)
3534
3535class TestTimezoneConversions(unittest.TestCase):
3536 # The DST switch times for 2002, in std time.
3537 dston = datetime(2002, 4, 7, 2)
3538 dstoff = datetime(2002, 10, 27, 1)
3539
3540 theclass = datetime
3541
3542 # Check a time that's inside DST.
3543 def checkinside(self, dt, tz, utc, dston, dstoff):
3544 self.assertEqual(dt.dst(), HOUR)
3545
3546 # Conversion to our own timezone is always an identity.
3547 self.assertEqual(dt.astimezone(tz), dt)
3548
3549 asutc = dt.astimezone(utc)
3550 there_and_back = asutc.astimezone(tz)
3551
3552 # Conversion to UTC and back isn't always an identity here,
3553 # because there are redundant spellings (in local time) of
3554 # UTC time when DST begins: the clock jumps from 1:59:59
3555 # to 3:00:00, and a local time of 2:MM:SS doesn't really
3556 # make sense then. The classes above treat 2:MM:SS as
3557 # daylight time then (it's "after 2am"), really an alias
3558 # for 1:MM:SS standard time. The latter form is what
3559 # conversion back from UTC produces.
3560 if dt.date() == dston.date() and dt.hour == 2:
3561 # We're in the redundant hour, and coming back from
3562 # UTC gives the 1:MM:SS standard-time spelling.
3563 self.assertEqual(there_and_back + HOUR, dt)
3564 # Although during was considered to be in daylight
3565 # time, there_and_back is not.
3566 self.assertEqual(there_and_back.dst(), ZERO)
3567 # They're the same times in UTC.
3568 self.assertEqual(there_and_back.astimezone(utc),
3569 dt.astimezone(utc))
3570 else:
3571 # We're not in the redundant hour.
3572 self.assertEqual(dt, there_and_back)
3573
3574 # Because we have a redundant spelling when DST begins, there is
Ezio Melotti3b3499b2011-03-16 11:35:38 +02003575 # (unfortunately) an hour when DST ends that can't be spelled at all in
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003576 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
3577 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
3578 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
3579 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
3580 # expressed in local time. Nevertheless, we want conversion back
3581 # from UTC to mimic the local clock's "repeat an hour" behavior.
3582 nexthour_utc = asutc + HOUR
3583 nexthour_tz = nexthour_utc.astimezone(tz)
3584 if dt.date() == dstoff.date() and dt.hour == 0:
3585 # We're in the hour before the last DST hour. The last DST hour
3586 # is ineffable. We want the conversion back to repeat 1:MM.
3587 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3588 nexthour_utc += HOUR
3589 nexthour_tz = nexthour_utc.astimezone(tz)
3590 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3591 else:
3592 self.assertEqual(nexthour_tz - dt, HOUR)
3593
3594 # Check a time that's outside DST.
3595 def checkoutside(self, dt, tz, utc):
3596 self.assertEqual(dt.dst(), ZERO)
3597
3598 # Conversion to our own timezone is always an identity.
3599 self.assertEqual(dt.astimezone(tz), dt)
3600
3601 # Converting to UTC and back is an identity too.
3602 asutc = dt.astimezone(utc)
3603 there_and_back = asutc.astimezone(tz)
3604 self.assertEqual(dt, there_and_back)
3605
3606 def convert_between_tz_and_utc(self, tz, utc):
3607 dston = self.dston.replace(tzinfo=tz)
3608 # Because 1:MM on the day DST ends is taken as being standard time,
3609 # there is no spelling in tz for the last hour of daylight time.
3610 # For purposes of the test, the last hour of DST is 0:MM, which is
3611 # taken as being daylight time (and 1:MM is taken as being standard
3612 # time).
3613 dstoff = self.dstoff.replace(tzinfo=tz)
3614 for delta in (timedelta(weeks=13),
3615 DAY,
3616 HOUR,
3617 timedelta(minutes=1),
3618 timedelta(microseconds=1)):
3619
3620 self.checkinside(dston, tz, utc, dston, dstoff)
3621 for during in dston + delta, dstoff - delta:
3622 self.checkinside(during, tz, utc, dston, dstoff)
3623
3624 self.checkoutside(dstoff, tz, utc)
3625 for outside in dston - delta, dstoff + delta:
3626 self.checkoutside(outside, tz, utc)
3627
3628 def test_easy(self):
3629 # Despite the name of this test, the endcases are excruciating.
3630 self.convert_between_tz_and_utc(Eastern, utc_real)
3631 self.convert_between_tz_and_utc(Pacific, utc_real)
3632 self.convert_between_tz_and_utc(Eastern, utc_fake)
3633 self.convert_between_tz_and_utc(Pacific, utc_fake)
3634 # The next is really dancing near the edge. It works because
3635 # Pacific and Eastern are far enough apart that their "problem
3636 # hours" don't overlap.
3637 self.convert_between_tz_and_utc(Eastern, Pacific)
3638 self.convert_between_tz_and_utc(Pacific, Eastern)
3639 # OTOH, these fail! Don't enable them. The difficulty is that
3640 # the edge case tests assume that every hour is representable in
3641 # the "utc" class. This is always true for a fixed-offset tzinfo
3642 # class (lke utc_real and utc_fake), but not for Eastern or Central.
3643 # For these adjacent DST-aware time zones, the range of time offsets
3644 # tested ends up creating hours in the one that aren't representable
3645 # in the other. For the same reason, we would see failures in the
3646 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
3647 # offset deltas in convert_between_tz_and_utc().
3648 #
3649 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
3650 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
3651
3652 def test_tricky(self):
3653 # 22:00 on day before daylight starts.
3654 fourback = self.dston - timedelta(hours=4)
3655 ninewest = FixedOffset(-9*60, "-0900", 0)
3656 fourback = fourback.replace(tzinfo=ninewest)
3657 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
3658 # 2", we should get the 3 spelling.
3659 # If we plug 22:00 the day before into Eastern, it "looks like std
3660 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
3661 # to 22:00 lands on 2:00, which makes no sense in local time (the
3662 # local clock jumps from 1 to 3). The point here is to make sure we
3663 # get the 3 spelling.
3664 expected = self.dston.replace(hour=3)
3665 got = fourback.astimezone(Eastern).replace(tzinfo=None)
3666 self.assertEqual(expected, got)
3667
3668 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
3669 # case we want the 1:00 spelling.
3670 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
3671 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
3672 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
3673 # spelling.
3674 expected = self.dston.replace(hour=1)
3675 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
3676 self.assertEqual(expected, got)
3677
3678 # Now on the day DST ends, we want "repeat an hour" behavior.
3679 # UTC 4:MM 5:MM 6:MM 7:MM checking these
3680 # EST 23:MM 0:MM 1:MM 2:MM
3681 # EDT 0:MM 1:MM 2:MM 3:MM
3682 # wall 0:MM 1:MM 1:MM 2:MM against these
3683 for utc in utc_real, utc_fake:
3684 for tz in Eastern, Pacific:
3685 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
3686 # Convert that to UTC.
3687 first_std_hour -= tz.utcoffset(None)
3688 # Adjust for possibly fake UTC.
3689 asutc = first_std_hour + utc.utcoffset(None)
3690 # First UTC hour to convert; this is 4:00 when utc=utc_real &
3691 # tz=Eastern.
3692 asutcbase = asutc.replace(tzinfo=utc)
3693 for tzhour in (0, 1, 1, 2):
3694 expectedbase = self.dstoff.replace(hour=tzhour)
3695 for minute in 0, 30, 59:
3696 expected = expectedbase.replace(minute=minute)
3697 asutc = asutcbase.replace(minute=minute)
3698 astz = asutc.astimezone(tz)
3699 self.assertEqual(astz.replace(tzinfo=None), expected)
3700 asutcbase += HOUR
3701
3702
3703 def test_bogus_dst(self):
3704 class ok(tzinfo):
3705 def utcoffset(self, dt): return HOUR
3706 def dst(self, dt): return HOUR
3707
3708 now = self.theclass.now().replace(tzinfo=utc_real)
3709 # Doesn't blow up.
3710 now.astimezone(ok())
3711
3712 # Does blow up.
3713 class notok(ok):
3714 def dst(self, dt): return None
3715 self.assertRaises(ValueError, now.astimezone, notok())
3716
3717 # Sometimes blow up. In the following, tzinfo.dst()
3718 # implementation may return None or not None depending on
3719 # whether DST is assumed to be in effect. In this situation,
3720 # a ValueError should be raised by astimezone().
3721 class tricky_notok(ok):
3722 def dst(self, dt):
3723 if dt.year == 2000:
3724 return None
3725 else:
3726 return 10*HOUR
3727 dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
3728 self.assertRaises(ValueError, dt.astimezone, tricky_notok())
3729
3730 def test_fromutc(self):
3731 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
3732 now = datetime.utcnow().replace(tzinfo=utc_real)
3733 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3734 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
3735 enow = Eastern.fromutc(now) # doesn't blow up
3736 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
3737 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
3738 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
3739
3740 # Always converts UTC to standard time.
3741 class FauxUSTimeZone(USTimeZone):
3742 def fromutc(self, dt):
3743 return dt + self.stdoffset
3744 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
3745
3746 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
3747 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
3748 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
3749
3750 # Check around DST start.
3751 start = self.dston.replace(hour=4, tzinfo=Eastern)
3752 fstart = start.replace(tzinfo=FEastern)
3753 for wall in 23, 0, 1, 3, 4, 5:
3754 expected = start.replace(hour=wall)
3755 if wall == 23:
3756 expected -= timedelta(days=1)
3757 got = Eastern.fromutc(start)
3758 self.assertEqual(expected, got)
3759
3760 expected = fstart + FEastern.stdoffset
3761 got = FEastern.fromutc(fstart)
3762 self.assertEqual(expected, got)
3763
3764 # Ensure astimezone() calls fromutc() too.
3765 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3766 self.assertEqual(expected, got)
3767
3768 start += HOUR
3769 fstart += HOUR
3770
3771 # Check around DST end.
3772 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
3773 fstart = start.replace(tzinfo=FEastern)
3774 for wall in 0, 1, 1, 2, 3, 4:
3775 expected = start.replace(hour=wall)
3776 got = Eastern.fromutc(start)
3777 self.assertEqual(expected, got)
3778
3779 expected = fstart + FEastern.stdoffset
3780 got = FEastern.fromutc(fstart)
3781 self.assertEqual(expected, got)
3782
3783 # Ensure astimezone() calls fromutc() too.
3784 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3785 self.assertEqual(expected, got)
3786
3787 start += HOUR
3788 fstart += HOUR
3789
3790
3791#############################################################################
3792# oddballs
3793
3794class Oddballs(unittest.TestCase):
3795
3796 def test_bug_1028306(self):
3797 # Trying to compare a date to a datetime should act like a mixed-
3798 # type comparison, despite that datetime is a subclass of date.
3799 as_date = date.today()
3800 as_datetime = datetime.combine(as_date, time())
3801 self.assertTrue(as_date != as_datetime)
3802 self.assertTrue(as_datetime != as_date)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003803 self.assertFalse(as_date == as_datetime)
3804 self.assertFalse(as_datetime == as_date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003805 self.assertRaises(TypeError, lambda: as_date < as_datetime)
3806 self.assertRaises(TypeError, lambda: as_datetime < as_date)
3807 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
3808 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
3809 self.assertRaises(TypeError, lambda: as_date > as_datetime)
3810 self.assertRaises(TypeError, lambda: as_datetime > as_date)
3811 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
3812 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
3813
3814 # Neverthelss, comparison should work with the base-class (date)
3815 # projection if use of a date method is forced.
3816 self.assertEqual(as_date.__eq__(as_datetime), True)
3817 different_day = (as_date.day + 1) % 20 + 1
3818 as_different = as_datetime.replace(day= different_day)
3819 self.assertEqual(as_date.__eq__(as_different), False)
3820
3821 # And date should compare with other subclasses of date. If a
3822 # subclass wants to stop this, it's up to the subclass to do so.
3823 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
3824 self.assertEqual(as_date, date_sc)
3825 self.assertEqual(date_sc, as_date)
3826
3827 # Ditto for datetimes.
3828 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
3829 as_date.day, 0, 0, 0)
3830 self.assertEqual(as_datetime, datetime_sc)
3831 self.assertEqual(datetime_sc, as_datetime)
3832
3833def test_main():
3834 support.run_unittest(__name__)
3835
3836if __name__ == "__main__":
3837 test_main()