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