blob: d0886c47baec173f39da25b13c4b56bf698a5f21 [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"""
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04005from test.support import is_resource_enabled
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04006
7import itertools
8import bisect
Alexander Belopolskycf86e362010-07-23 19:25:47 +00009
Serhiy Storchakae28209f2015-11-16 11:12:58 +020010import copy
Antoine Pitrou392f4132014-10-03 11:25:30 +020011import decimal
Alexander Belopolskycf86e362010-07-23 19:25:47 +000012import sys
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -040013import os
Alexander Belopolskycf86e362010-07-23 19:25:47 +000014import pickle
Raymond Hettinger5a2146a2014-07-25 14:59:48 -070015import random
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -040016import struct
Alexander Belopolskycf86e362010-07-23 19:25:47 +000017import unittest
18
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -040019from array import array
20
Alexander Belopolskycf86e362010-07-23 19:25:47 +000021from operator import lt, le, gt, ge, eq, ne, truediv, floordiv, mod
22
23from test import support
24
25import datetime as datetime_module
26from datetime import MINYEAR, MAXYEAR
27from datetime import timedelta
28from datetime import tzinfo
29from datetime import time
30from datetime import timezone
31from datetime import date, datetime
32import time as _time
33
34# Needed by test_datetime
35import _strptime
36#
37
38
39pickle_choices = [(pickle, pickle, proto)
40 for proto in range(pickle.HIGHEST_PROTOCOL + 1)]
41assert len(pickle_choices) == pickle.HIGHEST_PROTOCOL + 1
42
43# An arbitrary collection of objects of non-datetime types, for testing
44# mixed-type comparisons.
45OTHERSTUFF = (10, 34.5, "abc", {}, [], ())
46
47
48# XXX Copied from test_float.
49INF = float("inf")
50NAN = float("nan")
51
Alexander Belopolskycf86e362010-07-23 19:25:47 +000052
53#############################################################################
54# module tests
55
56class TestModule(unittest.TestCase):
57
58 def test_constants(self):
59 datetime = datetime_module
60 self.assertEqual(datetime.MINYEAR, 1)
61 self.assertEqual(datetime.MAXYEAR, 9999)
62
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -040063 def test_name_cleanup(self):
Utkarsh Upadhyay287c5592017-07-21 02:14:54 +020064 if '_Pure' in self.__class__.__name__:
65 self.skipTest('Only run for Fast C implementation')
66
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -040067 datetime = datetime_module
68 names = set(name for name in dir(datetime)
69 if not name.startswith('__') and not name.endswith('__'))
70 allowed = set(['MAXYEAR', 'MINYEAR', 'date', 'datetime',
71 'datetime_CAPI', 'time', 'timedelta', 'timezone',
72 'tzinfo'])
73 self.assertEqual(names - allowed, set([]))
74
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -050075 def test_divide_and_round(self):
Utkarsh Upadhyay287c5592017-07-21 02:14:54 +020076 if '_Fast' in self.__class__.__name__:
77 self.skipTest('Only run for Pure Python implementation')
78
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -050079 dar = datetime_module._divide_and_round
80
81 self.assertEqual(dar(-10, -3), 3)
82 self.assertEqual(dar(5, -2), -2)
83
84 # four cases: (2 signs of a) x (2 signs of b)
85 self.assertEqual(dar(7, 3), 2)
86 self.assertEqual(dar(-7, 3), -2)
87 self.assertEqual(dar(7, -3), -2)
88 self.assertEqual(dar(-7, -3), 2)
89
90 # ties to even - eight cases:
91 # (2 signs of a) x (2 signs of b) x (even / odd quotient)
92 self.assertEqual(dar(10, 4), 2)
93 self.assertEqual(dar(-10, 4), -2)
94 self.assertEqual(dar(10, -4), -2)
95 self.assertEqual(dar(-10, -4), 2)
96
97 self.assertEqual(dar(6, 4), 2)
98 self.assertEqual(dar(-6, 4), -2)
99 self.assertEqual(dar(6, -4), -2)
100 self.assertEqual(dar(-6, -4), 2)
101
102
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000103#############################################################################
104# tzinfo tests
105
106class FixedOffset(tzinfo):
107
108 def __init__(self, offset, name, dstoffset=42):
109 if isinstance(offset, int):
110 offset = timedelta(minutes=offset)
111 if isinstance(dstoffset, int):
112 dstoffset = timedelta(minutes=dstoffset)
113 self.__offset = offset
114 self.__name = name
115 self.__dstoffset = dstoffset
116 def __repr__(self):
117 return self.__name.lower()
118 def utcoffset(self, dt):
119 return self.__offset
120 def tzname(self, dt):
121 return self.__name
122 def dst(self, dt):
123 return self.__dstoffset
124
125class PicklableFixedOffset(FixedOffset):
126
127 def __init__(self, offset=None, name=None, dstoffset=None):
128 FixedOffset.__init__(self, offset, name, dstoffset)
129
Berker Peksage3385b42016-03-19 13:16:32 +0200130 def __getstate__(self):
131 return self.__dict__
132
Raymond Hettinger5a2146a2014-07-25 14:59:48 -0700133class _TZInfo(tzinfo):
134 def utcoffset(self, datetime_module):
135 return random.random()
136
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000137class TestTZInfo(unittest.TestCase):
138
Raymond Hettinger5a2146a2014-07-25 14:59:48 -0700139 def test_refcnt_crash_bug_22044(self):
140 tz1 = _TZInfo()
141 dt1 = datetime(2014, 7, 21, 11, 32, 3, 0, tz1)
142 with self.assertRaises(TypeError):
143 dt1.utcoffset()
144
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000145 def test_non_abstractness(self):
146 # In order to allow subclasses to get pickled, the C implementation
147 # wasn't able to get away with having __init__ raise
148 # NotImplementedError.
149 useless = tzinfo()
150 dt = datetime.max
151 self.assertRaises(NotImplementedError, useless.tzname, dt)
152 self.assertRaises(NotImplementedError, useless.utcoffset, dt)
153 self.assertRaises(NotImplementedError, useless.dst, dt)
154
155 def test_subclass_must_override(self):
156 class NotEnough(tzinfo):
157 def __init__(self, offset, name):
158 self.__offset = offset
159 self.__name = name
160 self.assertTrue(issubclass(NotEnough, tzinfo))
161 ne = NotEnough(3, "NotByALongShot")
162 self.assertIsInstance(ne, tzinfo)
163
164 dt = datetime.now()
165 self.assertRaises(NotImplementedError, ne.tzname, dt)
166 self.assertRaises(NotImplementedError, ne.utcoffset, dt)
167 self.assertRaises(NotImplementedError, ne.dst, dt)
168
169 def test_normal(self):
170 fo = FixedOffset(3, "Three")
171 self.assertIsInstance(fo, tzinfo)
172 for dt in datetime.now(), None:
173 self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3))
174 self.assertEqual(fo.tzname(dt), "Three")
175 self.assertEqual(fo.dst(dt), timedelta(minutes=42))
176
177 def test_pickling_base(self):
178 # There's no point to pickling tzinfo objects on their own (they
179 # carry no data), but they need to be picklable anyway else
180 # concrete subclasses can't be pickled.
181 orig = tzinfo.__new__(tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200182 self.assertIs(type(orig), tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000183 for pickler, unpickler, proto in pickle_choices:
184 green = pickler.dumps(orig, proto)
185 derived = unpickler.loads(green)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200186 self.assertIs(type(derived), tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000187
188 def test_pickling_subclass(self):
189 # Make sure we can pickle/unpickle an instance of a subclass.
190 offset = timedelta(minutes=-300)
191 for otype, args in [
192 (PicklableFixedOffset, (offset, 'cookie')),
193 (timezone, (offset,)),
194 (timezone, (offset, "EST"))]:
195 orig = otype(*args)
196 oname = orig.tzname(None)
197 self.assertIsInstance(orig, tzinfo)
198 self.assertIs(type(orig), otype)
199 self.assertEqual(orig.utcoffset(None), offset)
200 self.assertEqual(orig.tzname(None), oname)
201 for pickler, unpickler, proto in pickle_choices:
202 green = pickler.dumps(orig, proto)
203 derived = unpickler.loads(green)
204 self.assertIsInstance(derived, tzinfo)
205 self.assertIs(type(derived), otype)
206 self.assertEqual(derived.utcoffset(None), offset)
207 self.assertEqual(derived.tzname(None), oname)
208
Alexander Belopolskyc79447b2015-09-27 21:41:55 -0400209 def test_issue23600(self):
210 DSTDIFF = DSTOFFSET = timedelta(hours=1)
211
212 class UKSummerTime(tzinfo):
213 """Simple time zone which pretends to always be in summer time, since
214 that's what shows the failure.
215 """
216
217 def utcoffset(self, dt):
218 return DSTOFFSET
219
220 def dst(self, dt):
221 return DSTDIFF
222
223 def tzname(self, dt):
224 return 'UKSummerTime'
225
226 tz = UKSummerTime()
227 u = datetime(2014, 4, 26, 12, 1, tzinfo=tz)
228 t = tz.fromutc(u)
229 self.assertEqual(t - t.utcoffset(), u)
230
231
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000232class TestTimeZone(unittest.TestCase):
233
234 def setUp(self):
235 self.ACDT = timezone(timedelta(hours=9.5), 'ACDT')
236 self.EST = timezone(-timedelta(hours=5), 'EST')
237 self.DT = datetime(2010, 1, 1)
238
239 def test_str(self):
240 for tz in [self.ACDT, self.EST, timezone.utc,
241 timezone.min, timezone.max]:
242 self.assertEqual(str(tz), tz.tzname(None))
243
244 def test_repr(self):
245 datetime = datetime_module
246 for tz in [self.ACDT, self.EST, timezone.utc,
247 timezone.min, timezone.max]:
248 # test round-trip
249 tzrep = repr(tz)
250 self.assertEqual(tz, eval(tzrep))
251
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000252 def test_class_members(self):
253 limit = timedelta(hours=23, minutes=59)
254 self.assertEqual(timezone.utc.utcoffset(None), ZERO)
255 self.assertEqual(timezone.min.utcoffset(None), -limit)
256 self.assertEqual(timezone.max.utcoffset(None), limit)
257
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000258 def test_constructor(self):
Alexander Belopolsky1bcbaab2010-10-14 17:03:51 +0000259 self.assertIs(timezone.utc, timezone(timedelta(0)))
260 self.assertIsNot(timezone.utc, timezone(timedelta(0), 'UTC'))
261 self.assertEqual(timezone.utc, timezone(timedelta(0), 'UTC'))
Alexander Belopolsky018d3532017-07-31 10:26:50 -0400262 for subminute in [timedelta(microseconds=1), timedelta(seconds=1)]:
263 tz = timezone(subminute)
264 self.assertNotEqual(tz.utcoffset(None) % timedelta(minutes=1), 0)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000265 # invalid offsets
Alexander Belopolsky018d3532017-07-31 10:26:50 -0400266 for invalid in [timedelta(1, 1), timedelta(1)]:
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000267 self.assertRaises(ValueError, timezone, invalid)
268 self.assertRaises(ValueError, timezone, -invalid)
269
270 with self.assertRaises(TypeError): timezone(None)
271 with self.assertRaises(TypeError): timezone(42)
272 with self.assertRaises(TypeError): timezone(ZERO, None)
273 with self.assertRaises(TypeError): timezone(ZERO, 42)
274 with self.assertRaises(TypeError): timezone(ZERO, 'ABC', 'extra')
275
276 def test_inheritance(self):
277 self.assertIsInstance(timezone.utc, tzinfo)
278 self.assertIsInstance(self.EST, tzinfo)
279
280 def test_utcoffset(self):
281 dummy = self.DT
282 for h in [0, 1.5, 12]:
283 offset = h * HOUR
284 self.assertEqual(offset, timezone(offset).utcoffset(dummy))
285 self.assertEqual(-offset, timezone(-offset).utcoffset(dummy))
286
287 with self.assertRaises(TypeError): self.EST.utcoffset('')
288 with self.assertRaises(TypeError): self.EST.utcoffset(5)
289
290
291 def test_dst(self):
Raymond Hettinger7beae8a2011-01-06 05:34:17 +0000292 self.assertIsNone(timezone.utc.dst(self.DT))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000293
294 with self.assertRaises(TypeError): self.EST.dst('')
295 with self.assertRaises(TypeError): self.EST.dst(5)
296
297 def test_tzname(self):
Alexander Belopolsky7827a5b2015-09-06 13:07:21 -0400298 self.assertEqual('UTC', timezone.utc.tzname(None))
299 self.assertEqual('UTC', timezone(ZERO).tzname(None))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000300 self.assertEqual('UTC-05:00', timezone(-5 * HOUR).tzname(None))
301 self.assertEqual('UTC+09:30', timezone(9.5 * HOUR).tzname(None))
302 self.assertEqual('UTC-00:01', timezone(timedelta(minutes=-1)).tzname(None))
303 self.assertEqual('XYZ', timezone(-5 * HOUR, 'XYZ').tzname(None))
304
Alexander Belopolsky018d3532017-07-31 10:26:50 -0400305 # Sub-minute offsets:
306 self.assertEqual('UTC+01:06:40', timezone(timedelta(0, 4000)).tzname(None))
307 self.assertEqual('UTC-01:06:40',
308 timezone(-timedelta(0, 4000)).tzname(None))
309 self.assertEqual('UTC+01:06:40.000001',
310 timezone(timedelta(0, 4000, 1)).tzname(None))
311 self.assertEqual('UTC-01:06:40.000001',
312 timezone(-timedelta(0, 4000, 1)).tzname(None))
313
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000314 with self.assertRaises(TypeError): self.EST.tzname('')
315 with self.assertRaises(TypeError): self.EST.tzname(5)
316
317 def test_fromutc(self):
318 with self.assertRaises(ValueError):
319 timezone.utc.fromutc(self.DT)
320 with self.assertRaises(TypeError):
321 timezone.utc.fromutc('not datetime')
322 for tz in [self.EST, self.ACDT, Eastern]:
323 utctime = self.DT.replace(tzinfo=tz)
324 local = tz.fromutc(utctime)
325 self.assertEqual(local - utctime, tz.utcoffset(local))
326 self.assertEqual(local,
327 self.DT.replace(tzinfo=timezone.utc))
328
329 def test_comparison(self):
330 self.assertNotEqual(timezone(ZERO), timezone(HOUR))
331 self.assertEqual(timezone(HOUR), timezone(HOUR))
332 self.assertEqual(timezone(-5 * HOUR), timezone(-5 * HOUR, 'EST'))
333 with self.assertRaises(TypeError): timezone(ZERO) < timezone(ZERO)
334 self.assertIn(timezone(ZERO), {timezone(ZERO)})
Georg Brandl0085a242012-09-22 09:23:12 +0200335 self.assertTrue(timezone(ZERO) != None)
336 self.assertFalse(timezone(ZERO) == None)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000337
338 def test_aware_datetime(self):
339 # test that timezone instances can be used by datetime
340 t = datetime(1, 1, 1)
341 for tz in [timezone.min, timezone.max, timezone.utc]:
342 self.assertEqual(tz.tzname(t),
343 t.replace(tzinfo=tz).tzname())
344 self.assertEqual(tz.utcoffset(t),
345 t.replace(tzinfo=tz).utcoffset())
346 self.assertEqual(tz.dst(t),
347 t.replace(tzinfo=tz).dst())
348
Serhiy Storchakae28209f2015-11-16 11:12:58 +0200349 def test_pickle(self):
350 for tz in self.ACDT, self.EST, timezone.min, timezone.max:
351 for pickler, unpickler, proto in pickle_choices:
352 tz_copy = unpickler.loads(pickler.dumps(tz, proto))
353 self.assertEqual(tz_copy, tz)
354 tz = timezone.utc
355 for pickler, unpickler, proto in pickle_choices:
356 tz_copy = unpickler.loads(pickler.dumps(tz, proto))
357 self.assertIs(tz_copy, tz)
358
359 def test_copy(self):
360 for tz in self.ACDT, self.EST, timezone.min, timezone.max:
361 tz_copy = copy.copy(tz)
362 self.assertEqual(tz_copy, tz)
363 tz = timezone.utc
364 tz_copy = copy.copy(tz)
365 self.assertIs(tz_copy, tz)
366
367 def test_deepcopy(self):
368 for tz in self.ACDT, self.EST, timezone.min, timezone.max:
369 tz_copy = copy.deepcopy(tz)
370 self.assertEqual(tz_copy, tz)
371 tz = timezone.utc
372 tz_copy = copy.deepcopy(tz)
373 self.assertIs(tz_copy, tz)
374
375
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000376#############################################################################
Ezio Melotti85a86292013-08-17 16:57:41 +0300377# Base class for testing a particular aspect of timedelta, time, date and
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000378# datetime comparisons.
379
380class HarmlessMixedComparison:
381 # Test that __eq__ and __ne__ don't complain for mixed-type comparisons.
382
383 # Subclasses must define 'theclass', and theclass(1, 1, 1) must be a
384 # legit constructor.
385
386 def test_harmless_mixed_comparison(self):
387 me = self.theclass(1, 1, 1)
388
389 self.assertFalse(me == ())
390 self.assertTrue(me != ())
391 self.assertFalse(() == me)
392 self.assertTrue(() != me)
393
394 self.assertIn(me, [1, 20, [], me])
395 self.assertIn([], [me, 1, 20, []])
396
397 def test_harmful_mixed_comparison(self):
398 me = self.theclass(1, 1, 1)
399
400 self.assertRaises(TypeError, lambda: me < ())
401 self.assertRaises(TypeError, lambda: me <= ())
402 self.assertRaises(TypeError, lambda: me > ())
403 self.assertRaises(TypeError, lambda: me >= ())
404
405 self.assertRaises(TypeError, lambda: () < me)
406 self.assertRaises(TypeError, lambda: () <= me)
407 self.assertRaises(TypeError, lambda: () > me)
408 self.assertRaises(TypeError, lambda: () >= me)
409
410#############################################################################
411# timedelta tests
412
413class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
414
415 theclass = timedelta
416
417 def test_constructor(self):
418 eq = self.assertEqual
419 td = timedelta
420
421 # Check keyword args to constructor
422 eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0,
423 milliseconds=0, microseconds=0))
424 eq(td(1), td(days=1))
425 eq(td(0, 1), td(seconds=1))
426 eq(td(0, 0, 1), td(microseconds=1))
427 eq(td(weeks=1), td(days=7))
428 eq(td(days=1), td(hours=24))
429 eq(td(hours=1), td(minutes=60))
430 eq(td(minutes=1), td(seconds=60))
431 eq(td(seconds=1), td(milliseconds=1000))
432 eq(td(milliseconds=1), td(microseconds=1000))
433
434 # Check float args to constructor
435 eq(td(weeks=1.0/7), td(days=1))
436 eq(td(days=1.0/24), td(hours=1))
437 eq(td(hours=1.0/60), td(minutes=1))
438 eq(td(minutes=1.0/60), td(seconds=1))
439 eq(td(seconds=0.001), td(milliseconds=1))
440 eq(td(milliseconds=0.001), td(microseconds=1))
441
442 def test_computations(self):
443 eq = self.assertEqual
444 td = timedelta
445
446 a = td(7) # One week
447 b = td(0, 60) # One minute
448 c = td(0, 0, 1000) # One millisecond
449 eq(a+b+c, td(7, 60, 1000))
450 eq(a-b, td(6, 24*3600 - 60))
451 eq(b.__rsub__(a), td(6, 24*3600 - 60))
452 eq(-a, td(-7))
453 eq(+a, td(7))
454 eq(-b, td(-1, 24*3600 - 60))
455 eq(-c, td(-1, 24*3600 - 1, 999000))
456 eq(abs(a), a)
457 eq(abs(-a), a)
458 eq(td(6, 24*3600), a)
459 eq(td(0, 0, 60*1000000), b)
460 eq(a*10, td(70))
461 eq(a*10, 10*a)
462 eq(a*10, 10*a)
463 eq(b*10, td(0, 600))
464 eq(10*b, td(0, 600))
465 eq(b*10, td(0, 600))
466 eq(c*10, td(0, 0, 10000))
467 eq(10*c, td(0, 0, 10000))
468 eq(c*10, td(0, 0, 10000))
469 eq(a*-1, -a)
470 eq(b*-2, -b-b)
471 eq(c*-2, -c+-c)
472 eq(b*(60*24), (b*60)*24)
473 eq(b*(60*24), (60*b)*24)
474 eq(c*1000, td(0, 1))
475 eq(1000*c, td(0, 1))
476 eq(a//7, td(1))
477 eq(b//10, td(0, 6))
478 eq(c//1000, td(0, 0, 1))
479 eq(a//10, td(0, 7*24*360))
480 eq(a//3600000, td(0, 0, 7*24*1000))
481 eq(a/0.5, td(14))
482 eq(b/0.5, td(0, 120))
483 eq(a/7, td(1))
484 eq(b/10, td(0, 6))
485 eq(c/1000, td(0, 0, 1))
486 eq(a/10, td(0, 7*24*360))
487 eq(a/3600000, td(0, 0, 7*24*1000))
488
489 # Multiplication by float
490 us = td(microseconds=1)
491 eq((3*us) * 0.5, 2*us)
492 eq((5*us) * 0.5, 2*us)
493 eq(0.5 * (3*us), 2*us)
494 eq(0.5 * (5*us), 2*us)
495 eq((-3*us) * 0.5, -2*us)
496 eq((-5*us) * 0.5, -2*us)
497
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -0500498 # Issue #23521
499 eq(td(seconds=1) * 0.123456, td(microseconds=123456))
500 eq(td(seconds=1) * 0.6112295, td(microseconds=611229))
501
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000502 # Division by int and float
503 eq((3*us) / 2, 2*us)
504 eq((5*us) / 2, 2*us)
505 eq((-3*us) / 2.0, -2*us)
506 eq((-5*us) / 2.0, -2*us)
507 eq((3*us) / -2, -2*us)
508 eq((5*us) / -2, -2*us)
509 eq((3*us) / -2.0, -2*us)
510 eq((5*us) / -2.0, -2*us)
511 for i in range(-10, 10):
512 eq((i*us/3)//us, round(i/3))
513 for i in range(-10, 10):
514 eq((i*us/-3)//us, round(i/-3))
515
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -0500516 # Issue #23521
517 eq(td(seconds=1) / (1 / 0.6112295), td(microseconds=611229))
518
Alexander Belopolskyb6f5ec72011-04-05 20:07:38 -0400519 # Issue #11576
520 eq(td(999999999, 86399, 999999) - td(999999999, 86399, 999998),
521 td(0, 0, 1))
522 eq(td(999999999, 1, 1) - td(999999999, 1, 0),
523 td(0, 0, 1))
524
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000525 def test_disallowed_computations(self):
526 a = timedelta(42)
527
528 # Add/sub ints or floats should be illegal
529 for i in 1, 1.0:
530 self.assertRaises(TypeError, lambda: a+i)
531 self.assertRaises(TypeError, lambda: a-i)
532 self.assertRaises(TypeError, lambda: i+a)
533 self.assertRaises(TypeError, lambda: i-a)
534
535 # Division of int by timedelta doesn't make sense.
536 # Division by zero doesn't make sense.
537 zero = 0
538 self.assertRaises(TypeError, lambda: zero // a)
539 self.assertRaises(ZeroDivisionError, lambda: a // zero)
540 self.assertRaises(ZeroDivisionError, lambda: a / zero)
541 self.assertRaises(ZeroDivisionError, lambda: a / 0.0)
542 self.assertRaises(TypeError, lambda: a / '')
543
Eric Smith3ab08ca2010-12-04 15:17:38 +0000544 @support.requires_IEEE_754
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000545 def test_disallowed_special(self):
546 a = timedelta(42)
547 self.assertRaises(ValueError, a.__mul__, NAN)
548 self.assertRaises(ValueError, a.__truediv__, NAN)
549
550 def test_basic_attributes(self):
551 days, seconds, us = 1, 7, 31
552 td = timedelta(days, seconds, us)
553 self.assertEqual(td.days, days)
554 self.assertEqual(td.seconds, seconds)
555 self.assertEqual(td.microseconds, us)
556
557 def test_total_seconds(self):
558 td = timedelta(days=365)
559 self.assertEqual(td.total_seconds(), 31536000.0)
560 for total_seconds in [123456.789012, -123456.789012, 0.123456, 0, 1e6]:
561 td = timedelta(seconds=total_seconds)
562 self.assertEqual(td.total_seconds(), total_seconds)
563 # Issue8644: Test that td.total_seconds() has the same
564 # accuracy as td / timedelta(seconds=1).
565 for ms in [-1, -2, -123]:
566 td = timedelta(microseconds=ms)
567 self.assertEqual(td.total_seconds(), td / timedelta(seconds=1))
568
569 def test_carries(self):
570 t1 = timedelta(days=100,
571 weeks=-7,
572 hours=-24*(100-49),
573 minutes=-3,
574 seconds=12,
575 microseconds=(3*60 - 12) * 1e6 + 1)
576 t2 = timedelta(microseconds=1)
577 self.assertEqual(t1, t2)
578
579 def test_hash_equality(self):
580 t1 = timedelta(days=100,
581 weeks=-7,
582 hours=-24*(100-49),
583 minutes=-3,
584 seconds=12,
585 microseconds=(3*60 - 12) * 1000000)
586 t2 = timedelta()
587 self.assertEqual(hash(t1), hash(t2))
588
589 t1 += timedelta(weeks=7)
590 t2 += timedelta(days=7*7)
591 self.assertEqual(t1, t2)
592 self.assertEqual(hash(t1), hash(t2))
593
594 d = {t1: 1}
595 d[t2] = 2
596 self.assertEqual(len(d), 1)
597 self.assertEqual(d[t1], 2)
598
599 def test_pickling(self):
600 args = 12, 34, 56
601 orig = timedelta(*args)
602 for pickler, unpickler, proto in pickle_choices:
603 green = pickler.dumps(orig, proto)
604 derived = unpickler.loads(green)
605 self.assertEqual(orig, derived)
606
607 def test_compare(self):
608 t1 = timedelta(2, 3, 4)
609 t2 = timedelta(2, 3, 4)
610 self.assertEqual(t1, t2)
611 self.assertTrue(t1 <= t2)
612 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200613 self.assertFalse(t1 != t2)
614 self.assertFalse(t1 < t2)
615 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000616
617 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
618 t2 = timedelta(*args) # this is larger than t1
619 self.assertTrue(t1 < t2)
620 self.assertTrue(t2 > t1)
621 self.assertTrue(t1 <= t2)
622 self.assertTrue(t2 >= t1)
623 self.assertTrue(t1 != t2)
624 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200625 self.assertFalse(t1 == t2)
626 self.assertFalse(t2 == t1)
627 self.assertFalse(t1 > t2)
628 self.assertFalse(t2 < t1)
629 self.assertFalse(t1 >= t2)
630 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000631
632 for badarg in OTHERSTUFF:
633 self.assertEqual(t1 == badarg, False)
634 self.assertEqual(t1 != badarg, True)
635 self.assertEqual(badarg == t1, False)
636 self.assertEqual(badarg != t1, True)
637
638 self.assertRaises(TypeError, lambda: t1 <= badarg)
639 self.assertRaises(TypeError, lambda: t1 < badarg)
640 self.assertRaises(TypeError, lambda: t1 > badarg)
641 self.assertRaises(TypeError, lambda: t1 >= badarg)
642 self.assertRaises(TypeError, lambda: badarg <= t1)
643 self.assertRaises(TypeError, lambda: badarg < t1)
644 self.assertRaises(TypeError, lambda: badarg > t1)
645 self.assertRaises(TypeError, lambda: badarg >= t1)
646
647 def test_str(self):
648 td = timedelta
649 eq = self.assertEqual
650
651 eq(str(td(1)), "1 day, 0:00:00")
652 eq(str(td(-1)), "-1 day, 0:00:00")
653 eq(str(td(2)), "2 days, 0:00:00")
654 eq(str(td(-2)), "-2 days, 0:00:00")
655
656 eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
657 eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
658 eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
659 "-210 days, 23:12:34")
660
661 eq(str(td(milliseconds=1)), "0:00:00.001000")
662 eq(str(td(microseconds=3)), "0:00:00.000003")
663
664 eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
665 microseconds=999999)),
666 "999999999 days, 23:59:59.999999")
667
668 def test_repr(self):
669 name = 'datetime.' + self.theclass.__name__
670 self.assertEqual(repr(self.theclass(1)),
Utkarsh Upadhyaycc5a65c2017-07-25 23:51:33 +0200671 "%s(days=1)" % name)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000672 self.assertEqual(repr(self.theclass(10, 2)),
Utkarsh Upadhyaycc5a65c2017-07-25 23:51:33 +0200673 "%s(days=10, seconds=2)" % name)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000674 self.assertEqual(repr(self.theclass(-10, 2, 400000)),
Utkarsh Upadhyaycc5a65c2017-07-25 23:51:33 +0200675 "%s(days=-10, seconds=2, microseconds=400000)" % name)
676 self.assertEqual(repr(self.theclass(seconds=60)),
677 "%s(seconds=60)" % name)
678 self.assertEqual(repr(self.theclass()),
679 "%s(0)" % name)
680 self.assertEqual(repr(self.theclass(microseconds=100)),
681 "%s(microseconds=100)" % name)
682 self.assertEqual(repr(self.theclass(days=1, microseconds=100)),
683 "%s(days=1, microseconds=100)" % name)
684 self.assertEqual(repr(self.theclass(seconds=1, microseconds=100)),
685 "%s(seconds=1, microseconds=100)" % name)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000686
687 def test_roundtrip(self):
688 for td in (timedelta(days=999999999, hours=23, minutes=59,
689 seconds=59, microseconds=999999),
690 timedelta(days=-999999999),
691 timedelta(days=-999999999, seconds=1),
692 timedelta(days=1, seconds=2, microseconds=3)):
693
694 # Verify td -> string -> td identity.
695 s = repr(td)
696 self.assertTrue(s.startswith('datetime.'))
697 s = s[9:]
698 td2 = eval(s)
699 self.assertEqual(td, td2)
700
701 # Verify identity via reconstructing from pieces.
702 td2 = timedelta(td.days, td.seconds, td.microseconds)
703 self.assertEqual(td, td2)
704
705 def test_resolution_info(self):
706 self.assertIsInstance(timedelta.min, timedelta)
707 self.assertIsInstance(timedelta.max, timedelta)
708 self.assertIsInstance(timedelta.resolution, timedelta)
709 self.assertTrue(timedelta.max > timedelta.min)
710 self.assertEqual(timedelta.min, timedelta(-999999999))
711 self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
712 self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
713
714 def test_overflow(self):
715 tiny = timedelta.resolution
716
717 td = timedelta.min + tiny
718 td -= tiny # no problem
719 self.assertRaises(OverflowError, td.__sub__, tiny)
720 self.assertRaises(OverflowError, td.__add__, -tiny)
721
722 td = timedelta.max - tiny
723 td += tiny # no problem
724 self.assertRaises(OverflowError, td.__add__, tiny)
725 self.assertRaises(OverflowError, td.__sub__, -tiny)
726
727 self.assertRaises(OverflowError, lambda: -timedelta.max)
728
729 day = timedelta(1)
730 self.assertRaises(OverflowError, day.__mul__, 10**9)
731 self.assertRaises(OverflowError, day.__mul__, 1e9)
732 self.assertRaises(OverflowError, day.__truediv__, 1e-20)
733 self.assertRaises(OverflowError, day.__truediv__, 1e-10)
734 self.assertRaises(OverflowError, day.__truediv__, 9e-10)
735
Eric Smith3ab08ca2010-12-04 15:17:38 +0000736 @support.requires_IEEE_754
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000737 def _test_overflow_special(self):
738 day = timedelta(1)
739 self.assertRaises(OverflowError, day.__mul__, INF)
740 self.assertRaises(OverflowError, day.__mul__, -INF)
741
742 def test_microsecond_rounding(self):
743 td = timedelta
744 eq = self.assertEqual
745
746 # Single-field rounding.
747 eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
748 eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
Victor Stinner69cc4872015-09-08 23:58:54 +0200749 eq(td(milliseconds=0.5/1000), td(microseconds=0))
750 eq(td(milliseconds=-0.5/1000), td(microseconds=-0))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000751 eq(td(milliseconds=0.6/1000), td(microseconds=1))
752 eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
Victor Stinner69cc4872015-09-08 23:58:54 +0200753 eq(td(milliseconds=1.5/1000), td(microseconds=2))
754 eq(td(milliseconds=-1.5/1000), td(microseconds=-2))
755 eq(td(seconds=0.5/10**6), td(microseconds=0))
756 eq(td(seconds=-0.5/10**6), td(microseconds=-0))
757 eq(td(seconds=1/2**7), td(microseconds=7812))
758 eq(td(seconds=-1/2**7), td(microseconds=-7812))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000759
760 # Rounding due to contributions from more than one field.
761 us_per_hour = 3600e6
762 us_per_day = us_per_hour * 24
763 eq(td(days=.4/us_per_day), td(0))
764 eq(td(hours=.2/us_per_hour), td(0))
Victor Stinnercd5d7652015-09-09 01:09:21 +0200765 eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000766
767 eq(td(days=-.4/us_per_day), td(0))
768 eq(td(hours=-.2/us_per_hour), td(0))
769 eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
770
Victor Stinner69cc4872015-09-08 23:58:54 +0200771 # Test for a patch in Issue 8860
772 eq(td(microseconds=0.5), 0.5*td(microseconds=1.0))
773 eq(td(microseconds=0.5)//td.resolution, 0.5*td.resolution//td.resolution)
774
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000775 def test_massive_normalization(self):
776 td = timedelta(microseconds=-1)
777 self.assertEqual((td.days, td.seconds, td.microseconds),
778 (-1, 24*3600-1, 999999))
779
780 def test_bool(self):
781 self.assertTrue(timedelta(1))
782 self.assertTrue(timedelta(0, 1))
783 self.assertTrue(timedelta(0, 0, 1))
784 self.assertTrue(timedelta(microseconds=1))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200785 self.assertFalse(timedelta(0))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000786
787 def test_subclass_timedelta(self):
788
789 class T(timedelta):
790 @staticmethod
791 def from_td(td):
792 return T(td.days, td.seconds, td.microseconds)
793
794 def as_hours(self):
795 sum = (self.days * 24 +
796 self.seconds / 3600.0 +
797 self.microseconds / 3600e6)
798 return round(sum)
799
800 t1 = T(days=1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200801 self.assertIs(type(t1), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000802 self.assertEqual(t1.as_hours(), 24)
803
804 t2 = T(days=-1, seconds=-3600)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200805 self.assertIs(type(t2), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000806 self.assertEqual(t2.as_hours(), -25)
807
808 t3 = t1 + t2
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200809 self.assertIs(type(t3), timedelta)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000810 t4 = T.from_td(t3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200811 self.assertIs(type(t4), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000812 self.assertEqual(t3.days, t4.days)
813 self.assertEqual(t3.seconds, t4.seconds)
814 self.assertEqual(t3.microseconds, t4.microseconds)
815 self.assertEqual(str(t3), str(t4))
816 self.assertEqual(t4.as_hours(), -1)
817
818 def test_division(self):
819 t = timedelta(hours=1, minutes=24, seconds=19)
820 second = timedelta(seconds=1)
821 self.assertEqual(t / second, 5059.0)
822 self.assertEqual(t // second, 5059)
823
824 t = timedelta(minutes=2, seconds=30)
825 minute = timedelta(minutes=1)
826 self.assertEqual(t / minute, 2.5)
827 self.assertEqual(t // minute, 2)
828
829 zerotd = timedelta(0)
830 self.assertRaises(ZeroDivisionError, truediv, t, zerotd)
831 self.assertRaises(ZeroDivisionError, floordiv, t, zerotd)
832
833 # self.assertRaises(TypeError, truediv, t, 2)
834 # note: floor division of a timedelta by an integer *is*
835 # currently permitted.
836
837 def test_remainder(self):
838 t = timedelta(minutes=2, seconds=30)
839 minute = timedelta(minutes=1)
840 r = t % minute
841 self.assertEqual(r, timedelta(seconds=30))
842
843 t = timedelta(minutes=-2, seconds=30)
844 r = t % minute
845 self.assertEqual(r, timedelta(seconds=30))
846
847 zerotd = timedelta(0)
848 self.assertRaises(ZeroDivisionError, mod, t, zerotd)
849
850 self.assertRaises(TypeError, mod, t, 10)
851
852 def test_divmod(self):
853 t = timedelta(minutes=2, seconds=30)
854 minute = timedelta(minutes=1)
855 q, r = divmod(t, minute)
856 self.assertEqual(q, 2)
857 self.assertEqual(r, timedelta(seconds=30))
858
859 t = timedelta(minutes=-2, seconds=30)
860 q, r = divmod(t, minute)
861 self.assertEqual(q, -2)
862 self.assertEqual(r, timedelta(seconds=30))
863
864 zerotd = timedelta(0)
865 self.assertRaises(ZeroDivisionError, divmod, t, zerotd)
866
867 self.assertRaises(TypeError, divmod, t, 10)
868
Oren Milman865e4b42017-09-19 15:58:11 +0300869 def test_issue31293(self):
870 # The interpreter shouldn't crash in case a timedelta is divided or
871 # multiplied by a float with a bad as_integer_ratio() method.
872 def get_bad_float(bad_ratio):
873 class BadFloat(float):
874 def as_integer_ratio(self):
875 return bad_ratio
876 return BadFloat()
877
878 with self.assertRaises(TypeError):
879 timedelta() / get_bad_float(1 << 1000)
880 with self.assertRaises(TypeError):
881 timedelta() * get_bad_float(1 << 1000)
882
883 for bad_ratio in [(), (42, ), (1, 2, 3)]:
884 with self.assertRaises(ValueError):
885 timedelta() / get_bad_float(bad_ratio)
886 with self.assertRaises(ValueError):
887 timedelta() * get_bad_float(bad_ratio)
888
Serhiy Storchaka4ffd4652017-10-23 17:12:28 +0300889 def test_issue31752(self):
890 # The interpreter shouldn't crash because divmod() returns negative
891 # remainder.
892 class BadInt(int):
893 def __mul__(self, other):
894 return Prod()
895
896 class Prod:
897 def __radd__(self, other):
898 return Sum()
899
900 class Sum(int):
901 def __divmod__(self, other):
902 # negative remainder
903 return (0, -1)
904
905 timedelta(microseconds=BadInt(1))
906 timedelta(hours=BadInt(1))
907 timedelta(weeks=BadInt(1))
908
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000909
910#############################################################################
911# date tests
912
913class TestDateOnly(unittest.TestCase):
914 # Tests here won't pass if also run on datetime objects, so don't
915 # subclass this to test datetimes too.
916
917 def test_delta_non_days_ignored(self):
918 dt = date(2000, 1, 2)
919 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
920 microseconds=5)
921 days = timedelta(delta.days)
922 self.assertEqual(days, timedelta(1))
923
924 dt2 = dt + delta
925 self.assertEqual(dt2, dt + days)
926
927 dt2 = delta + dt
928 self.assertEqual(dt2, dt + days)
929
930 dt2 = dt - delta
931 self.assertEqual(dt2, dt - days)
932
933 delta = -delta
934 days = timedelta(delta.days)
935 self.assertEqual(days, timedelta(-2))
936
937 dt2 = dt + delta
938 self.assertEqual(dt2, dt + days)
939
940 dt2 = delta + dt
941 self.assertEqual(dt2, dt + days)
942
943 dt2 = dt - delta
944 self.assertEqual(dt2, dt - days)
945
946class SubclassDate(date):
947 sub_var = 1
948
949class TestDate(HarmlessMixedComparison, unittest.TestCase):
950 # Tests here should pass for both dates and datetimes, except for a
951 # few tests that TestDateTime overrides.
952
953 theclass = date
954
955 def test_basic_attributes(self):
956 dt = self.theclass(2002, 3, 1)
957 self.assertEqual(dt.year, 2002)
958 self.assertEqual(dt.month, 3)
959 self.assertEqual(dt.day, 1)
960
961 def test_roundtrip(self):
962 for dt in (self.theclass(1, 2, 3),
963 self.theclass.today()):
964 # Verify dt -> string -> date identity.
965 s = repr(dt)
966 self.assertTrue(s.startswith('datetime.'))
967 s = s[9:]
968 dt2 = eval(s)
969 self.assertEqual(dt, dt2)
970
971 # Verify identity via reconstructing from pieces.
972 dt2 = self.theclass(dt.year, dt.month, dt.day)
973 self.assertEqual(dt, dt2)
974
975 def test_ordinal_conversions(self):
976 # Check some fixed values.
977 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
978 (1, 12, 31, 365),
979 (2, 1, 1, 366),
980 # first example from "Calendrical Calculations"
981 (1945, 11, 12, 710347)]:
982 d = self.theclass(y, m, d)
983 self.assertEqual(n, d.toordinal())
984 fromord = self.theclass.fromordinal(n)
985 self.assertEqual(d, fromord)
986 if hasattr(fromord, "hour"):
987 # if we're checking something fancier than a date, verify
988 # the extra fields have been zeroed out
989 self.assertEqual(fromord.hour, 0)
990 self.assertEqual(fromord.minute, 0)
991 self.assertEqual(fromord.second, 0)
992 self.assertEqual(fromord.microsecond, 0)
993
994 # Check first and last days of year spottily across the whole
995 # range of years supported.
996 for year in range(MINYEAR, MAXYEAR+1, 7):
997 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
998 d = self.theclass(year, 1, 1)
999 n = d.toordinal()
1000 d2 = self.theclass.fromordinal(n)
1001 self.assertEqual(d, d2)
1002 # Verify that moving back a day gets to the end of year-1.
1003 if year > 1:
1004 d = self.theclass.fromordinal(n-1)
1005 d2 = self.theclass(year-1, 12, 31)
1006 self.assertEqual(d, d2)
1007 self.assertEqual(d2.toordinal(), n-1)
1008
1009 # Test every day in a leap-year and a non-leap year.
1010 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
1011 for year, isleap in (2000, True), (2002, False):
1012 n = self.theclass(year, 1, 1).toordinal()
1013 for month, maxday in zip(range(1, 13), dim):
1014 if month == 2 and isleap:
1015 maxday += 1
1016 for day in range(1, maxday+1):
1017 d = self.theclass(year, month, day)
1018 self.assertEqual(d.toordinal(), n)
1019 self.assertEqual(d, self.theclass.fromordinal(n))
1020 n += 1
1021
1022 def test_extreme_ordinals(self):
1023 a = self.theclass.min
1024 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
1025 aord = a.toordinal()
1026 b = a.fromordinal(aord)
1027 self.assertEqual(a, b)
1028
1029 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
1030
1031 b = a + timedelta(days=1)
1032 self.assertEqual(b.toordinal(), aord + 1)
1033 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
1034
1035 a = self.theclass.max
1036 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
1037 aord = a.toordinal()
1038 b = a.fromordinal(aord)
1039 self.assertEqual(a, b)
1040
1041 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
1042
1043 b = a - timedelta(days=1)
1044 self.assertEqual(b.toordinal(), aord - 1)
1045 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
1046
1047 def test_bad_constructor_arguments(self):
1048 # bad years
1049 self.theclass(MINYEAR, 1, 1) # no exception
1050 self.theclass(MAXYEAR, 1, 1) # no exception
1051 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1052 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1053 # bad months
1054 self.theclass(2000, 1, 1) # no exception
1055 self.theclass(2000, 12, 1) # no exception
1056 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1057 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1058 # bad days
1059 self.theclass(2000, 2, 29) # no exception
1060 self.theclass(2004, 2, 29) # no exception
1061 self.theclass(2400, 2, 29) # no exception
1062 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1063 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1064 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1065 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1066 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1067 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1068
1069 def test_hash_equality(self):
1070 d = self.theclass(2000, 12, 31)
1071 # same thing
1072 e = self.theclass(2000, 12, 31)
1073 self.assertEqual(d, e)
1074 self.assertEqual(hash(d), hash(e))
1075
1076 dic = {d: 1}
1077 dic[e] = 2
1078 self.assertEqual(len(dic), 1)
1079 self.assertEqual(dic[d], 2)
1080 self.assertEqual(dic[e], 2)
1081
1082 d = self.theclass(2001, 1, 1)
1083 # same thing
1084 e = self.theclass(2001, 1, 1)
1085 self.assertEqual(d, e)
1086 self.assertEqual(hash(d), hash(e))
1087
1088 dic = {d: 1}
1089 dic[e] = 2
1090 self.assertEqual(len(dic), 1)
1091 self.assertEqual(dic[d], 2)
1092 self.assertEqual(dic[e], 2)
1093
1094 def test_computations(self):
1095 a = self.theclass(2002, 1, 31)
1096 b = self.theclass(1956, 1, 31)
1097 c = self.theclass(2001,2,1)
1098
1099 diff = a-b
1100 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1101 self.assertEqual(diff.seconds, 0)
1102 self.assertEqual(diff.microseconds, 0)
1103
1104 day = timedelta(1)
1105 week = timedelta(7)
1106 a = self.theclass(2002, 3, 2)
1107 self.assertEqual(a + day, self.theclass(2002, 3, 3))
1108 self.assertEqual(day + a, self.theclass(2002, 3, 3))
1109 self.assertEqual(a - day, self.theclass(2002, 3, 1))
1110 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
1111 self.assertEqual(a + week, self.theclass(2002, 3, 9))
1112 self.assertEqual(a - week, self.theclass(2002, 2, 23))
1113 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
1114 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
1115 self.assertEqual((a + week) - a, week)
1116 self.assertEqual((a + day) - a, day)
1117 self.assertEqual((a - week) - a, -week)
1118 self.assertEqual((a - day) - a, -day)
1119 self.assertEqual(a - (a + week), -week)
1120 self.assertEqual(a - (a + day), -day)
1121 self.assertEqual(a - (a - week), week)
1122 self.assertEqual(a - (a - day), day)
1123 self.assertEqual(c - (c - day), day)
1124
1125 # Add/sub ints or floats should be illegal
1126 for i in 1, 1.0:
1127 self.assertRaises(TypeError, lambda: a+i)
1128 self.assertRaises(TypeError, lambda: a-i)
1129 self.assertRaises(TypeError, lambda: i+a)
1130 self.assertRaises(TypeError, lambda: i-a)
1131
1132 # delta - date is senseless.
1133 self.assertRaises(TypeError, lambda: day - a)
1134 # mixing date and (delta or date) via * or // is senseless
1135 self.assertRaises(TypeError, lambda: day * a)
1136 self.assertRaises(TypeError, lambda: a * day)
1137 self.assertRaises(TypeError, lambda: day // a)
1138 self.assertRaises(TypeError, lambda: a // day)
1139 self.assertRaises(TypeError, lambda: a * a)
1140 self.assertRaises(TypeError, lambda: a // a)
1141 # date + date is senseless
1142 self.assertRaises(TypeError, lambda: a + a)
1143
1144 def test_overflow(self):
1145 tiny = self.theclass.resolution
1146
1147 for delta in [tiny, timedelta(1), timedelta(2)]:
1148 dt = self.theclass.min + delta
1149 dt -= delta # no problem
1150 self.assertRaises(OverflowError, dt.__sub__, delta)
1151 self.assertRaises(OverflowError, dt.__add__, -delta)
1152
1153 dt = self.theclass.max - delta
1154 dt += delta # no problem
1155 self.assertRaises(OverflowError, dt.__add__, delta)
1156 self.assertRaises(OverflowError, dt.__sub__, -delta)
1157
1158 def test_fromtimestamp(self):
1159 import time
1160
1161 # Try an arbitrary fixed value.
1162 year, month, day = 1999, 9, 19
1163 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
1164 d = self.theclass.fromtimestamp(ts)
1165 self.assertEqual(d.year, year)
1166 self.assertEqual(d.month, month)
1167 self.assertEqual(d.day, day)
1168
1169 def test_insane_fromtimestamp(self):
1170 # It's possible that some platform maps time_t to double,
1171 # and that this test will fail there. This test should
1172 # exempt such platforms (provided they return reasonable
1173 # results!).
1174 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001175 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001176 insane)
1177
1178 def test_today(self):
1179 import time
1180
1181 # We claim that today() is like fromtimestamp(time.time()), so
1182 # prove it.
1183 for dummy in range(3):
1184 today = self.theclass.today()
1185 ts = time.time()
1186 todayagain = self.theclass.fromtimestamp(ts)
1187 if today == todayagain:
1188 break
1189 # There are several legit reasons that could fail:
1190 # 1. It recently became midnight, between the today() and the
1191 # time() calls.
1192 # 2. The platform time() has such fine resolution that we'll
1193 # never get the same value twice.
1194 # 3. The platform time() has poor resolution, and we just
1195 # happened to call today() right before a resolution quantum
1196 # boundary.
1197 # 4. The system clock got fiddled between calls.
1198 # In any case, wait a little while and try again.
1199 time.sleep(0.1)
1200
1201 # It worked or it didn't. If it didn't, assume it's reason #2, and
1202 # let the test pass if they're within half a second of each other.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001203 if today != todayagain:
1204 self.assertAlmostEqual(todayagain, today,
1205 delta=timedelta(seconds=0.5))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001206
1207 def test_weekday(self):
1208 for i in range(7):
1209 # March 4, 2002 is a Monday
1210 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
1211 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
1212 # January 2, 1956 is a Monday
1213 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
1214 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
1215
1216 def test_isocalendar(self):
1217 # Check examples from
1218 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1219 for i in range(7):
1220 d = self.theclass(2003, 12, 22+i)
1221 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
1222 d = self.theclass(2003, 12, 29) + timedelta(i)
1223 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
1224 d = self.theclass(2004, 1, 5+i)
1225 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
1226 d = self.theclass(2009, 12, 21+i)
1227 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
1228 d = self.theclass(2009, 12, 28) + timedelta(i)
1229 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
1230 d = self.theclass(2010, 1, 4+i)
1231 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
1232
1233 def test_iso_long_years(self):
1234 # Calculate long ISO years and compare to table from
1235 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1236 ISO_LONG_YEARS_TABLE = """
1237 4 32 60 88
1238 9 37 65 93
1239 15 43 71 99
1240 20 48 76
1241 26 54 82
1242
1243 105 133 161 189
1244 111 139 167 195
1245 116 144 172
1246 122 150 178
1247 128 156 184
1248
1249 201 229 257 285
1250 207 235 263 291
1251 212 240 268 296
1252 218 246 274
1253 224 252 280
1254
1255 303 331 359 387
1256 308 336 364 392
1257 314 342 370 398
1258 320 348 376
1259 325 353 381
1260 """
1261 iso_long_years = sorted(map(int, ISO_LONG_YEARS_TABLE.split()))
1262 L = []
1263 for i in range(400):
1264 d = self.theclass(2000+i, 12, 31)
1265 d1 = self.theclass(1600+i, 12, 31)
1266 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
1267 if d.isocalendar()[1] == 53:
1268 L.append(i)
1269 self.assertEqual(L, iso_long_years)
1270
1271 def test_isoformat(self):
1272 t = self.theclass(2, 3, 2)
1273 self.assertEqual(t.isoformat(), "0002-03-02")
1274
1275 def test_ctime(self):
1276 t = self.theclass(2002, 3, 2)
1277 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
1278
1279 def test_strftime(self):
1280 t = self.theclass(2005, 3, 2)
1281 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
1282 self.assertEqual(t.strftime(""), "") # SF bug #761337
1283 self.assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784
1284
1285 self.assertRaises(TypeError, t.strftime) # needs an arg
1286 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
1287 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
1288
1289 # test that unicode input is allowed (issue 2782)
1290 self.assertEqual(t.strftime("%m"), "03")
1291
1292 # A naive object replaces %z and %Z w/ empty strings.
1293 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1294
1295 #make sure that invalid format specifiers are handled correctly
1296 #self.assertRaises(ValueError, t.strftime, "%e")
1297 #self.assertRaises(ValueError, t.strftime, "%")
1298 #self.assertRaises(ValueError, t.strftime, "%#")
1299
1300 #oh well, some systems just ignore those invalid ones.
Martin Panter46f50722016-05-26 05:35:26 +00001301 #at least, exercise them to make sure that no crashes
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001302 #are generated
1303 for f in ["%e", "%", "%#"]:
1304 try:
1305 t.strftime(f)
1306 except ValueError:
1307 pass
1308
1309 #check that this standard extension works
1310 t.strftime("%f")
1311
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001312 def test_format(self):
1313 dt = self.theclass(2007, 9, 10)
1314 self.assertEqual(dt.__format__(''), str(dt))
1315
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02001316 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001317 dt.__format__(123)
1318
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001319 # check that a derived class's __str__() gets called
1320 class A(self.theclass):
1321 def __str__(self):
1322 return 'A'
1323 a = A(2007, 9, 10)
1324 self.assertEqual(a.__format__(''), 'A')
1325
1326 # check that a derived class's strftime gets called
1327 class B(self.theclass):
1328 def strftime(self, format_spec):
1329 return 'B'
1330 b = B(2007, 9, 10)
1331 self.assertEqual(b.__format__(''), str(dt))
1332
1333 for fmt in ["m:%m d:%d y:%y",
1334 "m:%m d:%d y:%y H:%H M:%M S:%S",
1335 "%z %Z",
1336 ]:
1337 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1338 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1339 self.assertEqual(b.__format__(fmt), 'B')
1340
1341 def test_resolution_info(self):
1342 # XXX: Should min and max respect subclassing?
1343 if issubclass(self.theclass, datetime):
1344 expected_class = datetime
1345 else:
1346 expected_class = date
1347 self.assertIsInstance(self.theclass.min, expected_class)
1348 self.assertIsInstance(self.theclass.max, expected_class)
1349 self.assertIsInstance(self.theclass.resolution, timedelta)
1350 self.assertTrue(self.theclass.max > self.theclass.min)
1351
1352 def test_extreme_timedelta(self):
1353 big = self.theclass.max - self.theclass.min
1354 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
1355 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
1356 # n == 315537897599999999 ~= 2**58.13
1357 justasbig = timedelta(0, 0, n)
1358 self.assertEqual(big, justasbig)
1359 self.assertEqual(self.theclass.min + big, self.theclass.max)
1360 self.assertEqual(self.theclass.max - big, self.theclass.min)
1361
1362 def test_timetuple(self):
1363 for i in range(7):
1364 # January 2, 1956 is a Monday (0)
1365 d = self.theclass(1956, 1, 2+i)
1366 t = d.timetuple()
1367 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
1368 # February 1, 1956 is a Wednesday (2)
1369 d = self.theclass(1956, 2, 1+i)
1370 t = d.timetuple()
1371 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
1372 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
1373 # of the year.
1374 d = self.theclass(1956, 3, 1+i)
1375 t = d.timetuple()
1376 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
1377 self.assertEqual(t.tm_year, 1956)
1378 self.assertEqual(t.tm_mon, 3)
1379 self.assertEqual(t.tm_mday, 1+i)
1380 self.assertEqual(t.tm_hour, 0)
1381 self.assertEqual(t.tm_min, 0)
1382 self.assertEqual(t.tm_sec, 0)
1383 self.assertEqual(t.tm_wday, (3+i)%7)
1384 self.assertEqual(t.tm_yday, 61+i)
1385 self.assertEqual(t.tm_isdst, -1)
1386
1387 def test_pickling(self):
1388 args = 6, 7, 23
1389 orig = self.theclass(*args)
1390 for pickler, unpickler, proto in pickle_choices:
1391 green = pickler.dumps(orig, proto)
1392 derived = unpickler.loads(green)
1393 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02001394 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001395
1396 def test_compare(self):
1397 t1 = self.theclass(2, 3, 4)
1398 t2 = self.theclass(2, 3, 4)
1399 self.assertEqual(t1, t2)
1400 self.assertTrue(t1 <= t2)
1401 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001402 self.assertFalse(t1 != t2)
1403 self.assertFalse(t1 < t2)
1404 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001405
1406 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
1407 t2 = self.theclass(*args) # this is larger than t1
1408 self.assertTrue(t1 < t2)
1409 self.assertTrue(t2 > t1)
1410 self.assertTrue(t1 <= t2)
1411 self.assertTrue(t2 >= t1)
1412 self.assertTrue(t1 != t2)
1413 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001414 self.assertFalse(t1 == t2)
1415 self.assertFalse(t2 == t1)
1416 self.assertFalse(t1 > t2)
1417 self.assertFalse(t2 < t1)
1418 self.assertFalse(t1 >= t2)
1419 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001420
1421 for badarg in OTHERSTUFF:
1422 self.assertEqual(t1 == badarg, False)
1423 self.assertEqual(t1 != badarg, True)
1424 self.assertEqual(badarg == t1, False)
1425 self.assertEqual(badarg != t1, True)
1426
1427 self.assertRaises(TypeError, lambda: t1 < badarg)
1428 self.assertRaises(TypeError, lambda: t1 > badarg)
1429 self.assertRaises(TypeError, lambda: t1 >= badarg)
1430 self.assertRaises(TypeError, lambda: badarg <= t1)
1431 self.assertRaises(TypeError, lambda: badarg < t1)
1432 self.assertRaises(TypeError, lambda: badarg > t1)
1433 self.assertRaises(TypeError, lambda: badarg >= t1)
1434
1435 def test_mixed_compare(self):
1436 our = self.theclass(2000, 4, 5)
1437
1438 # Our class can be compared for equality to other classes
1439 self.assertEqual(our == 1, False)
1440 self.assertEqual(1 == our, False)
1441 self.assertEqual(our != 1, True)
1442 self.assertEqual(1 != our, True)
1443
1444 # But the ordering is undefined
1445 self.assertRaises(TypeError, lambda: our < 1)
1446 self.assertRaises(TypeError, lambda: 1 < our)
1447
1448 # Repeat those tests with a different class
1449
1450 class SomeClass:
1451 pass
1452
1453 their = SomeClass()
1454 self.assertEqual(our == their, False)
1455 self.assertEqual(their == our, False)
1456 self.assertEqual(our != their, True)
1457 self.assertEqual(their != our, True)
1458 self.assertRaises(TypeError, lambda: our < their)
1459 self.assertRaises(TypeError, lambda: their < our)
1460
1461 # However, if the other class explicitly defines ordering
1462 # relative to our class, it is allowed to do so
1463
1464 class LargerThanAnything:
1465 def __lt__(self, other):
1466 return False
1467 def __le__(self, other):
1468 return isinstance(other, LargerThanAnything)
1469 def __eq__(self, other):
1470 return isinstance(other, LargerThanAnything)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001471 def __gt__(self, other):
1472 return not isinstance(other, LargerThanAnything)
1473 def __ge__(self, other):
1474 return True
1475
1476 their = LargerThanAnything()
1477 self.assertEqual(our == their, False)
1478 self.assertEqual(their == our, False)
1479 self.assertEqual(our != their, True)
1480 self.assertEqual(their != our, True)
1481 self.assertEqual(our < their, True)
1482 self.assertEqual(their < our, False)
1483
1484 def test_bool(self):
1485 # All dates are considered true.
1486 self.assertTrue(self.theclass.min)
1487 self.assertTrue(self.theclass.max)
1488
Alexander Belopolsky89da3492011-05-02 13:14:24 -04001489 def test_strftime_y2k(self):
1490 for y in (1, 49, 70, 99, 100, 999, 1000, 1970):
Florent Xicluna49ce0682011-11-01 12:56:14 +01001491 d = self.theclass(y, 1, 1)
1492 # Issue 13305: For years < 1000, the value is not always
1493 # padded to 4 digits across platforms. The C standard
1494 # assumes year >= 1900, so it does not specify the number
1495 # of digits.
1496 if d.strftime("%Y") != '%04d' % y:
1497 # Year 42 returns '42', not padded
1498 self.assertEqual(d.strftime("%Y"), '%d' % y)
Victor Stinner15a83e82016-03-12 08:16:48 +01001499 # '0042' is obtained anyway
1500 self.assertEqual(d.strftime("%4Y"), '%04d' % y)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001501
1502 def test_replace(self):
1503 cls = self.theclass
1504 args = [1, 2, 3]
1505 base = cls(*args)
1506 self.assertEqual(base, base.replace())
1507
1508 i = 0
1509 for name, newval in (("year", 2),
1510 ("month", 3),
1511 ("day", 4)):
1512 newargs = args[:]
1513 newargs[i] = newval
1514 expected = cls(*newargs)
1515 got = base.replace(**{name: newval})
1516 self.assertEqual(expected, got)
1517 i += 1
1518
1519 # Out of bounds.
1520 base = cls(2000, 2, 29)
1521 self.assertRaises(ValueError, base.replace, year=2001)
1522
Paul Ganssle191e9932017-11-09 16:34:29 -05001523 def test_subclass_replace(self):
1524 class DateSubclass(self.theclass):
1525 pass
1526
1527 dt = DateSubclass(2012, 1, 1)
1528 self.assertIs(type(dt.replace(year=2013)), DateSubclass)
1529
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001530 def test_subclass_date(self):
1531
1532 class C(self.theclass):
1533 theAnswer = 42
1534
1535 def __new__(cls, *args, **kws):
1536 temp = kws.copy()
1537 extra = temp.pop('extra')
1538 result = self.theclass.__new__(cls, *args, **temp)
1539 result.extra = extra
1540 return result
1541
1542 def newmeth(self, start):
1543 return start + self.year + self.month
1544
1545 args = 2003, 4, 14
1546
1547 dt1 = self.theclass(*args)
1548 dt2 = C(*args, **{'extra': 7})
1549
1550 self.assertEqual(dt2.__class__, C)
1551 self.assertEqual(dt2.theAnswer, 42)
1552 self.assertEqual(dt2.extra, 7)
1553 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1554 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1555
1556 def test_pickling_subclass_date(self):
1557
1558 args = 6, 7, 23
1559 orig = SubclassDate(*args)
1560 for pickler, unpickler, proto in pickle_choices:
1561 green = pickler.dumps(orig, proto)
1562 derived = unpickler.loads(green)
1563 self.assertEqual(orig, derived)
1564
1565 def test_backdoor_resistance(self):
1566 # For fast unpickling, the constructor accepts a pickle byte string.
1567 # This is a low-overhead backdoor. A user can (by intent or
1568 # mistake) pass a string directly, which (if it's the right length)
1569 # will get treated like a pickle, and bypass the normal sanity
1570 # checks in the constructor. This can create insane objects.
1571 # The constructor doesn't want to burn the time to validate all
1572 # fields, but does check the month field. This stops, e.g.,
1573 # datetime.datetime('1995-03-25') from yielding an insane object.
1574 base = b'1995-03-25'
1575 if not issubclass(self.theclass, datetime):
1576 base = base[:4]
1577 for month_byte in b'9', b'\0', b'\r', b'\xff':
1578 self.assertRaises(TypeError, self.theclass,
1579 base[:2] + month_byte + base[3:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001580 if issubclass(self.theclass, datetime):
1581 # Good bytes, but bad tzinfo:
1582 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
1583 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001584
1585 for ord_byte in range(1, 13):
1586 # This shouldn't blow up because of the month byte alone. If
1587 # the implementation changes to do more-careful checking, it may
1588 # blow up because other fields are insane.
1589 self.theclass(base[:2] + bytes([ord_byte]) + base[3:])
1590
1591#############################################################################
1592# datetime tests
1593
1594class SubclassDatetime(datetime):
1595 sub_var = 1
1596
1597class TestDateTime(TestDate):
1598
1599 theclass = datetime
1600
1601 def test_basic_attributes(self):
1602 dt = self.theclass(2002, 3, 1, 12, 0)
1603 self.assertEqual(dt.year, 2002)
1604 self.assertEqual(dt.month, 3)
1605 self.assertEqual(dt.day, 1)
1606 self.assertEqual(dt.hour, 12)
1607 self.assertEqual(dt.minute, 0)
1608 self.assertEqual(dt.second, 0)
1609 self.assertEqual(dt.microsecond, 0)
1610
1611 def test_basic_attributes_nonzero(self):
1612 # Make sure all attributes are non-zero so bugs in
1613 # bit-shifting access show up.
1614 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1615 self.assertEqual(dt.year, 2002)
1616 self.assertEqual(dt.month, 3)
1617 self.assertEqual(dt.day, 1)
1618 self.assertEqual(dt.hour, 12)
1619 self.assertEqual(dt.minute, 59)
1620 self.assertEqual(dt.second, 59)
1621 self.assertEqual(dt.microsecond, 8000)
1622
1623 def test_roundtrip(self):
1624 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1625 self.theclass.now()):
1626 # Verify dt -> string -> datetime identity.
1627 s = repr(dt)
1628 self.assertTrue(s.startswith('datetime.'))
1629 s = s[9:]
1630 dt2 = eval(s)
1631 self.assertEqual(dt, dt2)
1632
1633 # Verify identity via reconstructing from pieces.
1634 dt2 = self.theclass(dt.year, dt.month, dt.day,
1635 dt.hour, dt.minute, dt.second,
1636 dt.microsecond)
1637 self.assertEqual(dt, dt2)
1638
1639 def test_isoformat(self):
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001640 t = self.theclass(1, 2, 3, 4, 5, 1, 123)
1641 self.assertEqual(t.isoformat(), "0001-02-03T04:05:01.000123")
1642 self.assertEqual(t.isoformat('T'), "0001-02-03T04:05:01.000123")
1643 self.assertEqual(t.isoformat(' '), "0001-02-03 04:05:01.000123")
1644 self.assertEqual(t.isoformat('\x00'), "0001-02-03\x0004:05:01.000123")
1645 self.assertEqual(t.isoformat(timespec='hours'), "0001-02-03T04")
1646 self.assertEqual(t.isoformat(timespec='minutes'), "0001-02-03T04:05")
1647 self.assertEqual(t.isoformat(timespec='seconds'), "0001-02-03T04:05:01")
1648 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1649 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000123")
1650 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01.000123")
1651 self.assertEqual(t.isoformat(sep=' ', timespec='minutes'), "0001-02-03 04:05")
1652 self.assertRaises(ValueError, t.isoformat, timespec='foo')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001653 # str is ISO format with the separator forced to a blank.
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001654 self.assertEqual(str(t), "0001-02-03 04:05:01.000123")
1655
1656 t = self.theclass(1, 2, 3, 4, 5, 1, 999500, tzinfo=timezone.utc)
1657 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999+00:00")
1658
1659 t = self.theclass(1, 2, 3, 4, 5, 1, 999500)
1660 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999")
1661
1662 t = self.theclass(1, 2, 3, 4, 5, 1)
1663 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01")
1664 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1665 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000000")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001666
1667 t = self.theclass(2, 3, 2)
1668 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1669 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1670 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1671 # str is ISO format with the separator forced to a blank.
1672 self.assertEqual(str(t), "0002-03-02 00:00:00")
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001673 # ISO format with timezone
1674 tz = FixedOffset(timedelta(seconds=16), 'XXX')
1675 t = self.theclass(2, 3, 2, tzinfo=tz)
1676 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00+00:00:16")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001677
1678 def test_format(self):
1679 dt = self.theclass(2007, 9, 10, 4, 5, 1, 123)
1680 self.assertEqual(dt.__format__(''), str(dt))
1681
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02001682 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001683 dt.__format__(123)
1684
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001685 # check that a derived class's __str__() gets called
1686 class A(self.theclass):
1687 def __str__(self):
1688 return 'A'
1689 a = A(2007, 9, 10, 4, 5, 1, 123)
1690 self.assertEqual(a.__format__(''), 'A')
1691
1692 # check that a derived class's strftime gets called
1693 class B(self.theclass):
1694 def strftime(self, format_spec):
1695 return 'B'
1696 b = B(2007, 9, 10, 4, 5, 1, 123)
1697 self.assertEqual(b.__format__(''), str(dt))
1698
1699 for fmt in ["m:%m d:%d y:%y",
1700 "m:%m d:%d y:%y H:%H M:%M S:%S",
1701 "%z %Z",
1702 ]:
1703 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1704 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1705 self.assertEqual(b.__format__(fmt), 'B')
1706
1707 def test_more_ctime(self):
1708 # Test fields that TestDate doesn't touch.
1709 import time
1710
1711 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1712 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1713 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1714 # out. The difference is that t.ctime() produces " 2" for the day,
1715 # but platform ctime() produces "02" for the day. According to
1716 # C99, t.ctime() is correct here.
1717 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1718
1719 # So test a case where that difference doesn't matter.
1720 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1721 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1722
1723 def test_tz_independent_comparing(self):
1724 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1725 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1726 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1727 self.assertEqual(dt1, dt3)
1728 self.assertTrue(dt2 > dt3)
1729
1730 # Make sure comparison doesn't forget microseconds, and isn't done
1731 # via comparing a float timestamp (an IEEE double doesn't have enough
1732 # precision to span microsecond resolution across years 1 thru 9999,
1733 # so comparing via timestamp necessarily calls some distinct values
1734 # equal).
1735 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1736 us = timedelta(microseconds=1)
1737 dt2 = dt1 + us
1738 self.assertEqual(dt2 - dt1, us)
1739 self.assertTrue(dt1 < dt2)
1740
1741 def test_strftime_with_bad_tzname_replace(self):
1742 # verify ok if tzinfo.tzname().replace() returns a non-string
1743 class MyTzInfo(FixedOffset):
1744 def tzname(self, dt):
1745 class MyStr(str):
1746 def replace(self, *args):
1747 return None
1748 return MyStr('name')
1749 t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
1750 self.assertRaises(TypeError, t.strftime, '%Z')
1751
1752 def test_bad_constructor_arguments(self):
1753 # bad years
1754 self.theclass(MINYEAR, 1, 1) # no exception
1755 self.theclass(MAXYEAR, 1, 1) # no exception
1756 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1757 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1758 # bad months
1759 self.theclass(2000, 1, 1) # no exception
1760 self.theclass(2000, 12, 1) # no exception
1761 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1762 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1763 # bad days
1764 self.theclass(2000, 2, 29) # no exception
1765 self.theclass(2004, 2, 29) # no exception
1766 self.theclass(2400, 2, 29) # no exception
1767 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1768 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1769 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1770 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1771 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1772 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1773 # bad hours
1774 self.theclass(2000, 1, 31, 0) # no exception
1775 self.theclass(2000, 1, 31, 23) # no exception
1776 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1777 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1778 # bad minutes
1779 self.theclass(2000, 1, 31, 23, 0) # no exception
1780 self.theclass(2000, 1, 31, 23, 59) # no exception
1781 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1782 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1783 # bad seconds
1784 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1785 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1786 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1787 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1788 # bad microseconds
1789 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1790 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1791 self.assertRaises(ValueError, self.theclass,
1792 2000, 1, 31, 23, 59, 59, -1)
1793 self.assertRaises(ValueError, self.theclass,
1794 2000, 1, 31, 23, 59, 59,
1795 1000000)
Alexander Belopolsky47649ab2016-08-08 17:05:40 -04001796 # bad fold
1797 self.assertRaises(ValueError, self.theclass,
1798 2000, 1, 31, fold=-1)
1799 self.assertRaises(ValueError, self.theclass,
1800 2000, 1, 31, fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001801 # Positional fold:
1802 self.assertRaises(TypeError, self.theclass,
1803 2000, 1, 31, 23, 59, 59, 0, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04001804
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001805 def test_hash_equality(self):
1806 d = self.theclass(2000, 12, 31, 23, 30, 17)
1807 e = self.theclass(2000, 12, 31, 23, 30, 17)
1808 self.assertEqual(d, e)
1809 self.assertEqual(hash(d), hash(e))
1810
1811 dic = {d: 1}
1812 dic[e] = 2
1813 self.assertEqual(len(dic), 1)
1814 self.assertEqual(dic[d], 2)
1815 self.assertEqual(dic[e], 2)
1816
1817 d = self.theclass(2001, 1, 1, 0, 5, 17)
1818 e = self.theclass(2001, 1, 1, 0, 5, 17)
1819 self.assertEqual(d, e)
1820 self.assertEqual(hash(d), hash(e))
1821
1822 dic = {d: 1}
1823 dic[e] = 2
1824 self.assertEqual(len(dic), 1)
1825 self.assertEqual(dic[d], 2)
1826 self.assertEqual(dic[e], 2)
1827
1828 def test_computations(self):
1829 a = self.theclass(2002, 1, 31)
1830 b = self.theclass(1956, 1, 31)
1831 diff = a-b
1832 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1833 self.assertEqual(diff.seconds, 0)
1834 self.assertEqual(diff.microseconds, 0)
1835 a = self.theclass(2002, 3, 2, 17, 6)
1836 millisec = timedelta(0, 0, 1000)
1837 hour = timedelta(0, 3600)
1838 day = timedelta(1)
1839 week = timedelta(7)
1840 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1841 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1842 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1843 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1844 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1845 self.assertEqual(a - hour, a + -hour)
1846 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1847 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1848 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1849 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1850 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1851 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1852 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1853 self.assertEqual((a + week) - a, week)
1854 self.assertEqual((a + day) - a, day)
1855 self.assertEqual((a + hour) - a, hour)
1856 self.assertEqual((a + millisec) - a, millisec)
1857 self.assertEqual((a - week) - a, -week)
1858 self.assertEqual((a - day) - a, -day)
1859 self.assertEqual((a - hour) - a, -hour)
1860 self.assertEqual((a - millisec) - a, -millisec)
1861 self.assertEqual(a - (a + week), -week)
1862 self.assertEqual(a - (a + day), -day)
1863 self.assertEqual(a - (a + hour), -hour)
1864 self.assertEqual(a - (a + millisec), -millisec)
1865 self.assertEqual(a - (a - week), week)
1866 self.assertEqual(a - (a - day), day)
1867 self.assertEqual(a - (a - hour), hour)
1868 self.assertEqual(a - (a - millisec), millisec)
1869 self.assertEqual(a + (week + day + hour + millisec),
1870 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1871 self.assertEqual(a + (week + day + hour + millisec),
1872 (((a + week) + day) + hour) + millisec)
1873 self.assertEqual(a - (week + day + hour + millisec),
1874 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1875 self.assertEqual(a - (week + day + hour + millisec),
1876 (((a - week) - day) - hour) - millisec)
1877 # Add/sub ints or floats should be illegal
1878 for i in 1, 1.0:
1879 self.assertRaises(TypeError, lambda: a+i)
1880 self.assertRaises(TypeError, lambda: a-i)
1881 self.assertRaises(TypeError, lambda: i+a)
1882 self.assertRaises(TypeError, lambda: i-a)
1883
1884 # delta - datetime is senseless.
1885 self.assertRaises(TypeError, lambda: day - a)
1886 # mixing datetime and (delta or datetime) via * or // is senseless
1887 self.assertRaises(TypeError, lambda: day * a)
1888 self.assertRaises(TypeError, lambda: a * day)
1889 self.assertRaises(TypeError, lambda: day // a)
1890 self.assertRaises(TypeError, lambda: a // day)
1891 self.assertRaises(TypeError, lambda: a * a)
1892 self.assertRaises(TypeError, lambda: a // a)
1893 # datetime + datetime is senseless
1894 self.assertRaises(TypeError, lambda: a + a)
1895
1896 def test_pickling(self):
1897 args = 6, 7, 23, 20, 59, 1, 64**2
1898 orig = self.theclass(*args)
1899 for pickler, unpickler, proto in pickle_choices:
1900 green = pickler.dumps(orig, proto)
1901 derived = unpickler.loads(green)
1902 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02001903 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001904
1905 def test_more_pickling(self):
1906 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
Serhiy Storchakabad12572014-12-15 14:03:42 +02001907 for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1908 s = pickle.dumps(a, proto)
1909 b = pickle.loads(s)
1910 self.assertEqual(b.year, 2003)
1911 self.assertEqual(b.month, 2)
1912 self.assertEqual(b.day, 7)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001913
1914 def test_pickling_subclass_datetime(self):
1915 args = 6, 7, 23, 20, 59, 1, 64**2
1916 orig = SubclassDatetime(*args)
1917 for pickler, unpickler, proto in pickle_choices:
1918 green = pickler.dumps(orig, proto)
1919 derived = unpickler.loads(green)
1920 self.assertEqual(orig, derived)
1921
1922 def test_more_compare(self):
1923 # The test_compare() inherited from TestDate covers the error cases.
1924 # We just want to test lexicographic ordering on the members datetime
1925 # has that date lacks.
1926 args = [2000, 11, 29, 20, 58, 16, 999998]
1927 t1 = self.theclass(*args)
1928 t2 = self.theclass(*args)
1929 self.assertEqual(t1, t2)
1930 self.assertTrue(t1 <= t2)
1931 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001932 self.assertFalse(t1 != t2)
1933 self.assertFalse(t1 < t2)
1934 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001935
1936 for i in range(len(args)):
1937 newargs = args[:]
1938 newargs[i] = args[i] + 1
1939 t2 = self.theclass(*newargs) # this is larger than t1
1940 self.assertTrue(t1 < t2)
1941 self.assertTrue(t2 > t1)
1942 self.assertTrue(t1 <= t2)
1943 self.assertTrue(t2 >= t1)
1944 self.assertTrue(t1 != t2)
1945 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001946 self.assertFalse(t1 == t2)
1947 self.assertFalse(t2 == t1)
1948 self.assertFalse(t1 > t2)
1949 self.assertFalse(t2 < t1)
1950 self.assertFalse(t1 >= t2)
1951 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001952
1953
1954 # A helper for timestamp constructor tests.
1955 def verify_field_equality(self, expected, got):
1956 self.assertEqual(expected.tm_year, got.year)
1957 self.assertEqual(expected.tm_mon, got.month)
1958 self.assertEqual(expected.tm_mday, got.day)
1959 self.assertEqual(expected.tm_hour, got.hour)
1960 self.assertEqual(expected.tm_min, got.minute)
1961 self.assertEqual(expected.tm_sec, got.second)
1962
1963 def test_fromtimestamp(self):
1964 import time
1965
1966 ts = time.time()
1967 expected = time.localtime(ts)
1968 got = self.theclass.fromtimestamp(ts)
1969 self.verify_field_equality(expected, got)
1970
1971 def test_utcfromtimestamp(self):
1972 import time
1973
1974 ts = time.time()
1975 expected = time.gmtime(ts)
1976 got = self.theclass.utcfromtimestamp(ts)
1977 self.verify_field_equality(expected, got)
1978
Alexander Belopolskya4415142012-06-08 12:33:09 -04001979 # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in
1980 # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0).
1981 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
1982 def test_timestamp_naive(self):
1983 t = self.theclass(1970, 1, 1)
1984 self.assertEqual(t.timestamp(), 18000.0)
1985 t = self.theclass(1970, 1, 1, 1, 2, 3, 4)
1986 self.assertEqual(t.timestamp(),
1987 18000.0 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001988 # Missing hour
1989 t0 = self.theclass(2012, 3, 11, 2, 30)
1990 t1 = t0.replace(fold=1)
1991 self.assertEqual(self.theclass.fromtimestamp(t1.timestamp()),
1992 t0 - timedelta(hours=1))
1993 self.assertEqual(self.theclass.fromtimestamp(t0.timestamp()),
1994 t1 + timedelta(hours=1))
Alexander Belopolskya4415142012-06-08 12:33:09 -04001995 # Ambiguous hour defaults to DST
1996 t = self.theclass(2012, 11, 4, 1, 30)
1997 self.assertEqual(self.theclass.fromtimestamp(t.timestamp()), t)
1998
1999 # Timestamp may raise an overflow error on some platforms
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002000 # XXX: Do we care to support the first and last year?
2001 for t in [self.theclass(2,1,1), self.theclass(9998,12,12)]:
Alexander Belopolskya4415142012-06-08 12:33:09 -04002002 try:
2003 s = t.timestamp()
2004 except OverflowError:
2005 pass
2006 else:
2007 self.assertEqual(self.theclass.fromtimestamp(s), t)
2008
2009 def test_timestamp_aware(self):
2010 t = self.theclass(1970, 1, 1, tzinfo=timezone.utc)
2011 self.assertEqual(t.timestamp(), 0.0)
2012 t = self.theclass(1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone.utc)
2013 self.assertEqual(t.timestamp(),
2014 3600 + 2*60 + 3 + 4*1e-6)
2015 t = self.theclass(1970, 1, 1, 1, 2, 3, 4,
2016 tzinfo=timezone(timedelta(hours=-5), 'EST'))
2017 self.assertEqual(t.timestamp(),
2018 18000 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002019
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002020 @support.run_with_tz('MSK-03') # Something east of Greenwich
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002021 def test_microsecond_rounding(self):
Victor Stinner5ebfe422015-09-18 14:52:15 +02002022 for fts in [self.theclass.fromtimestamp,
2023 self.theclass.utcfromtimestamp]:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002024 zero = fts(0)
2025 self.assertEqual(zero.second, 0)
2026 self.assertEqual(zero.microsecond, 0)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002027 one = fts(1e-6)
Victor Stinner8050ca92012-03-14 00:17:05 +01002028 try:
2029 minus_one = fts(-1e-6)
2030 except OSError:
2031 # localtime(-1) and gmtime(-1) is not supported on Windows
2032 pass
2033 else:
2034 self.assertEqual(minus_one.second, 59)
2035 self.assertEqual(minus_one.microsecond, 999999)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002036
Victor Stinner8050ca92012-03-14 00:17:05 +01002037 t = fts(-1e-8)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002038 self.assertEqual(t, zero)
Victor Stinner8050ca92012-03-14 00:17:05 +01002039 t = fts(-9e-7)
2040 self.assertEqual(t, minus_one)
2041 t = fts(-1e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002042 self.assertEqual(t, zero)
Victor Stinner8820a352015-09-05 10:50:20 +02002043 t = fts(-1/2**7)
2044 self.assertEqual(t.second, 59)
Victor Stinner7667f582015-09-09 01:02:23 +02002045 self.assertEqual(t.microsecond, 992188)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002046
2047 t = fts(1e-7)
2048 self.assertEqual(t, zero)
2049 t = fts(9e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002050 self.assertEqual(t, one)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002051 t = fts(0.99999949)
2052 self.assertEqual(t.second, 0)
2053 self.assertEqual(t.microsecond, 999999)
2054 t = fts(0.9999999)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002055 self.assertEqual(t.second, 1)
2056 self.assertEqual(t.microsecond, 0)
Victor Stinneradfefa52015-09-04 23:57:25 +02002057 t = fts(1/2**7)
2058 self.assertEqual(t.second, 0)
Victor Stinner7667f582015-09-09 01:02:23 +02002059 self.assertEqual(t.microsecond, 7812)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002060
Victor Stinnerb67f0962017-02-10 10:34:02 +01002061 def test_timestamp_limits(self):
2062 # minimum timestamp
2063 min_dt = self.theclass.min.replace(tzinfo=timezone.utc)
2064 min_ts = min_dt.timestamp()
Victor Stinner6f37e362017-02-10 11:45:14 +01002065 try:
2066 # date 0001-01-01 00:00:00+00:00: timestamp=-62135596800
2067 self.assertEqual(self.theclass.fromtimestamp(min_ts, tz=timezone.utc),
2068 min_dt)
Victor Stinner2a35c932017-02-10 12:37:21 +01002069 except (OverflowError, OSError) as exc:
2070 # the date 0001-01-01 doesn't fit into 32-bit time_t,
2071 # or platform doesn't support such very old date
Victor Stinner6f37e362017-02-10 11:45:14 +01002072 self.skipTest(str(exc))
Victor Stinnerb67f0962017-02-10 10:34:02 +01002073
2074 # maximum timestamp: set seconds to zero to avoid rounding issues
2075 max_dt = self.theclass.max.replace(tzinfo=timezone.utc,
2076 second=0, microsecond=0)
2077 max_ts = max_dt.timestamp()
2078 # date 9999-12-31 23:59:00+00:00: timestamp 253402300740
2079 self.assertEqual(self.theclass.fromtimestamp(max_ts, tz=timezone.utc),
2080 max_dt)
2081
2082 # number of seconds greater than 1 year: make sure that the new date
2083 # is not valid in datetime.datetime limits
2084 delta = 3600 * 24 * 400
2085
2086 # too small
2087 ts = min_ts - delta
2088 # converting a Python int to C time_t can raise a OverflowError,
2089 # especially on 32-bit platforms.
2090 with self.assertRaises((ValueError, OverflowError)):
2091 self.theclass.fromtimestamp(ts)
2092 with self.assertRaises((ValueError, OverflowError)):
2093 self.theclass.utcfromtimestamp(ts)
2094
2095 # too big
2096 ts = max_dt.timestamp() + delta
2097 with self.assertRaises((ValueError, OverflowError)):
2098 self.theclass.fromtimestamp(ts)
2099 with self.assertRaises((ValueError, OverflowError)):
2100 self.theclass.utcfromtimestamp(ts)
2101
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002102 def test_insane_fromtimestamp(self):
2103 # It's possible that some platform maps time_t to double,
2104 # and that this test will fail there. This test should
2105 # exempt such platforms (provided they return reasonable
2106 # results!).
2107 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002108 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002109 insane)
2110
2111 def test_insane_utcfromtimestamp(self):
2112 # It's possible that some platform maps time_t to double,
2113 # and that this test will fail there. This test should
2114 # exempt such platforms (provided they return reasonable
2115 # results!).
2116 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002117 self.assertRaises(OverflowError, self.theclass.utcfromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002118 insane)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002119
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002120 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2121 def test_negative_float_fromtimestamp(self):
2122 # The result is tz-dependent; at least test that this doesn't
2123 # fail (like it did before bug 1646728 was fixed).
2124 self.theclass.fromtimestamp(-1.05)
2125
2126 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2127 def test_negative_float_utcfromtimestamp(self):
2128 d = self.theclass.utcfromtimestamp(-1.05)
2129 self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
2130
2131 def test_utcnow(self):
2132 import time
2133
2134 # Call it a success if utcnow() and utcfromtimestamp() are within
2135 # a second of each other.
2136 tolerance = timedelta(seconds=1)
2137 for dummy in range(3):
2138 from_now = self.theclass.utcnow()
2139 from_timestamp = self.theclass.utcfromtimestamp(time.time())
2140 if abs(from_timestamp - from_now) <= tolerance:
2141 break
2142 # Else try again a few times.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002143 self.assertLessEqual(abs(from_timestamp - from_now), tolerance)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002144
2145 def test_strptime(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002146 string = '2004-12-01 13:02:47.197'
2147 format = '%Y-%m-%d %H:%M:%S.%f'
2148 expected = _strptime._strptime_datetime(self.theclass, string, format)
2149 got = self.theclass.strptime(string, format)
2150 self.assertEqual(expected, got)
2151 self.assertIs(type(expected), self.theclass)
2152 self.assertIs(type(got), self.theclass)
2153
2154 strptime = self.theclass.strptime
2155 self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
2156 self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
Mario Corchero32318932017-10-26 01:35:41 +01002157 self.assertEqual(
2158 strptime("-00:02:01.000003", "%z").utcoffset(),
2159 -timedelta(minutes=2, seconds=1, microseconds=3)
2160 )
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002161 # Only local timezone and UTC are supported
2162 for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
2163 (-_time.timezone, _time.tzname[0])):
2164 if tzseconds < 0:
2165 sign = '-'
2166 seconds = -tzseconds
2167 else:
2168 sign ='+'
2169 seconds = tzseconds
2170 hours, minutes = divmod(seconds//60, 60)
2171 dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
Martin Panterfca22322015-11-16 09:22:19 +00002172 dt = strptime(dtstr, "%z %Z")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002173 self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
2174 self.assertEqual(dt.tzname(), tzname)
2175 # Can produce inconsistent datetime
2176 dtstr, fmt = "+1234 UTC", "%z %Z"
2177 dt = strptime(dtstr, fmt)
2178 self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE)
2179 self.assertEqual(dt.tzname(), 'UTC')
2180 # yet will roundtrip
2181 self.assertEqual(dt.strftime(fmt), dtstr)
2182
2183 # Produce naive datetime if no %z is provided
2184 self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
2185
2186 with self.assertRaises(ValueError): strptime("-2400", "%z")
2187 with self.assertRaises(ValueError): strptime("-000", "%z")
2188
2189 def test_more_timetuple(self):
2190 # This tests fields beyond those tested by the TestDate.test_timetuple.
2191 t = self.theclass(2004, 12, 31, 6, 22, 33)
2192 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
2193 self.assertEqual(t.timetuple(),
2194 (t.year, t.month, t.day,
2195 t.hour, t.minute, t.second,
2196 t.weekday(),
2197 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
2198 -1))
2199 tt = t.timetuple()
2200 self.assertEqual(tt.tm_year, t.year)
2201 self.assertEqual(tt.tm_mon, t.month)
2202 self.assertEqual(tt.tm_mday, t.day)
2203 self.assertEqual(tt.tm_hour, t.hour)
2204 self.assertEqual(tt.tm_min, t.minute)
2205 self.assertEqual(tt.tm_sec, t.second)
2206 self.assertEqual(tt.tm_wday, t.weekday())
2207 self.assertEqual(tt.tm_yday, t.toordinal() -
2208 date(t.year, 1, 1).toordinal() + 1)
2209 self.assertEqual(tt.tm_isdst, -1)
2210
2211 def test_more_strftime(self):
2212 # This tests fields beyond those tested by the TestDate.test_strftime.
2213 t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
2214 self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
2215 "12 31 04 000047 33 22 06 366")
Alexander Belopolsky018d3532017-07-31 10:26:50 -04002216 tz = timezone(-timedelta(hours=2, seconds=33, microseconds=123))
2217 t = t.replace(tzinfo=tz)
2218 self.assertEqual(t.strftime("%z"), "-020033.000123")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002219
2220 def test_extract(self):
2221 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2222 self.assertEqual(dt.date(), date(2002, 3, 4))
2223 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2224
2225 def test_combine(self):
2226 d = date(2002, 3, 4)
2227 t = time(18, 45, 3, 1234)
2228 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2229 combine = self.theclass.combine
2230 dt = combine(d, t)
2231 self.assertEqual(dt, expected)
2232
2233 dt = combine(time=t, date=d)
2234 self.assertEqual(dt, expected)
2235
2236 self.assertEqual(d, dt.date())
2237 self.assertEqual(t, dt.time())
2238 self.assertEqual(dt, combine(dt.date(), dt.time()))
2239
2240 self.assertRaises(TypeError, combine) # need an arg
2241 self.assertRaises(TypeError, combine, d) # need two args
2242 self.assertRaises(TypeError, combine, t, d) # args reversed
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002243 self.assertRaises(TypeError, combine, d, t, 1) # wrong tzinfo type
2244 self.assertRaises(TypeError, combine, d, t, 1, 2) # too many args
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002245 self.assertRaises(TypeError, combine, "date", "time") # wrong types
2246 self.assertRaises(TypeError, combine, d, "time") # wrong type
2247 self.assertRaises(TypeError, combine, "date", t) # wrong type
2248
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002249 # tzinfo= argument
2250 dt = combine(d, t, timezone.utc)
2251 self.assertIs(dt.tzinfo, timezone.utc)
2252 dt = combine(d, t, tzinfo=timezone.utc)
2253 self.assertIs(dt.tzinfo, timezone.utc)
2254 t = time()
2255 dt = combine(dt, t)
2256 self.assertEqual(dt.date(), d)
2257 self.assertEqual(dt.time(), t)
2258
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002259 def test_replace(self):
2260 cls = self.theclass
2261 args = [1, 2, 3, 4, 5, 6, 7]
2262 base = cls(*args)
2263 self.assertEqual(base, base.replace())
2264
2265 i = 0
2266 for name, newval in (("year", 2),
2267 ("month", 3),
2268 ("day", 4),
2269 ("hour", 5),
2270 ("minute", 6),
2271 ("second", 7),
2272 ("microsecond", 8)):
2273 newargs = args[:]
2274 newargs[i] = newval
2275 expected = cls(*newargs)
2276 got = base.replace(**{name: newval})
2277 self.assertEqual(expected, got)
2278 i += 1
2279
2280 # Out of bounds.
2281 base = cls(2000, 2, 29)
2282 self.assertRaises(ValueError, base.replace, year=2001)
2283
2284 def test_astimezone(self):
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002285 return # The rest is no longer applicable
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002286 # Pretty boring! The TZ test is more interesting here. astimezone()
2287 # simply can't be applied to a naive object.
2288 dt = self.theclass.now()
2289 f = FixedOffset(44, "")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04002290 self.assertRaises(ValueError, dt.astimezone) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002291 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
2292 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
2293 self.assertRaises(ValueError, dt.astimezone, f) # naive
2294 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
2295
2296 class Bogus(tzinfo):
2297 def utcoffset(self, dt): return None
2298 def dst(self, dt): return timedelta(0)
2299 bog = Bogus()
2300 self.assertRaises(ValueError, dt.astimezone, bog) # naive
2301 self.assertRaises(ValueError,
2302 dt.replace(tzinfo=bog).astimezone, f)
2303
2304 class AlsoBogus(tzinfo):
2305 def utcoffset(self, dt): return timedelta(0)
2306 def dst(self, dt): return None
2307 alsobog = AlsoBogus()
2308 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
2309
2310 def test_subclass_datetime(self):
2311
2312 class C(self.theclass):
2313 theAnswer = 42
2314
2315 def __new__(cls, *args, **kws):
2316 temp = kws.copy()
2317 extra = temp.pop('extra')
2318 result = self.theclass.__new__(cls, *args, **temp)
2319 result.extra = extra
2320 return result
2321
2322 def newmeth(self, start):
2323 return start + self.year + self.month + self.second
2324
2325 args = 2003, 4, 14, 12, 13, 41
2326
2327 dt1 = self.theclass(*args)
2328 dt2 = C(*args, **{'extra': 7})
2329
2330 self.assertEqual(dt2.__class__, C)
2331 self.assertEqual(dt2.theAnswer, 42)
2332 self.assertEqual(dt2.extra, 7)
2333 self.assertEqual(dt1.toordinal(), dt2.toordinal())
2334 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
2335 dt1.second - 7)
2336
2337class TestSubclassDateTime(TestDateTime):
2338 theclass = SubclassDatetime
2339 # Override tests not designed for subclass
Zachary Ware9fe6d862013-12-08 00:20:35 -06002340 @unittest.skip('not appropriate for subclasses')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002341 def test_roundtrip(self):
2342 pass
2343
2344class SubclassTime(time):
2345 sub_var = 1
2346
2347class TestTime(HarmlessMixedComparison, unittest.TestCase):
2348
2349 theclass = time
2350
2351 def test_basic_attributes(self):
2352 t = self.theclass(12, 0)
2353 self.assertEqual(t.hour, 12)
2354 self.assertEqual(t.minute, 0)
2355 self.assertEqual(t.second, 0)
2356 self.assertEqual(t.microsecond, 0)
2357
2358 def test_basic_attributes_nonzero(self):
2359 # Make sure all attributes are non-zero so bugs in
2360 # bit-shifting access show up.
2361 t = self.theclass(12, 59, 59, 8000)
2362 self.assertEqual(t.hour, 12)
2363 self.assertEqual(t.minute, 59)
2364 self.assertEqual(t.second, 59)
2365 self.assertEqual(t.microsecond, 8000)
2366
2367 def test_roundtrip(self):
2368 t = self.theclass(1, 2, 3, 4)
2369
2370 # Verify t -> string -> time identity.
2371 s = repr(t)
2372 self.assertTrue(s.startswith('datetime.'))
2373 s = s[9:]
2374 t2 = eval(s)
2375 self.assertEqual(t, t2)
2376
2377 # Verify identity via reconstructing from pieces.
2378 t2 = self.theclass(t.hour, t.minute, t.second,
2379 t.microsecond)
2380 self.assertEqual(t, t2)
2381
2382 def test_comparing(self):
2383 args = [1, 2, 3, 4]
2384 t1 = self.theclass(*args)
2385 t2 = self.theclass(*args)
2386 self.assertEqual(t1, t2)
2387 self.assertTrue(t1 <= t2)
2388 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002389 self.assertFalse(t1 != t2)
2390 self.assertFalse(t1 < t2)
2391 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002392
2393 for i in range(len(args)):
2394 newargs = args[:]
2395 newargs[i] = args[i] + 1
2396 t2 = self.theclass(*newargs) # this is larger than t1
2397 self.assertTrue(t1 < t2)
2398 self.assertTrue(t2 > t1)
2399 self.assertTrue(t1 <= t2)
2400 self.assertTrue(t2 >= t1)
2401 self.assertTrue(t1 != t2)
2402 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002403 self.assertFalse(t1 == t2)
2404 self.assertFalse(t2 == t1)
2405 self.assertFalse(t1 > t2)
2406 self.assertFalse(t2 < t1)
2407 self.assertFalse(t1 >= t2)
2408 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002409
2410 for badarg in OTHERSTUFF:
2411 self.assertEqual(t1 == badarg, False)
2412 self.assertEqual(t1 != badarg, True)
2413 self.assertEqual(badarg == t1, False)
2414 self.assertEqual(badarg != t1, True)
2415
2416 self.assertRaises(TypeError, lambda: t1 <= badarg)
2417 self.assertRaises(TypeError, lambda: t1 < badarg)
2418 self.assertRaises(TypeError, lambda: t1 > badarg)
2419 self.assertRaises(TypeError, lambda: t1 >= badarg)
2420 self.assertRaises(TypeError, lambda: badarg <= t1)
2421 self.assertRaises(TypeError, lambda: badarg < t1)
2422 self.assertRaises(TypeError, lambda: badarg > t1)
2423 self.assertRaises(TypeError, lambda: badarg >= t1)
2424
2425 def test_bad_constructor_arguments(self):
2426 # bad hours
2427 self.theclass(0, 0) # no exception
2428 self.theclass(23, 0) # no exception
2429 self.assertRaises(ValueError, self.theclass, -1, 0)
2430 self.assertRaises(ValueError, self.theclass, 24, 0)
2431 # bad minutes
2432 self.theclass(23, 0) # no exception
2433 self.theclass(23, 59) # no exception
2434 self.assertRaises(ValueError, self.theclass, 23, -1)
2435 self.assertRaises(ValueError, self.theclass, 23, 60)
2436 # bad seconds
2437 self.theclass(23, 59, 0) # no exception
2438 self.theclass(23, 59, 59) # no exception
2439 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
2440 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
2441 # bad microseconds
2442 self.theclass(23, 59, 59, 0) # no exception
2443 self.theclass(23, 59, 59, 999999) # no exception
2444 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
2445 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
2446
2447 def test_hash_equality(self):
2448 d = self.theclass(23, 30, 17)
2449 e = self.theclass(23, 30, 17)
2450 self.assertEqual(d, e)
2451 self.assertEqual(hash(d), hash(e))
2452
2453 dic = {d: 1}
2454 dic[e] = 2
2455 self.assertEqual(len(dic), 1)
2456 self.assertEqual(dic[d], 2)
2457 self.assertEqual(dic[e], 2)
2458
2459 d = self.theclass(0, 5, 17)
2460 e = self.theclass(0, 5, 17)
2461 self.assertEqual(d, e)
2462 self.assertEqual(hash(d), hash(e))
2463
2464 dic = {d: 1}
2465 dic[e] = 2
2466 self.assertEqual(len(dic), 1)
2467 self.assertEqual(dic[d], 2)
2468 self.assertEqual(dic[e], 2)
2469
2470 def test_isoformat(self):
2471 t = self.theclass(4, 5, 1, 123)
2472 self.assertEqual(t.isoformat(), "04:05:01.000123")
2473 self.assertEqual(t.isoformat(), str(t))
2474
2475 t = self.theclass()
2476 self.assertEqual(t.isoformat(), "00:00:00")
2477 self.assertEqual(t.isoformat(), str(t))
2478
2479 t = self.theclass(microsecond=1)
2480 self.assertEqual(t.isoformat(), "00:00:00.000001")
2481 self.assertEqual(t.isoformat(), str(t))
2482
2483 t = self.theclass(microsecond=10)
2484 self.assertEqual(t.isoformat(), "00:00:00.000010")
2485 self.assertEqual(t.isoformat(), str(t))
2486
2487 t = self.theclass(microsecond=100)
2488 self.assertEqual(t.isoformat(), "00:00:00.000100")
2489 self.assertEqual(t.isoformat(), str(t))
2490
2491 t = self.theclass(microsecond=1000)
2492 self.assertEqual(t.isoformat(), "00:00:00.001000")
2493 self.assertEqual(t.isoformat(), str(t))
2494
2495 t = self.theclass(microsecond=10000)
2496 self.assertEqual(t.isoformat(), "00:00:00.010000")
2497 self.assertEqual(t.isoformat(), str(t))
2498
2499 t = self.theclass(microsecond=100000)
2500 self.assertEqual(t.isoformat(), "00:00:00.100000")
2501 self.assertEqual(t.isoformat(), str(t))
2502
Alexander Belopolskya2998a62016-03-06 14:58:43 -05002503 t = self.theclass(hour=12, minute=34, second=56, microsecond=123456)
2504 self.assertEqual(t.isoformat(timespec='hours'), "12")
2505 self.assertEqual(t.isoformat(timespec='minutes'), "12:34")
2506 self.assertEqual(t.isoformat(timespec='seconds'), "12:34:56")
2507 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.123")
2508 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.123456")
2509 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56.123456")
2510 self.assertRaises(ValueError, t.isoformat, timespec='monkey')
2511
2512 t = self.theclass(hour=12, minute=34, second=56, microsecond=999500)
2513 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.999")
2514
2515 t = self.theclass(hour=12, minute=34, second=56, microsecond=0)
2516 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.000")
2517 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.000000")
2518 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56")
2519
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002520 def test_1653736(self):
2521 # verify it doesn't accept extra keyword arguments
2522 t = self.theclass(second=1)
2523 self.assertRaises(TypeError, t.isoformat, foo=3)
2524
2525 def test_strftime(self):
2526 t = self.theclass(1, 2, 3, 4)
2527 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
2528 # A naive object replaces %z and %Z with empty strings.
2529 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
2530
2531 def test_format(self):
2532 t = self.theclass(1, 2, 3, 4)
2533 self.assertEqual(t.__format__(''), str(t))
2534
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02002535 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002536 t.__format__(123)
2537
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002538 # check that a derived class's __str__() gets called
2539 class A(self.theclass):
2540 def __str__(self):
2541 return 'A'
2542 a = A(1, 2, 3, 4)
2543 self.assertEqual(a.__format__(''), 'A')
2544
2545 # check that a derived class's strftime gets called
2546 class B(self.theclass):
2547 def strftime(self, format_spec):
2548 return 'B'
2549 b = B(1, 2, 3, 4)
2550 self.assertEqual(b.__format__(''), str(t))
2551
2552 for fmt in ['%H %M %S',
2553 ]:
2554 self.assertEqual(t.__format__(fmt), t.strftime(fmt))
2555 self.assertEqual(a.__format__(fmt), t.strftime(fmt))
2556 self.assertEqual(b.__format__(fmt), 'B')
2557
2558 def test_str(self):
2559 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
2560 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
2561 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
2562 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
2563 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
2564
2565 def test_repr(self):
2566 name = 'datetime.' + self.theclass.__name__
2567 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
2568 "%s(1, 2, 3, 4)" % name)
2569 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
2570 "%s(10, 2, 3, 4000)" % name)
2571 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
2572 "%s(0, 2, 3, 400000)" % name)
2573 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
2574 "%s(12, 2, 3)" % name)
2575 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
2576 "%s(23, 15)" % name)
2577
2578 def test_resolution_info(self):
2579 self.assertIsInstance(self.theclass.min, self.theclass)
2580 self.assertIsInstance(self.theclass.max, self.theclass)
2581 self.assertIsInstance(self.theclass.resolution, timedelta)
2582 self.assertTrue(self.theclass.max > self.theclass.min)
2583
2584 def test_pickling(self):
2585 args = 20, 59, 16, 64**2
2586 orig = self.theclass(*args)
2587 for pickler, unpickler, proto in pickle_choices:
2588 green = pickler.dumps(orig, proto)
2589 derived = unpickler.loads(green)
2590 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02002591 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002592
2593 def test_pickling_subclass_time(self):
2594 args = 20, 59, 16, 64**2
2595 orig = SubclassTime(*args)
2596 for pickler, unpickler, proto in pickle_choices:
2597 green = pickler.dumps(orig, proto)
2598 derived = unpickler.loads(green)
2599 self.assertEqual(orig, derived)
2600
2601 def test_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002602 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002603 cls = self.theclass
2604 self.assertTrue(cls(1))
2605 self.assertTrue(cls(0, 1))
2606 self.assertTrue(cls(0, 0, 1))
2607 self.assertTrue(cls(0, 0, 0, 1))
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002608 self.assertTrue(cls(0))
2609 self.assertTrue(cls())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002610
2611 def test_replace(self):
2612 cls = self.theclass
2613 args = [1, 2, 3, 4]
2614 base = cls(*args)
2615 self.assertEqual(base, base.replace())
2616
2617 i = 0
2618 for name, newval in (("hour", 5),
2619 ("minute", 6),
2620 ("second", 7),
2621 ("microsecond", 8)):
2622 newargs = args[:]
2623 newargs[i] = newval
2624 expected = cls(*newargs)
2625 got = base.replace(**{name: newval})
2626 self.assertEqual(expected, got)
2627 i += 1
2628
2629 # Out of bounds.
2630 base = cls(1)
2631 self.assertRaises(ValueError, base.replace, hour=24)
2632 self.assertRaises(ValueError, base.replace, minute=-1)
2633 self.assertRaises(ValueError, base.replace, second=100)
2634 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2635
Paul Ganssle191e9932017-11-09 16:34:29 -05002636 def test_subclass_replace(self):
2637 class TimeSubclass(self.theclass):
2638 pass
2639
2640 ctime = TimeSubclass(12, 30)
2641 self.assertIs(type(ctime.replace(hour=10)), TimeSubclass)
2642
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002643 def test_subclass_time(self):
2644
2645 class C(self.theclass):
2646 theAnswer = 42
2647
2648 def __new__(cls, *args, **kws):
2649 temp = kws.copy()
2650 extra = temp.pop('extra')
2651 result = self.theclass.__new__(cls, *args, **temp)
2652 result.extra = extra
2653 return result
2654
2655 def newmeth(self, start):
2656 return start + self.hour + self.second
2657
2658 args = 4, 5, 6
2659
2660 dt1 = self.theclass(*args)
2661 dt2 = C(*args, **{'extra': 7})
2662
2663 self.assertEqual(dt2.__class__, C)
2664 self.assertEqual(dt2.theAnswer, 42)
2665 self.assertEqual(dt2.extra, 7)
2666 self.assertEqual(dt1.isoformat(), dt2.isoformat())
2667 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2668
2669 def test_backdoor_resistance(self):
2670 # see TestDate.test_backdoor_resistance().
2671 base = '2:59.0'
2672 for hour_byte in ' ', '9', chr(24), '\xff':
2673 self.assertRaises(TypeError, self.theclass,
2674 hour_byte + base[1:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002675 # Good bytes, but bad tzinfo:
2676 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
2677 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002678
2679# A mixin for classes with a tzinfo= argument. Subclasses must define
Martin Panter46f50722016-05-26 05:35:26 +00002680# theclass as a class attribute, and theclass(1, 1, 1, tzinfo=whatever)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002681# must be legit (which is true for time and datetime).
2682class TZInfoBase:
2683
2684 def test_argument_passing(self):
2685 cls = self.theclass
2686 # A datetime passes itself on, a time passes None.
2687 class introspective(tzinfo):
2688 def tzname(self, dt): return dt and "real" or "none"
2689 def utcoffset(self, dt):
2690 return timedelta(minutes = dt and 42 or -42)
2691 dst = utcoffset
2692
2693 obj = cls(1, 2, 3, tzinfo=introspective())
2694
2695 expected = cls is time and "none" or "real"
2696 self.assertEqual(obj.tzname(), expected)
2697
2698 expected = timedelta(minutes=(cls is time and -42 or 42))
2699 self.assertEqual(obj.utcoffset(), expected)
2700 self.assertEqual(obj.dst(), expected)
2701
2702 def test_bad_tzinfo_classes(self):
2703 cls = self.theclass
2704 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
2705
2706 class NiceTry(object):
2707 def __init__(self): pass
2708 def utcoffset(self, dt): pass
2709 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
2710
2711 class BetterTry(tzinfo):
2712 def __init__(self): pass
2713 def utcoffset(self, dt): pass
2714 b = BetterTry()
2715 t = cls(1, 1, 1, tzinfo=b)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002716 self.assertIs(t.tzinfo, b)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002717
2718 def test_utc_offset_out_of_bounds(self):
2719 class Edgy(tzinfo):
2720 def __init__(self, offset):
2721 self.offset = timedelta(minutes=offset)
2722 def utcoffset(self, dt):
2723 return self.offset
2724
2725 cls = self.theclass
2726 for offset, legit in ((-1440, False),
2727 (-1439, True),
2728 (1439, True),
2729 (1440, False)):
2730 if cls is time:
2731 t = cls(1, 2, 3, tzinfo=Edgy(offset))
2732 elif cls is datetime:
2733 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
2734 else:
2735 assert 0, "impossible"
2736 if legit:
2737 aofs = abs(offset)
2738 h, m = divmod(aofs, 60)
2739 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
2740 if isinstance(t, datetime):
2741 t = t.timetz()
2742 self.assertEqual(str(t), "01:02:03" + tag)
2743 else:
2744 self.assertRaises(ValueError, str, t)
2745
2746 def test_tzinfo_classes(self):
2747 cls = self.theclass
2748 class C1(tzinfo):
2749 def utcoffset(self, dt): return None
2750 def dst(self, dt): return None
2751 def tzname(self, dt): return None
2752 for t in (cls(1, 1, 1),
2753 cls(1, 1, 1, tzinfo=None),
2754 cls(1, 1, 1, tzinfo=C1())):
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002755 self.assertIsNone(t.utcoffset())
2756 self.assertIsNone(t.dst())
2757 self.assertIsNone(t.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002758
2759 class C3(tzinfo):
2760 def utcoffset(self, dt): return timedelta(minutes=-1439)
2761 def dst(self, dt): return timedelta(minutes=1439)
2762 def tzname(self, dt): return "aname"
2763 t = cls(1, 1, 1, tzinfo=C3())
2764 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
2765 self.assertEqual(t.dst(), timedelta(minutes=1439))
2766 self.assertEqual(t.tzname(), "aname")
2767
2768 # Wrong types.
2769 class C4(tzinfo):
2770 def utcoffset(self, dt): return "aname"
2771 def dst(self, dt): return 7
2772 def tzname(self, dt): return 0
2773 t = cls(1, 1, 1, tzinfo=C4())
2774 self.assertRaises(TypeError, t.utcoffset)
2775 self.assertRaises(TypeError, t.dst)
2776 self.assertRaises(TypeError, t.tzname)
2777
2778 # Offset out of range.
2779 class C6(tzinfo):
2780 def utcoffset(self, dt): return timedelta(hours=-24)
2781 def dst(self, dt): return timedelta(hours=24)
2782 t = cls(1, 1, 1, tzinfo=C6())
2783 self.assertRaises(ValueError, t.utcoffset)
2784 self.assertRaises(ValueError, t.dst)
2785
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002786 # Not a whole number of seconds.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002787 class C7(tzinfo):
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002788 def utcoffset(self, dt): return timedelta(microseconds=61)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002789 def dst(self, dt): return timedelta(microseconds=-81)
2790 t = cls(1, 1, 1, tzinfo=C7())
Alexander Belopolsky018d3532017-07-31 10:26:50 -04002791 self.assertEqual(t.utcoffset(), timedelta(microseconds=61))
2792 self.assertEqual(t.dst(), timedelta(microseconds=-81))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002793
2794 def test_aware_compare(self):
2795 cls = self.theclass
2796
2797 # Ensure that utcoffset() gets ignored if the comparands have
2798 # the same tzinfo member.
2799 class OperandDependentOffset(tzinfo):
2800 def utcoffset(self, t):
2801 if t.minute < 10:
2802 # d0 and d1 equal after adjustment
2803 return timedelta(minutes=t.minute)
2804 else:
2805 # d2 off in the weeds
2806 return timedelta(minutes=59)
2807
2808 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
2809 d0 = base.replace(minute=3)
2810 d1 = base.replace(minute=9)
2811 d2 = base.replace(minute=11)
2812 for x in d0, d1, d2:
2813 for y in d0, d1, d2:
2814 for op in lt, le, gt, ge, eq, ne:
2815 got = op(x, y)
2816 expected = op(x.minute, y.minute)
2817 self.assertEqual(got, expected)
2818
2819 # However, if they're different members, uctoffset is not ignored.
2820 # Note that a time can't actually have an operand-depedent offset,
2821 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
2822 # so skip this test for time.
2823 if cls is not time:
2824 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2825 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2826 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2827 for x in d0, d1, d2:
2828 for y in d0, d1, d2:
2829 got = (x > y) - (x < y)
2830 if (x is d0 or x is d1) and (y is d0 or y is d1):
2831 expected = 0
2832 elif x is y is d2:
2833 expected = 0
2834 elif x is d2:
2835 expected = -1
2836 else:
2837 assert y is d2
2838 expected = 1
2839 self.assertEqual(got, expected)
2840
2841
2842# Testing time objects with a non-None tzinfo.
2843class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
2844 theclass = time
2845
2846 def test_empty(self):
2847 t = self.theclass()
2848 self.assertEqual(t.hour, 0)
2849 self.assertEqual(t.minute, 0)
2850 self.assertEqual(t.second, 0)
2851 self.assertEqual(t.microsecond, 0)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002852 self.assertIsNone(t.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002853
2854 def test_zones(self):
2855 est = FixedOffset(-300, "EST", 1)
2856 utc = FixedOffset(0, "UTC", -2)
2857 met = FixedOffset(60, "MET", 3)
2858 t1 = time( 7, 47, tzinfo=est)
2859 t2 = time(12, 47, tzinfo=utc)
2860 t3 = time(13, 47, tzinfo=met)
2861 t4 = time(microsecond=40)
2862 t5 = time(microsecond=40, tzinfo=utc)
2863
2864 self.assertEqual(t1.tzinfo, est)
2865 self.assertEqual(t2.tzinfo, utc)
2866 self.assertEqual(t3.tzinfo, met)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002867 self.assertIsNone(t4.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002868 self.assertEqual(t5.tzinfo, utc)
2869
2870 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2871 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2872 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002873 self.assertIsNone(t4.utcoffset())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002874 self.assertRaises(TypeError, t1.utcoffset, "no args")
2875
2876 self.assertEqual(t1.tzname(), "EST")
2877 self.assertEqual(t2.tzname(), "UTC")
2878 self.assertEqual(t3.tzname(), "MET")
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002879 self.assertIsNone(t4.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002880 self.assertRaises(TypeError, t1.tzname, "no args")
2881
2882 self.assertEqual(t1.dst(), timedelta(minutes=1))
2883 self.assertEqual(t2.dst(), timedelta(minutes=-2))
2884 self.assertEqual(t3.dst(), timedelta(minutes=3))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002885 self.assertIsNone(t4.dst())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002886 self.assertRaises(TypeError, t1.dst, "no args")
2887
2888 self.assertEqual(hash(t1), hash(t2))
2889 self.assertEqual(hash(t1), hash(t3))
2890 self.assertEqual(hash(t2), hash(t3))
2891
2892 self.assertEqual(t1, t2)
2893 self.assertEqual(t1, t3)
2894 self.assertEqual(t2, t3)
Alexander Belopolsky08313822012-06-15 20:19:47 -04002895 self.assertNotEqual(t4, t5) # mixed tz-aware & naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002896 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
2897 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
2898
2899 self.assertEqual(str(t1), "07:47:00-05:00")
2900 self.assertEqual(str(t2), "12:47:00+00:00")
2901 self.assertEqual(str(t3), "13:47:00+01:00")
2902 self.assertEqual(str(t4), "00:00:00.000040")
2903 self.assertEqual(str(t5), "00:00:00.000040+00:00")
2904
2905 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
2906 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
2907 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
2908 self.assertEqual(t4.isoformat(), "00:00:00.000040")
2909 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
2910
2911 d = 'datetime.time'
2912 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
2913 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
2914 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
2915 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
2916 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
2917
2918 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
2919 "07:47:00 %Z=EST %z=-0500")
2920 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
2921 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
2922
2923 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
2924 t1 = time(23, 59, tzinfo=yuck)
2925 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
2926 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
2927
2928 # Check that an invalid tzname result raises an exception.
2929 class Badtzname(tzinfo):
Alexander Belopolskye239d232010-12-08 23:31:48 +00002930 tz = 42
2931 def tzname(self, dt): return self.tz
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002932 t = time(2, 3, 4, tzinfo=Badtzname())
2933 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
2934 self.assertRaises(TypeError, t.strftime, "%Z")
2935
Alexander Belopolskye239d232010-12-08 23:31:48 +00002936 # Issue #6697:
Utkarsh Upadhyay287c5592017-07-21 02:14:54 +02002937 if '_Fast' in self.__class__.__name__:
Alexander Belopolskye239d232010-12-08 23:31:48 +00002938 Badtzname.tz = '\ud800'
2939 self.assertRaises(ValueError, t.strftime, "%Z")
2940
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002941 def test_hash_edge_cases(self):
2942 # Offsets that overflow a basic time.
2943 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2944 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2945 self.assertEqual(hash(t1), hash(t2))
2946
2947 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2948 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2949 self.assertEqual(hash(t1), hash(t2))
2950
2951 def test_pickling(self):
2952 # Try one without a tzinfo.
2953 args = 20, 59, 16, 64**2
2954 orig = self.theclass(*args)
2955 for pickler, unpickler, proto in pickle_choices:
2956 green = pickler.dumps(orig, proto)
2957 derived = unpickler.loads(green)
2958 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02002959 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002960
2961 # Try one with a tzinfo.
2962 tinfo = PicklableFixedOffset(-300, 'cookie')
2963 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
2964 for pickler, unpickler, proto in pickle_choices:
2965 green = pickler.dumps(orig, proto)
2966 derived = unpickler.loads(green)
2967 self.assertEqual(orig, derived)
2968 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
2969 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2970 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02002971 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002972
2973 def test_more_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002974 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002975 cls = self.theclass
2976
2977 t = cls(0, tzinfo=FixedOffset(-300, ""))
2978 self.assertTrue(t)
2979
2980 t = cls(5, tzinfo=FixedOffset(-300, ""))
2981 self.assertTrue(t)
2982
2983 t = cls(5, tzinfo=FixedOffset(300, ""))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002984 self.assertTrue(t)
2985
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002986 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2987 self.assertTrue(t)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002988
2989 def test_replace(self):
2990 cls = self.theclass
2991 z100 = FixedOffset(100, "+100")
2992 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2993 args = [1, 2, 3, 4, z100]
2994 base = cls(*args)
2995 self.assertEqual(base, base.replace())
2996
2997 i = 0
2998 for name, newval in (("hour", 5),
2999 ("minute", 6),
3000 ("second", 7),
3001 ("microsecond", 8),
3002 ("tzinfo", zm200)):
3003 newargs = args[:]
3004 newargs[i] = newval
3005 expected = cls(*newargs)
3006 got = base.replace(**{name: newval})
3007 self.assertEqual(expected, got)
3008 i += 1
3009
3010 # Ensure we can get rid of a tzinfo.
3011 self.assertEqual(base.tzname(), "+100")
3012 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003013 self.assertIsNone(base2.tzinfo)
3014 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003015
3016 # Ensure we can add one.
3017 base3 = base2.replace(tzinfo=z100)
3018 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003019 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003020
3021 # Out of bounds.
3022 base = cls(1)
3023 self.assertRaises(ValueError, base.replace, hour=24)
3024 self.assertRaises(ValueError, base.replace, minute=-1)
3025 self.assertRaises(ValueError, base.replace, second=100)
3026 self.assertRaises(ValueError, base.replace, microsecond=1000000)
3027
3028 def test_mixed_compare(self):
3029 t1 = time(1, 2, 3)
3030 t2 = time(1, 2, 3)
3031 self.assertEqual(t1, t2)
3032 t2 = t2.replace(tzinfo=None)
3033 self.assertEqual(t1, t2)
3034 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3035 self.assertEqual(t1, t2)
3036 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04003037 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003038
3039 # In time w/ identical tzinfo objects, utcoffset is ignored.
3040 class Varies(tzinfo):
3041 def __init__(self):
3042 self.offset = timedelta(minutes=22)
3043 def utcoffset(self, t):
3044 self.offset += timedelta(minutes=1)
3045 return self.offset
3046
3047 v = Varies()
3048 t1 = t2.replace(tzinfo=v)
3049 t2 = t2.replace(tzinfo=v)
3050 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3051 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3052 self.assertEqual(t1, t2)
3053
3054 # But if they're not identical, it isn't ignored.
3055 t2 = t2.replace(tzinfo=Varies())
3056 self.assertTrue(t1 < t2) # t1's offset counter still going up
3057
3058 def test_subclass_timetz(self):
3059
3060 class C(self.theclass):
3061 theAnswer = 42
3062
3063 def __new__(cls, *args, **kws):
3064 temp = kws.copy()
3065 extra = temp.pop('extra')
3066 result = self.theclass.__new__(cls, *args, **temp)
3067 result.extra = extra
3068 return result
3069
3070 def newmeth(self, start):
3071 return start + self.hour + self.second
3072
3073 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3074
3075 dt1 = self.theclass(*args)
3076 dt2 = C(*args, **{'extra': 7})
3077
3078 self.assertEqual(dt2.__class__, C)
3079 self.assertEqual(dt2.theAnswer, 42)
3080 self.assertEqual(dt2.extra, 7)
3081 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3082 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
3083
3084
3085# Testing datetime objects with a non-None tzinfo.
3086
3087class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
3088 theclass = datetime
3089
3090 def test_trivial(self):
3091 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
3092 self.assertEqual(dt.year, 1)
3093 self.assertEqual(dt.month, 2)
3094 self.assertEqual(dt.day, 3)
3095 self.assertEqual(dt.hour, 4)
3096 self.assertEqual(dt.minute, 5)
3097 self.assertEqual(dt.second, 6)
3098 self.assertEqual(dt.microsecond, 7)
3099 self.assertEqual(dt.tzinfo, None)
3100
3101 def test_even_more_compare(self):
3102 # The test_compare() and test_more_compare() inherited from TestDate
3103 # and TestDateTime covered non-tzinfo cases.
3104
3105 # Smallest possible after UTC adjustment.
3106 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
3107 # Largest possible after UTC adjustment.
3108 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3109 tzinfo=FixedOffset(-1439, ""))
3110
3111 # Make sure those compare correctly, and w/o overflow.
3112 self.assertTrue(t1 < t2)
3113 self.assertTrue(t1 != t2)
3114 self.assertTrue(t2 > t1)
3115
3116 self.assertEqual(t1, t1)
3117 self.assertEqual(t2, t2)
3118
3119 # Equal afer adjustment.
3120 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
3121 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
3122 self.assertEqual(t1, t2)
3123
3124 # Change t1 not to subtract a minute, and t1 should be larger.
3125 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
3126 self.assertTrue(t1 > t2)
3127
3128 # Change t1 to subtract 2 minutes, and t1 should be smaller.
3129 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
3130 self.assertTrue(t1 < t2)
3131
3132 # Back to the original t1, but make seconds resolve it.
3133 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3134 second=1)
3135 self.assertTrue(t1 > t2)
3136
3137 # Likewise, but make microseconds resolve it.
3138 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3139 microsecond=1)
3140 self.assertTrue(t1 > t2)
3141
Alexander Belopolsky08313822012-06-15 20:19:47 -04003142 # Make t2 naive and it should differ.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003143 t2 = self.theclass.min
Alexander Belopolsky08313822012-06-15 20:19:47 -04003144 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003145 self.assertEqual(t2, t2)
3146
3147 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
3148 class Naive(tzinfo):
3149 def utcoffset(self, dt): return None
3150 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
Alexander Belopolsky08313822012-06-15 20:19:47 -04003151 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003152 self.assertEqual(t2, t2)
3153
3154 # OTOH, it's OK to compare two of these mixing the two ways of being
3155 # naive.
3156 t1 = self.theclass(5, 6, 7)
3157 self.assertEqual(t1, t2)
3158
3159 # Try a bogus uctoffset.
3160 class Bogus(tzinfo):
3161 def utcoffset(self, dt):
3162 return timedelta(minutes=1440) # out of bounds
3163 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
3164 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
3165 self.assertRaises(ValueError, lambda: t1 == t2)
3166
3167 def test_pickling(self):
3168 # Try one without a tzinfo.
3169 args = 6, 7, 23, 20, 59, 1, 64**2
3170 orig = self.theclass(*args)
3171 for pickler, unpickler, proto in pickle_choices:
3172 green = pickler.dumps(orig, proto)
3173 derived = unpickler.loads(green)
3174 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003175 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003176
3177 # Try one with a tzinfo.
3178 tinfo = PicklableFixedOffset(-300, 'cookie')
3179 orig = self.theclass(*args, **{'tzinfo': tinfo})
3180 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
3181 for pickler, unpickler, proto in pickle_choices:
3182 green = pickler.dumps(orig, proto)
3183 derived = unpickler.loads(green)
3184 self.assertEqual(orig, derived)
3185 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3186 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3187 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003188 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003189
3190 def test_extreme_hashes(self):
3191 # If an attempt is made to hash these via subtracting the offset
3192 # then hashing a datetime object, OverflowError results. The
3193 # Python implementation used to blow up here.
3194 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
3195 hash(t)
3196 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3197 tzinfo=FixedOffset(-1439, ""))
3198 hash(t)
3199
3200 # OTOH, an OOB offset should blow up.
3201 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
3202 self.assertRaises(ValueError, hash, t)
3203
3204 def test_zones(self):
3205 est = FixedOffset(-300, "EST")
3206 utc = FixedOffset(0, "UTC")
3207 met = FixedOffset(60, "MET")
3208 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
3209 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
3210 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
3211 self.assertEqual(t1.tzinfo, est)
3212 self.assertEqual(t2.tzinfo, utc)
3213 self.assertEqual(t3.tzinfo, met)
3214 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
3215 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
3216 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
3217 self.assertEqual(t1.tzname(), "EST")
3218 self.assertEqual(t2.tzname(), "UTC")
3219 self.assertEqual(t3.tzname(), "MET")
3220 self.assertEqual(hash(t1), hash(t2))
3221 self.assertEqual(hash(t1), hash(t3))
3222 self.assertEqual(hash(t2), hash(t3))
3223 self.assertEqual(t1, t2)
3224 self.assertEqual(t1, t3)
3225 self.assertEqual(t2, t3)
3226 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
3227 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
3228 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
3229 d = 'datetime.datetime(2002, 3, 19, '
3230 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
3231 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
3232 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
3233
3234 def test_combine(self):
3235 met = FixedOffset(60, "MET")
3236 d = date(2002, 3, 4)
3237 tz = time(18, 45, 3, 1234, tzinfo=met)
3238 dt = datetime.combine(d, tz)
3239 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
3240 tzinfo=met))
3241
3242 def test_extract(self):
3243 met = FixedOffset(60, "MET")
3244 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
3245 self.assertEqual(dt.date(), date(2002, 3, 4))
3246 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
3247 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
3248
3249 def test_tz_aware_arithmetic(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003250 now = self.theclass.now()
3251 tz55 = FixedOffset(-330, "west 5:30")
3252 timeaware = now.time().replace(tzinfo=tz55)
3253 nowaware = self.theclass.combine(now.date(), timeaware)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003254 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003255 self.assertEqual(nowaware.timetz(), timeaware)
3256
3257 # Can't mix aware and non-aware.
3258 self.assertRaises(TypeError, lambda: now - nowaware)
3259 self.assertRaises(TypeError, lambda: nowaware - now)
3260
3261 # And adding datetime's doesn't make sense, aware or not.
3262 self.assertRaises(TypeError, lambda: now + nowaware)
3263 self.assertRaises(TypeError, lambda: nowaware + now)
3264 self.assertRaises(TypeError, lambda: nowaware + nowaware)
3265
3266 # Subtracting should yield 0.
3267 self.assertEqual(now - now, timedelta(0))
3268 self.assertEqual(nowaware - nowaware, timedelta(0))
3269
3270 # Adding a delta should preserve tzinfo.
3271 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
3272 nowawareplus = nowaware + delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003273 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003274 nowawareplus2 = delta + nowaware
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003275 self.assertIs(nowawareplus2.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003276 self.assertEqual(nowawareplus, nowawareplus2)
3277
3278 # that - delta should be what we started with, and that - what we
3279 # started with should be delta.
3280 diff = nowawareplus - delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003281 self.assertIs(diff.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003282 self.assertEqual(nowaware, diff)
3283 self.assertRaises(TypeError, lambda: delta - nowawareplus)
3284 self.assertEqual(nowawareplus - nowaware, delta)
3285
3286 # Make up a random timezone.
3287 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
3288 # Attach it to nowawareplus.
3289 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003290 self.assertIs(nowawareplus.tzinfo, tzr)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003291 # Make sure the difference takes the timezone adjustments into account.
3292 got = nowaware - nowawareplus
3293 # Expected: (nowaware base - nowaware offset) -
3294 # (nowawareplus base - nowawareplus offset) =
3295 # (nowaware base - nowawareplus base) +
3296 # (nowawareplus offset - nowaware offset) =
3297 # -delta + nowawareplus offset - nowaware offset
3298 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
3299 self.assertEqual(got, expected)
3300
3301 # Try max possible difference.
3302 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
3303 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3304 tzinfo=FixedOffset(-1439, "max"))
3305 maxdiff = max - min
3306 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
3307 timedelta(minutes=2*1439))
3308 # Different tzinfo, but the same offset
3309 tza = timezone(HOUR, 'A')
3310 tzb = timezone(HOUR, 'B')
3311 delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
3312 self.assertEqual(delta, self.theclass.min - self.theclass.max)
3313
3314 def test_tzinfo_now(self):
3315 meth = self.theclass.now
3316 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3317 base = meth()
3318 # Try with and without naming the keyword.
3319 off42 = FixedOffset(42, "42")
3320 another = meth(off42)
3321 again = meth(tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003322 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003323 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3324 # Bad argument with and w/o naming the keyword.
3325 self.assertRaises(TypeError, meth, 16)
3326 self.assertRaises(TypeError, meth, tzinfo=16)
3327 # Bad keyword name.
3328 self.assertRaises(TypeError, meth, tinfo=off42)
3329 # Too many args.
3330 self.assertRaises(TypeError, meth, off42, off42)
3331
3332 # We don't know which time zone we're in, and don't have a tzinfo
3333 # class to represent it, so seeing whether a tz argument actually
3334 # does a conversion is tricky.
3335 utc = FixedOffset(0, "utc", 0)
3336 for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
3337 timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
3338 for dummy in range(3):
3339 now = datetime.now(weirdtz)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003340 self.assertIs(now.tzinfo, weirdtz)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003341 utcnow = datetime.utcnow().replace(tzinfo=utc)
3342 now2 = utcnow.astimezone(weirdtz)
3343 if abs(now - now2) < timedelta(seconds=30):
3344 break
3345 # Else the code is broken, or more than 30 seconds passed between
3346 # calls; assuming the latter, just try again.
3347 else:
3348 # Three strikes and we're out.
3349 self.fail("utcnow(), now(tz), or astimezone() may be broken")
3350
3351 def test_tzinfo_fromtimestamp(self):
3352 import time
3353 meth = self.theclass.fromtimestamp
3354 ts = time.time()
3355 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3356 base = meth(ts)
3357 # Try with and without naming the keyword.
3358 off42 = FixedOffset(42, "42")
3359 another = meth(ts, off42)
3360 again = meth(ts, tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003361 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003362 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3363 # Bad argument with and w/o naming the keyword.
3364 self.assertRaises(TypeError, meth, ts, 16)
3365 self.assertRaises(TypeError, meth, ts, tzinfo=16)
3366 # Bad keyword name.
3367 self.assertRaises(TypeError, meth, ts, tinfo=off42)
3368 # Too many args.
3369 self.assertRaises(TypeError, meth, ts, off42, off42)
3370 # Too few args.
3371 self.assertRaises(TypeError, meth)
3372
3373 # Try to make sure tz= actually does some conversion.
3374 timestamp = 1000000000
3375 utcdatetime = datetime.utcfromtimestamp(timestamp)
3376 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
3377 # But on some flavor of Mac, it's nowhere near that. So we can't have
3378 # any idea here what time that actually is, we can only test that
3379 # relative changes match.
3380 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
3381 tz = FixedOffset(utcoffset, "tz", 0)
3382 expected = utcdatetime + utcoffset
3383 got = datetime.fromtimestamp(timestamp, tz)
3384 self.assertEqual(expected, got.replace(tzinfo=None))
3385
3386 def test_tzinfo_utcnow(self):
3387 meth = self.theclass.utcnow
3388 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3389 base = meth()
3390 # Try with and without naming the keyword; for whatever reason,
3391 # utcnow() doesn't accept a tzinfo argument.
3392 off42 = FixedOffset(42, "42")
3393 self.assertRaises(TypeError, meth, off42)
3394 self.assertRaises(TypeError, meth, tzinfo=off42)
3395
3396 def test_tzinfo_utcfromtimestamp(self):
3397 import time
3398 meth = self.theclass.utcfromtimestamp
3399 ts = time.time()
3400 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3401 base = meth(ts)
3402 # Try with and without naming the keyword; for whatever reason,
3403 # utcfromtimestamp() doesn't accept a tzinfo argument.
3404 off42 = FixedOffset(42, "42")
3405 self.assertRaises(TypeError, meth, ts, off42)
3406 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
3407
3408 def test_tzinfo_timetuple(self):
3409 # TestDateTime tested most of this. datetime adds a twist to the
3410 # DST flag.
3411 class DST(tzinfo):
3412 def __init__(self, dstvalue):
3413 if isinstance(dstvalue, int):
3414 dstvalue = timedelta(minutes=dstvalue)
3415 self.dstvalue = dstvalue
3416 def dst(self, dt):
3417 return self.dstvalue
3418
3419 cls = self.theclass
3420 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
3421 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
3422 t = d.timetuple()
3423 self.assertEqual(1, t.tm_year)
3424 self.assertEqual(1, t.tm_mon)
3425 self.assertEqual(1, t.tm_mday)
3426 self.assertEqual(10, t.tm_hour)
3427 self.assertEqual(20, t.tm_min)
3428 self.assertEqual(30, t.tm_sec)
3429 self.assertEqual(0, t.tm_wday)
3430 self.assertEqual(1, t.tm_yday)
3431 self.assertEqual(flag, t.tm_isdst)
3432
3433 # dst() returns wrong type.
3434 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
3435
3436 # dst() at the edge.
3437 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
3438 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
3439
3440 # dst() out of range.
3441 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
3442 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
3443
3444 def test_utctimetuple(self):
3445 class DST(tzinfo):
3446 def __init__(self, dstvalue=0):
3447 if isinstance(dstvalue, int):
3448 dstvalue = timedelta(minutes=dstvalue)
3449 self.dstvalue = dstvalue
3450 def dst(self, dt):
3451 return self.dstvalue
3452
3453 cls = self.theclass
3454 # This can't work: DST didn't implement utcoffset.
3455 self.assertRaises(NotImplementedError,
3456 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
3457
3458 class UOFS(DST):
3459 def __init__(self, uofs, dofs=None):
3460 DST.__init__(self, dofs)
3461 self.uofs = timedelta(minutes=uofs)
3462 def utcoffset(self, dt):
3463 return self.uofs
3464
3465 for dstvalue in -33, 33, 0, None:
3466 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
3467 t = d.utctimetuple()
3468 self.assertEqual(d.year, t.tm_year)
3469 self.assertEqual(d.month, t.tm_mon)
3470 self.assertEqual(d.day, t.tm_mday)
3471 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
3472 self.assertEqual(13, t.tm_min)
3473 self.assertEqual(d.second, t.tm_sec)
3474 self.assertEqual(d.weekday(), t.tm_wday)
3475 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
3476 t.tm_yday)
3477 # Ensure tm_isdst is 0 regardless of what dst() says: DST
3478 # is never in effect for a UTC time.
3479 self.assertEqual(0, t.tm_isdst)
3480
3481 # For naive datetime, utctimetuple == timetuple except for isdst
3482 d = cls(1, 2, 3, 10, 20, 30, 40)
3483 t = d.utctimetuple()
3484 self.assertEqual(t[:-1], d.timetuple()[:-1])
3485 self.assertEqual(0, t.tm_isdst)
3486 # Same if utcoffset is None
3487 class NOFS(DST):
3488 def utcoffset(self, dt):
3489 return None
3490 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
3491 t = d.utctimetuple()
3492 self.assertEqual(t[:-1], d.timetuple()[:-1])
3493 self.assertEqual(0, t.tm_isdst)
3494 # Check that bad tzinfo is detected
3495 class BOFS(DST):
3496 def utcoffset(self, dt):
3497 return "EST"
3498 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
3499 self.assertRaises(TypeError, d.utctimetuple)
3500
3501 # Check that utctimetuple() is the same as
3502 # astimezone(utc).timetuple()
3503 d = cls(2010, 11, 13, 14, 15, 16, 171819)
3504 for tz in [timezone.min, timezone.utc, timezone.max]:
3505 dtz = d.replace(tzinfo=tz)
3506 self.assertEqual(dtz.utctimetuple()[:-1],
3507 dtz.astimezone(timezone.utc).timetuple()[:-1])
3508 # At the edges, UTC adjustment can produce years out-of-range
3509 # for a datetime object. Ensure that an OverflowError is
3510 # raised.
3511 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
3512 # That goes back 1 minute less than a full day.
3513 self.assertRaises(OverflowError, tiny.utctimetuple)
3514
3515 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
3516 # That goes forward 1 minute less than a full day.
3517 self.assertRaises(OverflowError, huge.utctimetuple)
3518 # More overflow cases
3519 tiny = cls.min.replace(tzinfo=timezone(MINUTE))
3520 self.assertRaises(OverflowError, tiny.utctimetuple)
3521 huge = cls.max.replace(tzinfo=timezone(-MINUTE))
3522 self.assertRaises(OverflowError, huge.utctimetuple)
3523
3524 def test_tzinfo_isoformat(self):
3525 zero = FixedOffset(0, "+00:00")
3526 plus = FixedOffset(220, "+03:40")
3527 minus = FixedOffset(-231, "-03:51")
3528 unknown = FixedOffset(None, "")
3529
3530 cls = self.theclass
3531 datestr = '0001-02-03'
3532 for ofs in None, zero, plus, minus, unknown:
3533 for us in 0, 987001:
3534 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
3535 timestr = '04:05:59' + (us and '.987001' or '')
3536 ofsstr = ofs is not None and d.tzname() or ''
3537 tailstr = timestr + ofsstr
3538 iso = d.isoformat()
3539 self.assertEqual(iso, datestr + 'T' + tailstr)
3540 self.assertEqual(iso, d.isoformat('T'))
3541 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
3542 self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
3543 self.assertEqual(str(d), datestr + ' ' + tailstr)
3544
3545 def test_replace(self):
3546 cls = self.theclass
3547 z100 = FixedOffset(100, "+100")
3548 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3549 args = [1, 2, 3, 4, 5, 6, 7, z100]
3550 base = cls(*args)
3551 self.assertEqual(base, base.replace())
3552
3553 i = 0
3554 for name, newval in (("year", 2),
3555 ("month", 3),
3556 ("day", 4),
3557 ("hour", 5),
3558 ("minute", 6),
3559 ("second", 7),
3560 ("microsecond", 8),
3561 ("tzinfo", zm200)):
3562 newargs = args[:]
3563 newargs[i] = newval
3564 expected = cls(*newargs)
3565 got = base.replace(**{name: newval})
3566 self.assertEqual(expected, got)
3567 i += 1
3568
3569 # Ensure we can get rid of a tzinfo.
3570 self.assertEqual(base.tzname(), "+100")
3571 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003572 self.assertIsNone(base2.tzinfo)
3573 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003574
3575 # Ensure we can add one.
3576 base3 = base2.replace(tzinfo=z100)
3577 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003578 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003579
3580 # Out of bounds.
3581 base = cls(2000, 2, 29)
3582 self.assertRaises(ValueError, base.replace, year=2001)
3583
3584 def test_more_astimezone(self):
3585 # The inherited test_astimezone covered some trivial and error cases.
3586 fnone = FixedOffset(None, "None")
3587 f44m = FixedOffset(44, "44")
3588 fm5h = FixedOffset(-timedelta(hours=5), "m300")
3589
3590 dt = self.theclass.now(tz=f44m)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003591 self.assertIs(dt.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003592 # Replacing with degenerate tzinfo raises an exception.
3593 self.assertRaises(ValueError, dt.astimezone, fnone)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003594 # Replacing with same tzinfo makes no change.
3595 x = dt.astimezone(dt.tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003596 self.assertIs(x.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003597 self.assertEqual(x.date(), dt.date())
3598 self.assertEqual(x.time(), dt.time())
3599
3600 # Replacing with different tzinfo does adjust.
3601 got = dt.astimezone(fm5h)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003602 self.assertIs(got.tzinfo, fm5h)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003603 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
3604 expected = dt - dt.utcoffset() # in effect, convert to UTC
3605 expected += fm5h.utcoffset(dt) # and from there to local time
3606 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
3607 self.assertEqual(got.date(), expected.date())
3608 self.assertEqual(got.time(), expected.time())
3609 self.assertEqual(got.timetz(), expected.timetz())
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003610 self.assertIs(got.tzinfo, expected.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003611 self.assertEqual(got, expected)
3612
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003613 @support.run_with_tz('UTC')
3614 def test_astimezone_default_utc(self):
3615 dt = self.theclass.now(timezone.utc)
3616 self.assertEqual(dt.astimezone(None), dt)
3617 self.assertEqual(dt.astimezone(), dt)
3618
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003619 # Note that offset in TZ variable has the opposite sign to that
3620 # produced by %z directive.
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003621 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
3622 def test_astimezone_default_eastern(self):
3623 dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
3624 local = dt.astimezone()
3625 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003626 self.assertEqual(local.strftime("%z %Z"), "-0500 EST")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003627 dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc)
3628 local = dt.astimezone()
3629 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003630 self.assertEqual(local.strftime("%z %Z"), "-0400 EDT")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003631
Alexander Belopolsky1dcf4f92016-03-25 15:42:59 -04003632 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
3633 def test_astimezone_default_near_fold(self):
3634 # Issue #26616.
3635 u = datetime(2015, 11, 1, 5, tzinfo=timezone.utc)
3636 t = u.astimezone()
3637 s = t.astimezone()
3638 self.assertEqual(t.tzinfo, s.tzinfo)
3639
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003640 def test_aware_subtract(self):
3641 cls = self.theclass
3642
3643 # Ensure that utcoffset() is ignored when the operands have the
3644 # same tzinfo member.
3645 class OperandDependentOffset(tzinfo):
3646 def utcoffset(self, t):
3647 if t.minute < 10:
3648 # d0 and d1 equal after adjustment
3649 return timedelta(minutes=t.minute)
3650 else:
3651 # d2 off in the weeds
3652 return timedelta(minutes=59)
3653
3654 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
3655 d0 = base.replace(minute=3)
3656 d1 = base.replace(minute=9)
3657 d2 = base.replace(minute=11)
3658 for x in d0, d1, d2:
3659 for y in d0, d1, d2:
3660 got = x - y
3661 expected = timedelta(minutes=x.minute - y.minute)
3662 self.assertEqual(got, expected)
3663
3664 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
3665 # ignored.
3666 base = cls(8, 9, 10, 11, 12, 13, 14)
3667 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
3668 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
3669 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
3670 for x in d0, d1, d2:
3671 for y in d0, d1, d2:
3672 got = x - y
3673 if (x is d0 or x is d1) and (y is d0 or y is d1):
3674 expected = timedelta(0)
3675 elif x is y is d2:
3676 expected = timedelta(0)
3677 elif x is d2:
3678 expected = timedelta(minutes=(11-59)-0)
3679 else:
3680 assert y is d2
3681 expected = timedelta(minutes=0-(11-59))
3682 self.assertEqual(got, expected)
3683
3684 def test_mixed_compare(self):
3685 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
3686 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
3687 self.assertEqual(t1, t2)
3688 t2 = t2.replace(tzinfo=None)
3689 self.assertEqual(t1, t2)
3690 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3691 self.assertEqual(t1, t2)
3692 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04003693 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003694
3695 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
3696 class Varies(tzinfo):
3697 def __init__(self):
3698 self.offset = timedelta(minutes=22)
3699 def utcoffset(self, t):
3700 self.offset += timedelta(minutes=1)
3701 return self.offset
3702
3703 v = Varies()
3704 t1 = t2.replace(tzinfo=v)
3705 t2 = t2.replace(tzinfo=v)
3706 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3707 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3708 self.assertEqual(t1, t2)
3709
3710 # But if they're not identical, it isn't ignored.
3711 t2 = t2.replace(tzinfo=Varies())
3712 self.assertTrue(t1 < t2) # t1's offset counter still going up
3713
3714 def test_subclass_datetimetz(self):
3715
3716 class C(self.theclass):
3717 theAnswer = 42
3718
3719 def __new__(cls, *args, **kws):
3720 temp = kws.copy()
3721 extra = temp.pop('extra')
3722 result = self.theclass.__new__(cls, *args, **temp)
3723 result.extra = extra
3724 return result
3725
3726 def newmeth(self, start):
3727 return start + self.hour + self.year
3728
3729 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3730
3731 dt1 = self.theclass(*args)
3732 dt2 = C(*args, **{'extra': 7})
3733
3734 self.assertEqual(dt2.__class__, C)
3735 self.assertEqual(dt2.theAnswer, 42)
3736 self.assertEqual(dt2.extra, 7)
3737 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3738 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
3739
3740# Pain to set up DST-aware tzinfo classes.
3741
3742def first_sunday_on_or_after(dt):
3743 days_to_go = 6 - dt.weekday()
3744 if days_to_go:
3745 dt += timedelta(days_to_go)
3746 return dt
3747
3748ZERO = timedelta(0)
3749MINUTE = timedelta(minutes=1)
3750HOUR = timedelta(hours=1)
3751DAY = timedelta(days=1)
3752# In the US, DST starts at 2am (standard time) on the first Sunday in April.
3753DSTSTART = datetime(1, 4, 1, 2)
3754# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
3755# which is the first Sunday on or after Oct 25. Because we view 1:MM as
3756# being standard time on that day, there is no spelling in local time of
3757# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
3758DSTEND = datetime(1, 10, 25, 1)
3759
3760class USTimeZone(tzinfo):
3761
3762 def __init__(self, hours, reprname, stdname, dstname):
3763 self.stdoffset = timedelta(hours=hours)
3764 self.reprname = reprname
3765 self.stdname = stdname
3766 self.dstname = dstname
3767
3768 def __repr__(self):
3769 return self.reprname
3770
3771 def tzname(self, dt):
3772 if self.dst(dt):
3773 return self.dstname
3774 else:
3775 return self.stdname
3776
3777 def utcoffset(self, dt):
3778 return self.stdoffset + self.dst(dt)
3779
3780 def dst(self, dt):
3781 if dt is None or dt.tzinfo is None:
3782 # An exception instead may be sensible here, in one or more of
3783 # the cases.
3784 return ZERO
3785 assert dt.tzinfo is self
3786
3787 # Find first Sunday in April.
3788 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
3789 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
3790
3791 # Find last Sunday in October.
3792 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
3793 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
3794
3795 # Can't compare naive to aware objects, so strip the timezone from
3796 # dt first.
3797 if start <= dt.replace(tzinfo=None) < end:
3798 return HOUR
3799 else:
3800 return ZERO
3801
3802Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
3803Central = USTimeZone(-6, "Central", "CST", "CDT")
3804Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
3805Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
3806utc_real = FixedOffset(0, "UTC", 0)
3807# For better test coverage, we want another flavor of UTC that's west of
3808# the Eastern and Pacific timezones.
3809utc_fake = FixedOffset(-12*60, "UTCfake", 0)
3810
3811class TestTimezoneConversions(unittest.TestCase):
3812 # The DST switch times for 2002, in std time.
3813 dston = datetime(2002, 4, 7, 2)
3814 dstoff = datetime(2002, 10, 27, 1)
3815
3816 theclass = datetime
3817
3818 # Check a time that's inside DST.
3819 def checkinside(self, dt, tz, utc, dston, dstoff):
3820 self.assertEqual(dt.dst(), HOUR)
3821
3822 # Conversion to our own timezone is always an identity.
3823 self.assertEqual(dt.astimezone(tz), dt)
3824
3825 asutc = dt.astimezone(utc)
3826 there_and_back = asutc.astimezone(tz)
3827
3828 # Conversion to UTC and back isn't always an identity here,
3829 # because there are redundant spellings (in local time) of
3830 # UTC time when DST begins: the clock jumps from 1:59:59
3831 # to 3:00:00, and a local time of 2:MM:SS doesn't really
3832 # make sense then. The classes above treat 2:MM:SS as
3833 # daylight time then (it's "after 2am"), really an alias
3834 # for 1:MM:SS standard time. The latter form is what
3835 # conversion back from UTC produces.
3836 if dt.date() == dston.date() and dt.hour == 2:
3837 # We're in the redundant hour, and coming back from
3838 # UTC gives the 1:MM:SS standard-time spelling.
3839 self.assertEqual(there_and_back + HOUR, dt)
3840 # Although during was considered to be in daylight
3841 # time, there_and_back is not.
3842 self.assertEqual(there_and_back.dst(), ZERO)
3843 # They're the same times in UTC.
3844 self.assertEqual(there_and_back.astimezone(utc),
3845 dt.astimezone(utc))
3846 else:
3847 # We're not in the redundant hour.
3848 self.assertEqual(dt, there_and_back)
3849
3850 # Because we have a redundant spelling when DST begins, there is
Ezio Melotti3b3499b2011-03-16 11:35:38 +02003851 # (unfortunately) an hour when DST ends that can't be spelled at all in
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003852 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
3853 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
3854 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
3855 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
3856 # expressed in local time. Nevertheless, we want conversion back
3857 # from UTC to mimic the local clock's "repeat an hour" behavior.
3858 nexthour_utc = asutc + HOUR
3859 nexthour_tz = nexthour_utc.astimezone(tz)
3860 if dt.date() == dstoff.date() and dt.hour == 0:
3861 # We're in the hour before the last DST hour. The last DST hour
3862 # is ineffable. We want the conversion back to repeat 1:MM.
3863 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3864 nexthour_utc += HOUR
3865 nexthour_tz = nexthour_utc.astimezone(tz)
3866 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3867 else:
3868 self.assertEqual(nexthour_tz - dt, HOUR)
3869
3870 # Check a time that's outside DST.
3871 def checkoutside(self, dt, tz, utc):
3872 self.assertEqual(dt.dst(), ZERO)
3873
3874 # Conversion to our own timezone is always an identity.
3875 self.assertEqual(dt.astimezone(tz), dt)
3876
3877 # Converting to UTC and back is an identity too.
3878 asutc = dt.astimezone(utc)
3879 there_and_back = asutc.astimezone(tz)
3880 self.assertEqual(dt, there_and_back)
3881
3882 def convert_between_tz_and_utc(self, tz, utc):
3883 dston = self.dston.replace(tzinfo=tz)
3884 # Because 1:MM on the day DST ends is taken as being standard time,
3885 # there is no spelling in tz for the last hour of daylight time.
3886 # For purposes of the test, the last hour of DST is 0:MM, which is
3887 # taken as being daylight time (and 1:MM is taken as being standard
3888 # time).
3889 dstoff = self.dstoff.replace(tzinfo=tz)
3890 for delta in (timedelta(weeks=13),
3891 DAY,
3892 HOUR,
3893 timedelta(minutes=1),
3894 timedelta(microseconds=1)):
3895
3896 self.checkinside(dston, tz, utc, dston, dstoff)
3897 for during in dston + delta, dstoff - delta:
3898 self.checkinside(during, tz, utc, dston, dstoff)
3899
3900 self.checkoutside(dstoff, tz, utc)
3901 for outside in dston - delta, dstoff + delta:
3902 self.checkoutside(outside, tz, utc)
3903
3904 def test_easy(self):
3905 # Despite the name of this test, the endcases are excruciating.
3906 self.convert_between_tz_and_utc(Eastern, utc_real)
3907 self.convert_between_tz_and_utc(Pacific, utc_real)
3908 self.convert_between_tz_and_utc(Eastern, utc_fake)
3909 self.convert_between_tz_and_utc(Pacific, utc_fake)
3910 # The next is really dancing near the edge. It works because
3911 # Pacific and Eastern are far enough apart that their "problem
3912 # hours" don't overlap.
3913 self.convert_between_tz_and_utc(Eastern, Pacific)
3914 self.convert_between_tz_and_utc(Pacific, Eastern)
3915 # OTOH, these fail! Don't enable them. The difficulty is that
3916 # the edge case tests assume that every hour is representable in
3917 # the "utc" class. This is always true for a fixed-offset tzinfo
3918 # class (lke utc_real and utc_fake), but not for Eastern or Central.
3919 # For these adjacent DST-aware time zones, the range of time offsets
3920 # tested ends up creating hours in the one that aren't representable
3921 # in the other. For the same reason, we would see failures in the
3922 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
3923 # offset deltas in convert_between_tz_and_utc().
3924 #
3925 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
3926 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
3927
3928 def test_tricky(self):
3929 # 22:00 on day before daylight starts.
3930 fourback = self.dston - timedelta(hours=4)
3931 ninewest = FixedOffset(-9*60, "-0900", 0)
3932 fourback = fourback.replace(tzinfo=ninewest)
3933 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
3934 # 2", we should get the 3 spelling.
3935 # If we plug 22:00 the day before into Eastern, it "looks like std
3936 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
3937 # to 22:00 lands on 2:00, which makes no sense in local time (the
3938 # local clock jumps from 1 to 3). The point here is to make sure we
3939 # get the 3 spelling.
3940 expected = self.dston.replace(hour=3)
3941 got = fourback.astimezone(Eastern).replace(tzinfo=None)
3942 self.assertEqual(expected, got)
3943
3944 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
3945 # case we want the 1:00 spelling.
3946 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
3947 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
3948 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
3949 # spelling.
3950 expected = self.dston.replace(hour=1)
3951 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
3952 self.assertEqual(expected, got)
3953
3954 # Now on the day DST ends, we want "repeat an hour" behavior.
3955 # UTC 4:MM 5:MM 6:MM 7:MM checking these
3956 # EST 23:MM 0:MM 1:MM 2:MM
3957 # EDT 0:MM 1:MM 2:MM 3:MM
3958 # wall 0:MM 1:MM 1:MM 2:MM against these
3959 for utc in utc_real, utc_fake:
3960 for tz in Eastern, Pacific:
3961 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
3962 # Convert that to UTC.
3963 first_std_hour -= tz.utcoffset(None)
3964 # Adjust for possibly fake UTC.
3965 asutc = first_std_hour + utc.utcoffset(None)
3966 # First UTC hour to convert; this is 4:00 when utc=utc_real &
3967 # tz=Eastern.
3968 asutcbase = asutc.replace(tzinfo=utc)
3969 for tzhour in (0, 1, 1, 2):
3970 expectedbase = self.dstoff.replace(hour=tzhour)
3971 for minute in 0, 30, 59:
3972 expected = expectedbase.replace(minute=minute)
3973 asutc = asutcbase.replace(minute=minute)
3974 astz = asutc.astimezone(tz)
3975 self.assertEqual(astz.replace(tzinfo=None), expected)
3976 asutcbase += HOUR
3977
3978
3979 def test_bogus_dst(self):
3980 class ok(tzinfo):
3981 def utcoffset(self, dt): return HOUR
3982 def dst(self, dt): return HOUR
3983
3984 now = self.theclass.now().replace(tzinfo=utc_real)
3985 # Doesn't blow up.
3986 now.astimezone(ok())
3987
3988 # Does blow up.
3989 class notok(ok):
3990 def dst(self, dt): return None
3991 self.assertRaises(ValueError, now.astimezone, notok())
3992
3993 # Sometimes blow up. In the following, tzinfo.dst()
3994 # implementation may return None or not None depending on
3995 # whether DST is assumed to be in effect. In this situation,
3996 # a ValueError should be raised by astimezone().
3997 class tricky_notok(ok):
3998 def dst(self, dt):
3999 if dt.year == 2000:
4000 return None
4001 else:
4002 return 10*HOUR
4003 dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
4004 self.assertRaises(ValueError, dt.astimezone, tricky_notok())
4005
4006 def test_fromutc(self):
4007 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
4008 now = datetime.utcnow().replace(tzinfo=utc_real)
4009 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
4010 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
4011 enow = Eastern.fromutc(now) # doesn't blow up
4012 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
4013 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
4014 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
4015
4016 # Always converts UTC to standard time.
4017 class FauxUSTimeZone(USTimeZone):
4018 def fromutc(self, dt):
4019 return dt + self.stdoffset
4020 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
4021
4022 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
4023 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
4024 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
4025
4026 # Check around DST start.
4027 start = self.dston.replace(hour=4, tzinfo=Eastern)
4028 fstart = start.replace(tzinfo=FEastern)
4029 for wall in 23, 0, 1, 3, 4, 5:
4030 expected = start.replace(hour=wall)
4031 if wall == 23:
4032 expected -= timedelta(days=1)
4033 got = Eastern.fromutc(start)
4034 self.assertEqual(expected, got)
4035
4036 expected = fstart + FEastern.stdoffset
4037 got = FEastern.fromutc(fstart)
4038 self.assertEqual(expected, got)
4039
4040 # Ensure astimezone() calls fromutc() too.
4041 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
4042 self.assertEqual(expected, got)
4043
4044 start += HOUR
4045 fstart += HOUR
4046
4047 # Check around DST end.
4048 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
4049 fstart = start.replace(tzinfo=FEastern)
4050 for wall in 0, 1, 1, 2, 3, 4:
4051 expected = start.replace(hour=wall)
4052 got = Eastern.fromutc(start)
4053 self.assertEqual(expected, got)
4054
4055 expected = fstart + FEastern.stdoffset
4056 got = FEastern.fromutc(fstart)
4057 self.assertEqual(expected, got)
4058
4059 # Ensure astimezone() calls fromutc() too.
4060 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
4061 self.assertEqual(expected, got)
4062
4063 start += HOUR
4064 fstart += HOUR
4065
4066
4067#############################################################################
4068# oddballs
4069
4070class Oddballs(unittest.TestCase):
4071
4072 def test_bug_1028306(self):
4073 # Trying to compare a date to a datetime should act like a mixed-
4074 # type comparison, despite that datetime is a subclass of date.
4075 as_date = date.today()
4076 as_datetime = datetime.combine(as_date, time())
4077 self.assertTrue(as_date != as_datetime)
4078 self.assertTrue(as_datetime != as_date)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004079 self.assertFalse(as_date == as_datetime)
4080 self.assertFalse(as_datetime == as_date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004081 self.assertRaises(TypeError, lambda: as_date < as_datetime)
4082 self.assertRaises(TypeError, lambda: as_datetime < as_date)
4083 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
4084 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
4085 self.assertRaises(TypeError, lambda: as_date > as_datetime)
4086 self.assertRaises(TypeError, lambda: as_datetime > as_date)
4087 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
4088 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
4089
Raymond Hettinger15f44ab2016-08-30 10:47:49 -07004090 # Nevertheless, comparison should work with the base-class (date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004091 # projection if use of a date method is forced.
4092 self.assertEqual(as_date.__eq__(as_datetime), True)
4093 different_day = (as_date.day + 1) % 20 + 1
4094 as_different = as_datetime.replace(day= different_day)
4095 self.assertEqual(as_date.__eq__(as_different), False)
4096
4097 # And date should compare with other subclasses of date. If a
4098 # subclass wants to stop this, it's up to the subclass to do so.
4099 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
4100 self.assertEqual(as_date, date_sc)
4101 self.assertEqual(date_sc, as_date)
4102
4103 # Ditto for datetimes.
4104 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
4105 as_date.day, 0, 0, 0)
4106 self.assertEqual(as_datetime, datetime_sc)
4107 self.assertEqual(datetime_sc, as_datetime)
4108
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004109 def test_extra_attributes(self):
4110 for x in [date.today(),
4111 time(),
4112 datetime.utcnow(),
4113 timedelta(),
4114 tzinfo(),
4115 timezone(timedelta())]:
4116 with self.assertRaises(AttributeError):
4117 x.abc = 1
4118
4119 def test_check_arg_types(self):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004120 class Number:
4121 def __init__(self, value):
4122 self.value = value
4123 def __int__(self):
4124 return self.value
4125
4126 for xx in [decimal.Decimal(10),
4127 decimal.Decimal('10.9'),
4128 Number(10)]:
4129 self.assertEqual(datetime(10, 10, 10, 10, 10, 10, 10),
4130 datetime(xx, xx, xx, xx, xx, xx, xx))
4131
4132 with self.assertRaisesRegex(TypeError, '^an integer is required '
R David Murray44b548d2016-09-08 13:59:53 -04004133 r'\(got type str\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004134 datetime(10, 10, '10')
4135
4136 f10 = Number(10.9)
4137 with self.assertRaisesRegex(TypeError, '^__int__ returned non-int '
R David Murray44b548d2016-09-08 13:59:53 -04004138 r'\(type float\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004139 datetime(10, 10, f10)
4140
4141 class Float(float):
4142 pass
4143 s10 = Float(10.9)
4144 with self.assertRaisesRegex(TypeError, '^integer argument expected, '
4145 'got float$'):
4146 datetime(10, 10, s10)
4147
4148 with self.assertRaises(TypeError):
4149 datetime(10., 10, 10)
4150 with self.assertRaises(TypeError):
4151 datetime(10, 10., 10)
4152 with self.assertRaises(TypeError):
4153 datetime(10, 10, 10.)
4154 with self.assertRaises(TypeError):
4155 datetime(10, 10, 10, 10.)
4156 with self.assertRaises(TypeError):
4157 datetime(10, 10, 10, 10, 10.)
4158 with self.assertRaises(TypeError):
4159 datetime(10, 10, 10, 10, 10, 10.)
4160 with self.assertRaises(TypeError):
4161 datetime(10, 10, 10, 10, 10, 10, 10.)
4162
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004163#############################################################################
4164# Local Time Disambiguation
4165
4166# An experimental reimplementation of fromutc that respects the "fold" flag.
4167
4168class tzinfo2(tzinfo):
4169
4170 def fromutc(self, dt):
4171 "datetime in UTC -> datetime in local time."
4172
4173 if not isinstance(dt, datetime):
4174 raise TypeError("fromutc() requires a datetime argument")
4175 if dt.tzinfo is not self:
4176 raise ValueError("dt.tzinfo is not self")
4177 # Returned value satisfies
4178 # dt + ldt.utcoffset() = ldt
4179 off0 = dt.replace(fold=0).utcoffset()
4180 off1 = dt.replace(fold=1).utcoffset()
4181 if off0 is None or off1 is None or dt.dst() is None:
4182 raise ValueError
4183 if off0 == off1:
4184 ldt = dt + off0
4185 off1 = ldt.utcoffset()
4186 if off0 == off1:
4187 return ldt
4188 # Now, we discovered both possible offsets, so
4189 # we can just try four possible solutions:
4190 for off in [off0, off1]:
4191 ldt = dt + off
4192 if ldt.utcoffset() == off:
4193 return ldt
4194 ldt = ldt.replace(fold=1)
4195 if ldt.utcoffset() == off:
4196 return ldt
4197
4198 raise ValueError("No suitable local time found")
4199
4200# Reimplementing simplified US timezones to respect the "fold" flag:
4201
4202class USTimeZone2(tzinfo2):
4203
4204 def __init__(self, hours, reprname, stdname, dstname):
4205 self.stdoffset = timedelta(hours=hours)
4206 self.reprname = reprname
4207 self.stdname = stdname
4208 self.dstname = dstname
4209
4210 def __repr__(self):
4211 return self.reprname
4212
4213 def tzname(self, dt):
4214 if self.dst(dt):
4215 return self.dstname
4216 else:
4217 return self.stdname
4218
4219 def utcoffset(self, dt):
4220 return self.stdoffset + self.dst(dt)
4221
4222 def dst(self, dt):
4223 if dt is None or dt.tzinfo is None:
4224 # An exception instead may be sensible here, in one or more of
4225 # the cases.
4226 return ZERO
4227 assert dt.tzinfo is self
4228
4229 # Find first Sunday in April.
4230 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
4231 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
4232
4233 # Find last Sunday in October.
4234 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
4235 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
4236
4237 # Can't compare naive to aware objects, so strip the timezone from
4238 # dt first.
4239 dt = dt.replace(tzinfo=None)
4240 if start + HOUR <= dt < end:
4241 # DST is in effect.
4242 return HOUR
4243 elif end <= dt < end + HOUR:
4244 # Fold (an ambiguous hour): use dt.fold to disambiguate.
4245 return ZERO if dt.fold else HOUR
4246 elif start <= dt < start + HOUR:
4247 # Gap (a non-existent hour): reverse the fold rule.
4248 return HOUR if dt.fold else ZERO
4249 else:
4250 # DST is off.
4251 return ZERO
4252
4253Eastern2 = USTimeZone2(-5, "Eastern2", "EST", "EDT")
4254Central2 = USTimeZone2(-6, "Central2", "CST", "CDT")
4255Mountain2 = USTimeZone2(-7, "Mountain2", "MST", "MDT")
4256Pacific2 = USTimeZone2(-8, "Pacific2", "PST", "PDT")
4257
4258# Europe_Vilnius_1941 tzinfo implementation reproduces the following
4259# 1941 transition from Olson's tzdist:
4260#
4261# Zone NAME GMTOFF RULES FORMAT [UNTIL]
4262# ZoneEurope/Vilnius 1:00 - CET 1940 Aug 3
4263# 3:00 - MSK 1941 Jun 24
4264# 1:00 C-Eur CE%sT 1944 Aug
4265#
4266# $ zdump -v Europe/Vilnius | grep 1941
4267# Europe/Vilnius Mon Jun 23 20:59:59 1941 UTC = Mon Jun 23 23:59:59 1941 MSK isdst=0 gmtoff=10800
4268# Europe/Vilnius Mon Jun 23 21:00:00 1941 UTC = Mon Jun 23 23:00:00 1941 CEST isdst=1 gmtoff=7200
4269
4270class Europe_Vilnius_1941(tzinfo):
4271 def _utc_fold(self):
4272 return [datetime(1941, 6, 23, 21, tzinfo=self), # Mon Jun 23 21:00:00 1941 UTC
4273 datetime(1941, 6, 23, 22, tzinfo=self)] # Mon Jun 23 22:00:00 1941 UTC
4274
4275 def _loc_fold(self):
4276 return [datetime(1941, 6, 23, 23, tzinfo=self), # Mon Jun 23 23:00:00 1941 MSK / CEST
4277 datetime(1941, 6, 24, 0, tzinfo=self)] # Mon Jun 24 00:00:00 1941 CEST
4278
4279 def utcoffset(self, dt):
4280 fold_start, fold_stop = self._loc_fold()
4281 if dt < fold_start:
4282 return 3 * HOUR
4283 if dt < fold_stop:
4284 return (2 if dt.fold else 3) * HOUR
4285 # if dt >= fold_stop
4286 return 2 * HOUR
4287
4288 def dst(self, dt):
4289 fold_start, fold_stop = self._loc_fold()
4290 if dt < fold_start:
4291 return 0 * HOUR
4292 if dt < fold_stop:
4293 return (1 if dt.fold else 0) * HOUR
4294 # if dt >= fold_stop
4295 return 1 * HOUR
4296
4297 def tzname(self, dt):
4298 fold_start, fold_stop = self._loc_fold()
4299 if dt < fold_start:
4300 return 'MSK'
4301 if dt < fold_stop:
4302 return ('MSK', 'CEST')[dt.fold]
4303 # if dt >= fold_stop
4304 return 'CEST'
4305
4306 def fromutc(self, dt):
4307 assert dt.fold == 0
4308 assert dt.tzinfo is self
4309 if dt.year != 1941:
4310 raise NotImplementedError
4311 fold_start, fold_stop = self._utc_fold()
4312 if dt < fold_start:
4313 return dt + 3 * HOUR
4314 if dt < fold_stop:
4315 return (dt + 2 * HOUR).replace(fold=1)
4316 # if dt >= fold_stop
4317 return dt + 2 * HOUR
4318
4319
4320class TestLocalTimeDisambiguation(unittest.TestCase):
4321
4322 def test_vilnius_1941_fromutc(self):
4323 Vilnius = Europe_Vilnius_1941()
4324
4325 gdt = datetime(1941, 6, 23, 20, 59, 59, tzinfo=timezone.utc)
4326 ldt = gdt.astimezone(Vilnius)
4327 self.assertEqual(ldt.strftime("%c %Z%z"),
4328 'Mon Jun 23 23:59:59 1941 MSK+0300')
4329 self.assertEqual(ldt.fold, 0)
4330 self.assertFalse(ldt.dst())
4331
4332 gdt = datetime(1941, 6, 23, 21, tzinfo=timezone.utc)
4333 ldt = gdt.astimezone(Vilnius)
4334 self.assertEqual(ldt.strftime("%c %Z%z"),
4335 'Mon Jun 23 23:00:00 1941 CEST+0200')
4336 self.assertEqual(ldt.fold, 1)
4337 self.assertTrue(ldt.dst())
4338
4339 gdt = datetime(1941, 6, 23, 22, tzinfo=timezone.utc)
4340 ldt = gdt.astimezone(Vilnius)
4341 self.assertEqual(ldt.strftime("%c %Z%z"),
4342 'Tue Jun 24 00:00:00 1941 CEST+0200')
4343 self.assertEqual(ldt.fold, 0)
4344 self.assertTrue(ldt.dst())
4345
4346 def test_vilnius_1941_toutc(self):
4347 Vilnius = Europe_Vilnius_1941()
4348
4349 ldt = datetime(1941, 6, 23, 22, 59, 59, tzinfo=Vilnius)
4350 gdt = ldt.astimezone(timezone.utc)
4351 self.assertEqual(gdt.strftime("%c %Z"),
4352 'Mon Jun 23 19:59:59 1941 UTC')
4353
4354 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius)
4355 gdt = ldt.astimezone(timezone.utc)
4356 self.assertEqual(gdt.strftime("%c %Z"),
4357 'Mon Jun 23 20:59:59 1941 UTC')
4358
4359 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius, fold=1)
4360 gdt = ldt.astimezone(timezone.utc)
4361 self.assertEqual(gdt.strftime("%c %Z"),
4362 'Mon Jun 23 21:59:59 1941 UTC')
4363
4364 ldt = datetime(1941, 6, 24, 0, tzinfo=Vilnius)
4365 gdt = ldt.astimezone(timezone.utc)
4366 self.assertEqual(gdt.strftime("%c %Z"),
4367 'Mon Jun 23 22:00:00 1941 UTC')
4368
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004369 def test_constructors(self):
4370 t = time(0, fold=1)
4371 dt = datetime(1, 1, 1, fold=1)
4372 self.assertEqual(t.fold, 1)
4373 self.assertEqual(dt.fold, 1)
4374 with self.assertRaises(TypeError):
4375 time(0, 0, 0, 0, None, 0)
4376
4377 def test_member(self):
4378 dt = datetime(1, 1, 1, fold=1)
4379 t = dt.time()
4380 self.assertEqual(t.fold, 1)
4381 t = dt.timetz()
4382 self.assertEqual(t.fold, 1)
4383
4384 def test_replace(self):
4385 t = time(0)
4386 dt = datetime(1, 1, 1)
4387 self.assertEqual(t.replace(fold=1).fold, 1)
4388 self.assertEqual(dt.replace(fold=1).fold, 1)
4389 self.assertEqual(t.replace(fold=0).fold, 0)
4390 self.assertEqual(dt.replace(fold=0).fold, 0)
4391 # Check that replacement of other fields does not change "fold".
4392 t = t.replace(fold=1, tzinfo=Eastern)
4393 dt = dt.replace(fold=1, tzinfo=Eastern)
4394 self.assertEqual(t.replace(tzinfo=None).fold, 1)
4395 self.assertEqual(dt.replace(tzinfo=None).fold, 1)
Serhiy Storchaka314d6fc2017-03-31 22:48:16 +03004396 # Out of bounds.
4397 with self.assertRaises(ValueError):
4398 t.replace(fold=2)
4399 with self.assertRaises(ValueError):
4400 dt.replace(fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004401 # Check that fold is a keyword-only argument
4402 with self.assertRaises(TypeError):
4403 t.replace(1, 1, 1, None, 1)
4404 with self.assertRaises(TypeError):
4405 dt.replace(1, 1, 1, 1, 1, 1, 1, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04004406
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004407 def test_comparison(self):
4408 t = time(0)
4409 dt = datetime(1, 1, 1)
4410 self.assertEqual(t, t.replace(fold=1))
4411 self.assertEqual(dt, dt.replace(fold=1))
4412
4413 def test_hash(self):
4414 t = time(0)
4415 dt = datetime(1, 1, 1)
4416 self.assertEqual(hash(t), hash(t.replace(fold=1)))
4417 self.assertEqual(hash(dt), hash(dt.replace(fold=1)))
4418
4419 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4420 def test_fromtimestamp(self):
4421 s = 1414906200
4422 dt0 = datetime.fromtimestamp(s)
4423 dt1 = datetime.fromtimestamp(s + 3600)
4424 self.assertEqual(dt0.fold, 0)
4425 self.assertEqual(dt1.fold, 1)
4426
4427 @support.run_with_tz('Australia/Lord_Howe')
4428 def test_fromtimestamp_lord_howe(self):
4429 tm = _time.localtime(1.4e9)
4430 if _time.strftime('%Z%z', tm) != 'LHST+1030':
4431 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
4432 # $ TZ=Australia/Lord_Howe date -r 1428158700
4433 # Sun Apr 5 01:45:00 LHDT 2015
4434 # $ TZ=Australia/Lord_Howe date -r 1428160500
4435 # Sun Apr 5 01:45:00 LHST 2015
4436 s = 1428158700
4437 t0 = datetime.fromtimestamp(s)
4438 t1 = datetime.fromtimestamp(s + 1800)
4439 self.assertEqual(t0, t1)
4440 self.assertEqual(t0.fold, 0)
4441 self.assertEqual(t1.fold, 1)
4442
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004443 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4444 def test_timestamp(self):
4445 dt0 = datetime(2014, 11, 2, 1, 30)
4446 dt1 = dt0.replace(fold=1)
4447 self.assertEqual(dt0.timestamp() + 3600,
4448 dt1.timestamp())
4449
4450 @support.run_with_tz('Australia/Lord_Howe')
4451 def test_timestamp_lord_howe(self):
4452 tm = _time.localtime(1.4e9)
4453 if _time.strftime('%Z%z', tm) != 'LHST+1030':
4454 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
4455 t = datetime(2015, 4, 5, 1, 45)
4456 s0 = t.replace(fold=0).timestamp()
4457 s1 = t.replace(fold=1).timestamp()
4458 self.assertEqual(s0 + 1800, s1)
4459
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004460 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4461 def test_astimezone(self):
4462 dt0 = datetime(2014, 11, 2, 1, 30)
4463 dt1 = dt0.replace(fold=1)
4464 # Convert both naive instances to aware.
4465 adt0 = dt0.astimezone()
4466 adt1 = dt1.astimezone()
4467 # Check that the first instance in DST zone and the second in STD
4468 self.assertEqual(adt0.tzname(), 'EDT')
4469 self.assertEqual(adt1.tzname(), 'EST')
4470 self.assertEqual(adt0 + HOUR, adt1)
4471 # Aware instances with fixed offset tzinfo's always have fold=0
4472 self.assertEqual(adt0.fold, 0)
4473 self.assertEqual(adt1.fold, 0)
4474
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004475 def test_pickle_fold(self):
4476 t = time(fold=1)
4477 dt = datetime(1, 1, 1, fold=1)
4478 for pickler, unpickler, proto in pickle_choices:
4479 for x in [t, dt]:
4480 s = pickler.dumps(x, proto)
4481 y = unpickler.loads(s)
4482 self.assertEqual(x, y)
4483 self.assertEqual((0 if proto < 4 else x.fold), y.fold)
4484
4485 def test_repr(self):
4486 t = time(fold=1)
4487 dt = datetime(1, 1, 1, fold=1)
4488 self.assertEqual(repr(t), 'datetime.time(0, 0, fold=1)')
4489 self.assertEqual(repr(dt),
4490 'datetime.datetime(1, 1, 1, 0, 0, fold=1)')
4491
4492 def test_dst(self):
4493 # Let's first establish that things work in regular times.
4494 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
4495 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
4496 self.assertEqual(dt_summer.dst(), HOUR)
4497 self.assertEqual(dt_winter.dst(), ZERO)
4498 # The disambiguation flag is ignored
4499 self.assertEqual(dt_summer.replace(fold=1).dst(), HOUR)
4500 self.assertEqual(dt_winter.replace(fold=1).dst(), ZERO)
4501
4502 # Pick local time in the fold.
4503 for minute in [0, 30, 59]:
4504 dt = datetime(2002, 10, 27, 1, minute, tzinfo=Eastern2)
4505 # With fold=0 (the default) it is in DST.
4506 self.assertEqual(dt.dst(), HOUR)
4507 # With fold=1 it is in STD.
4508 self.assertEqual(dt.replace(fold=1).dst(), ZERO)
4509
4510 # Pick local time in the gap.
4511 for minute in [0, 30, 59]:
4512 dt = datetime(2002, 4, 7, 2, minute, tzinfo=Eastern2)
4513 # With fold=0 (the default) it is in STD.
4514 self.assertEqual(dt.dst(), ZERO)
4515 # With fold=1 it is in DST.
4516 self.assertEqual(dt.replace(fold=1).dst(), HOUR)
4517
4518
4519 def test_utcoffset(self):
4520 # Let's first establish that things work in regular times.
4521 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
4522 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
4523 self.assertEqual(dt_summer.utcoffset(), -4 * HOUR)
4524 self.assertEqual(dt_winter.utcoffset(), -5 * HOUR)
4525 # The disambiguation flag is ignored
4526 self.assertEqual(dt_summer.replace(fold=1).utcoffset(), -4 * HOUR)
4527 self.assertEqual(dt_winter.replace(fold=1).utcoffset(), -5 * HOUR)
4528
4529 def test_fromutc(self):
4530 # Let's first establish that things work in regular times.
4531 u_summer = datetime(2002, 10, 27, 6, tzinfo=Eastern2) - timedelta.resolution
4532 u_winter = datetime(2002, 10, 27, 7, tzinfo=Eastern2)
4533 t_summer = Eastern2.fromutc(u_summer)
4534 t_winter = Eastern2.fromutc(u_winter)
4535 self.assertEqual(t_summer, u_summer - 4 * HOUR)
4536 self.assertEqual(t_winter, u_winter - 5 * HOUR)
4537 self.assertEqual(t_summer.fold, 0)
4538 self.assertEqual(t_winter.fold, 0)
4539
4540 # What happens in the fall-back fold?
4541 u = datetime(2002, 10, 27, 5, 30, tzinfo=Eastern2)
4542 t0 = Eastern2.fromutc(u)
4543 u += HOUR
4544 t1 = Eastern2.fromutc(u)
4545 self.assertEqual(t0, t1)
4546 self.assertEqual(t0.fold, 0)
4547 self.assertEqual(t1.fold, 1)
4548 # The tricky part is when u is in the local fold:
4549 u = datetime(2002, 10, 27, 1, 30, tzinfo=Eastern2)
4550 t = Eastern2.fromutc(u)
4551 self.assertEqual((t.day, t.hour), (26, 21))
4552 # .. or gets into the local fold after a standard time adjustment
4553 u = datetime(2002, 10, 27, 6, 30, tzinfo=Eastern2)
4554 t = Eastern2.fromutc(u)
4555 self.assertEqual((t.day, t.hour), (27, 1))
4556
4557 # What happens in the spring-forward gap?
4558 u = datetime(2002, 4, 7, 2, 0, tzinfo=Eastern2)
4559 t = Eastern2.fromutc(u)
4560 self.assertEqual((t.day, t.hour), (6, 21))
4561
4562 def test_mixed_compare_regular(self):
4563 t = datetime(2000, 1, 1, tzinfo=Eastern2)
4564 self.assertEqual(t, t.astimezone(timezone.utc))
4565 t = datetime(2000, 6, 1, tzinfo=Eastern2)
4566 self.assertEqual(t, t.astimezone(timezone.utc))
4567
4568 def test_mixed_compare_fold(self):
4569 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
4570 t_fold_utc = t_fold.astimezone(timezone.utc)
4571 self.assertNotEqual(t_fold, t_fold_utc)
4572
4573 def test_mixed_compare_gap(self):
4574 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
4575 t_gap_utc = t_gap.astimezone(timezone.utc)
4576 self.assertNotEqual(t_gap, t_gap_utc)
4577
4578 def test_hash_aware(self):
4579 t = datetime(2000, 1, 1, tzinfo=Eastern2)
4580 self.assertEqual(hash(t), hash(t.replace(fold=1)))
4581 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
4582 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
4583 self.assertEqual(hash(t_fold), hash(t_fold.replace(fold=1)))
4584 self.assertEqual(hash(t_gap), hash(t_gap.replace(fold=1)))
4585
4586SEC = timedelta(0, 1)
4587
4588def pairs(iterable):
4589 a, b = itertools.tee(iterable)
4590 next(b, None)
4591 return zip(a, b)
4592
4593class ZoneInfo(tzinfo):
4594 zoneroot = '/usr/share/zoneinfo'
4595 def __init__(self, ut, ti):
4596 """
4597
4598 :param ut: array
4599 Array of transition point timestamps
4600 :param ti: list
4601 A list of (offset, isdst, abbr) tuples
4602 :return: None
4603 """
4604 self.ut = ut
4605 self.ti = ti
4606 self.lt = self.invert(ut, ti)
4607
4608 @staticmethod
4609 def invert(ut, ti):
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04004610 lt = (array('q', ut), array('q', ut))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004611 if ut:
4612 offset = ti[0][0] // SEC
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04004613 lt[0][0] += offset
4614 lt[1][0] += offset
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004615 for i in range(1, len(ut)):
4616 lt[0][i] += ti[i-1][0] // SEC
4617 lt[1][i] += ti[i][0] // SEC
4618 return lt
4619
4620 @classmethod
4621 def fromfile(cls, fileobj):
4622 if fileobj.read(4).decode() != "TZif":
4623 raise ValueError("not a zoneinfo file")
4624 fileobj.seek(32)
4625 counts = array('i')
4626 counts.fromfile(fileobj, 3)
4627 if sys.byteorder != 'big':
4628 counts.byteswap()
4629
4630 ut = array('i')
4631 ut.fromfile(fileobj, counts[0])
4632 if sys.byteorder != 'big':
4633 ut.byteswap()
4634
4635 type_indices = array('B')
4636 type_indices.fromfile(fileobj, counts[0])
4637
4638 ttis = []
4639 for i in range(counts[1]):
4640 ttis.append(struct.unpack(">lbb", fileobj.read(6)))
4641
4642 abbrs = fileobj.read(counts[2])
4643
4644 # Convert ttis
4645 for i, (gmtoff, isdst, abbrind) in enumerate(ttis):
4646 abbr = abbrs[abbrind:abbrs.find(0, abbrind)].decode()
4647 ttis[i] = (timedelta(0, gmtoff), isdst, abbr)
4648
4649 ti = [None] * len(ut)
4650 for i, idx in enumerate(type_indices):
4651 ti[i] = ttis[idx]
4652
4653 self = cls(ut, ti)
4654
4655 return self
4656
4657 @classmethod
4658 def fromname(cls, name):
4659 path = os.path.join(cls.zoneroot, name)
4660 with open(path, 'rb') as f:
4661 return cls.fromfile(f)
4662
4663 EPOCHORDINAL = date(1970, 1, 1).toordinal()
4664
4665 def fromutc(self, dt):
4666 """datetime in UTC -> datetime in local time."""
4667
4668 if not isinstance(dt, datetime):
4669 raise TypeError("fromutc() requires a datetime argument")
4670 if dt.tzinfo is not self:
4671 raise ValueError("dt.tzinfo is not self")
4672
4673 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
4674 + dt.hour * 3600
4675 + dt.minute * 60
4676 + dt.second)
4677
4678 if timestamp < self.ut[1]:
4679 tti = self.ti[0]
4680 fold = 0
4681 else:
4682 idx = bisect.bisect_right(self.ut, timestamp)
4683 assert self.ut[idx-1] <= timestamp
4684 assert idx == len(self.ut) or timestamp < self.ut[idx]
4685 tti_prev, tti = self.ti[idx-2:idx]
4686 # Detect fold
4687 shift = tti_prev[0] - tti[0]
4688 fold = (shift > timedelta(0, timestamp - self.ut[idx-1]))
4689 dt += tti[0]
4690 if fold:
4691 return dt.replace(fold=1)
4692 else:
4693 return dt
4694
4695 def _find_ti(self, dt, i):
4696 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
4697 + dt.hour * 3600
4698 + dt.minute * 60
4699 + dt.second)
4700 lt = self.lt[dt.fold]
4701 idx = bisect.bisect_right(lt, timestamp)
4702
4703 return self.ti[max(0, idx - 1)][i]
4704
4705 def utcoffset(self, dt):
4706 return self._find_ti(dt, 0)
4707
4708 def dst(self, dt):
4709 isdst = self._find_ti(dt, 1)
4710 # XXX: We cannot accurately determine the "save" value,
4711 # so let's return 1h whenever DST is in effect. Since
4712 # we don't use dst() in fromutc(), it is unlikely that
4713 # it will be needed for anything more than bool(dst()).
4714 return ZERO if isdst else HOUR
4715
4716 def tzname(self, dt):
4717 return self._find_ti(dt, 2)
4718
4719 @classmethod
4720 def zonenames(cls, zonedir=None):
4721 if zonedir is None:
4722 zonedir = cls.zoneroot
Alexander Belopolsky1b8f26c2016-08-11 11:01:45 -04004723 zone_tab = os.path.join(zonedir, 'zone.tab')
4724 try:
4725 f = open(zone_tab)
4726 except OSError:
4727 return
4728 with f:
4729 for line in f:
4730 line = line.strip()
4731 if line and not line.startswith('#'):
4732 yield line.split()[2]
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004733
4734 @classmethod
4735 def stats(cls, start_year=1):
4736 count = gap_count = fold_count = zeros_count = 0
4737 min_gap = min_fold = timedelta.max
4738 max_gap = max_fold = ZERO
4739 min_gap_datetime = max_gap_datetime = datetime.min
4740 min_gap_zone = max_gap_zone = None
4741 min_fold_datetime = max_fold_datetime = datetime.min
4742 min_fold_zone = max_fold_zone = None
4743 stats_since = datetime(start_year, 1, 1) # Starting from 1970 eliminates a lot of noise
4744 for zonename in cls.zonenames():
4745 count += 1
4746 tz = cls.fromname(zonename)
4747 for dt, shift in tz.transitions():
4748 if dt < stats_since:
4749 continue
4750 if shift > ZERO:
4751 gap_count += 1
4752 if (shift, dt) > (max_gap, max_gap_datetime):
4753 max_gap = shift
4754 max_gap_zone = zonename
4755 max_gap_datetime = dt
4756 if (shift, datetime.max - dt) < (min_gap, datetime.max - min_gap_datetime):
4757 min_gap = shift
4758 min_gap_zone = zonename
4759 min_gap_datetime = dt
4760 elif shift < ZERO:
4761 fold_count += 1
4762 shift = -shift
4763 if (shift, dt) > (max_fold, max_fold_datetime):
4764 max_fold = shift
4765 max_fold_zone = zonename
4766 max_fold_datetime = dt
4767 if (shift, datetime.max - dt) < (min_fold, datetime.max - min_fold_datetime):
4768 min_fold = shift
4769 min_fold_zone = zonename
4770 min_fold_datetime = dt
4771 else:
4772 zeros_count += 1
4773 trans_counts = (gap_count, fold_count, zeros_count)
4774 print("Number of zones: %5d" % count)
4775 print("Number of transitions: %5d = %d (gaps) + %d (folds) + %d (zeros)" %
4776 ((sum(trans_counts),) + trans_counts))
4777 print("Min gap: %16s at %s in %s" % (min_gap, min_gap_datetime, min_gap_zone))
4778 print("Max gap: %16s at %s in %s" % (max_gap, max_gap_datetime, max_gap_zone))
4779 print("Min fold: %16s at %s in %s" % (min_fold, min_fold_datetime, min_fold_zone))
4780 print("Max fold: %16s at %s in %s" % (max_fold, max_fold_datetime, max_fold_zone))
4781
4782
4783 def transitions(self):
4784 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
4785 shift = ti[0] - prev_ti[0]
4786 yield datetime.utcfromtimestamp(t), shift
4787
4788 def nondst_folds(self):
4789 """Find all folds with the same value of isdst on both sides of the transition."""
4790 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
4791 shift = ti[0] - prev_ti[0]
4792 if shift < ZERO and ti[1] == prev_ti[1]:
4793 yield datetime.utcfromtimestamp(t), -shift, prev_ti[2], ti[2]
4794
4795 @classmethod
4796 def print_all_nondst_folds(cls, same_abbr=False, start_year=1):
4797 count = 0
4798 for zonename in cls.zonenames():
4799 tz = cls.fromname(zonename)
4800 for dt, shift, prev_abbr, abbr in tz.nondst_folds():
4801 if dt.year < start_year or same_abbr and prev_abbr != abbr:
4802 continue
4803 count += 1
4804 print("%3d) %-30s %s %10s %5s -> %s" %
4805 (count, zonename, dt, shift, prev_abbr, abbr))
4806
4807 def folds(self):
4808 for t, shift in self.transitions():
4809 if shift < ZERO:
4810 yield t, -shift
4811
4812 def gaps(self):
4813 for t, shift in self.transitions():
4814 if shift > ZERO:
4815 yield t, shift
4816
4817 def zeros(self):
4818 for t, shift in self.transitions():
4819 if not shift:
4820 yield t
4821
4822
4823class ZoneInfoTest(unittest.TestCase):
4824 zonename = 'America/New_York'
4825
4826 def setUp(self):
4827 if sys.platform == "win32":
4828 self.skipTest("Skipping zoneinfo tests on Windows")
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04004829 try:
4830 self.tz = ZoneInfo.fromname(self.zonename)
4831 except FileNotFoundError as err:
4832 self.skipTest("Skipping %s: %s" % (self.zonename, err))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004833
4834 def assertEquivDatetimes(self, a, b):
4835 self.assertEqual((a.replace(tzinfo=None), a.fold, id(a.tzinfo)),
4836 (b.replace(tzinfo=None), b.fold, id(b.tzinfo)))
4837
4838 def test_folds(self):
4839 tz = self.tz
4840 for dt, shift in tz.folds():
4841 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
4842 udt = dt + x
4843 ldt = tz.fromutc(udt.replace(tzinfo=tz))
4844 self.assertEqual(ldt.fold, 1)
4845 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
4846 self.assertEquivDatetimes(adt, ldt)
4847 utcoffset = ldt.utcoffset()
4848 self.assertEqual(ldt.replace(tzinfo=None), udt + utcoffset)
4849 # Round trip
4850 self.assertEquivDatetimes(ldt.astimezone(timezone.utc),
4851 udt.replace(tzinfo=timezone.utc))
4852
4853
4854 for x in [-timedelta.resolution, shift]:
4855 udt = dt + x
4856 udt = udt.replace(tzinfo=tz)
4857 ldt = tz.fromutc(udt)
4858 self.assertEqual(ldt.fold, 0)
4859
4860 def test_gaps(self):
4861 tz = self.tz
4862 for dt, shift in tz.gaps():
4863 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
4864 udt = dt + x
4865 udt = udt.replace(tzinfo=tz)
4866 ldt = tz.fromutc(udt)
4867 self.assertEqual(ldt.fold, 0)
4868 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
4869 self.assertEquivDatetimes(adt, ldt)
4870 utcoffset = ldt.utcoffset()
4871 self.assertEqual(ldt.replace(tzinfo=None), udt.replace(tzinfo=None) + utcoffset)
4872 # Create a local time inside the gap
4873 ldt = tz.fromutc(dt.replace(tzinfo=tz)) - shift + x
4874 self.assertLess(ldt.replace(fold=1).utcoffset(),
4875 ldt.replace(fold=0).utcoffset(),
4876 "At %s." % ldt)
4877
4878 for x in [-timedelta.resolution, shift]:
4879 udt = dt + x
4880 ldt = tz.fromutc(udt.replace(tzinfo=tz))
4881 self.assertEqual(ldt.fold, 0)
4882
4883 def test_system_transitions(self):
4884 if ('Riyadh8' in self.zonename or
4885 # From tzdata NEWS file:
4886 # The files solar87, solar88, and solar89 are no longer distributed.
4887 # They were a negative experiment - that is, a demonstration that
4888 # tz data can represent solar time only with some difficulty and error.
4889 # Their presence in the distribution caused confusion, as Riyadh
4890 # civil time was generally not solar time in those years.
4891 self.zonename.startswith('right/')):
4892 self.skipTest("Skipping %s" % self.zonename)
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04004893 tz = self.tz
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004894 TZ = os.environ.get('TZ')
4895 os.environ['TZ'] = self.zonename
4896 try:
4897 _time.tzset()
4898 for udt, shift in tz.transitions():
Alexander Belopolsky10c2dd22016-08-12 19:08:15 -04004899 if udt.year >= 2037:
4900 # System support for times around the end of 32-bit time_t
4901 # and later is flaky on many systems.
4902 break
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004903 s0 = (udt - datetime(1970, 1, 1)) // SEC
4904 ss = shift // SEC # shift seconds
4905 for x in [-40 * 3600, -20*3600, -1, 0,
4906 ss - 1, ss + 20 * 3600, ss + 40 * 3600]:
4907 s = s0 + x
4908 sdt = datetime.fromtimestamp(s)
4909 tzdt = datetime.fromtimestamp(s, tz).replace(tzinfo=None)
4910 self.assertEquivDatetimes(sdt, tzdt)
4911 s1 = sdt.timestamp()
4912 self.assertEqual(s, s1)
4913 if ss > 0: # gap
4914 # Create local time inside the gap
4915 dt = datetime.fromtimestamp(s0) - shift / 2
4916 ts0 = dt.timestamp()
4917 ts1 = dt.replace(fold=1).timestamp()
4918 self.assertEqual(ts0, s0 + ss / 2)
4919 self.assertEqual(ts1, s0 - ss / 2)
4920 finally:
4921 if TZ is None:
4922 del os.environ['TZ']
4923 else:
4924 os.environ['TZ'] = TZ
4925 _time.tzset()
4926
4927
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04004928class ZoneInfoCompleteTest(unittest.TestSuite):
4929 def __init__(self):
4930 tests = []
4931 if is_resource_enabled('tzdata'):
4932 for name in ZoneInfo.zonenames():
4933 Test = type('ZoneInfoTest[%s]' % name, (ZoneInfoTest,), {})
4934 Test.zonename = name
4935 for method in dir(Test):
4936 if method.startswith('test_'):
4937 tests.append(Test(method))
4938 super().__init__(tests)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004939
4940# Iran had a sub-minute UTC offset before 1946.
4941class IranTest(ZoneInfoTest):
Alexander Belopolsky07e2a0a2016-07-30 11:41:02 -04004942 zonename = 'Asia/Tehran'
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004943
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04004944def load_tests(loader, standard_tests, pattern):
4945 standard_tests.addTest(ZoneInfoCompleteTest())
4946 return standard_tests
4947
4948
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004949if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -05004950 unittest.main()