blob: c5f91fbe1840baef1c52b70c7e53a715ebcd5588 [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
1523 def test_subclass_date(self):
1524
1525 class C(self.theclass):
1526 theAnswer = 42
1527
1528 def __new__(cls, *args, **kws):
1529 temp = kws.copy()
1530 extra = temp.pop('extra')
1531 result = self.theclass.__new__(cls, *args, **temp)
1532 result.extra = extra
1533 return result
1534
1535 def newmeth(self, start):
1536 return start + self.year + self.month
1537
1538 args = 2003, 4, 14
1539
1540 dt1 = self.theclass(*args)
1541 dt2 = C(*args, **{'extra': 7})
1542
1543 self.assertEqual(dt2.__class__, C)
1544 self.assertEqual(dt2.theAnswer, 42)
1545 self.assertEqual(dt2.extra, 7)
1546 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1547 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1548
1549 def test_pickling_subclass_date(self):
1550
1551 args = 6, 7, 23
1552 orig = SubclassDate(*args)
1553 for pickler, unpickler, proto in pickle_choices:
1554 green = pickler.dumps(orig, proto)
1555 derived = unpickler.loads(green)
1556 self.assertEqual(orig, derived)
1557
1558 def test_backdoor_resistance(self):
1559 # For fast unpickling, the constructor accepts a pickle byte string.
1560 # This is a low-overhead backdoor. A user can (by intent or
1561 # mistake) pass a string directly, which (if it's the right length)
1562 # will get treated like a pickle, and bypass the normal sanity
1563 # checks in the constructor. This can create insane objects.
1564 # The constructor doesn't want to burn the time to validate all
1565 # fields, but does check the month field. This stops, e.g.,
1566 # datetime.datetime('1995-03-25') from yielding an insane object.
1567 base = b'1995-03-25'
1568 if not issubclass(self.theclass, datetime):
1569 base = base[:4]
1570 for month_byte in b'9', b'\0', b'\r', b'\xff':
1571 self.assertRaises(TypeError, self.theclass,
1572 base[:2] + month_byte + base[3:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001573 if issubclass(self.theclass, datetime):
1574 # Good bytes, but bad tzinfo:
1575 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
1576 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001577
1578 for ord_byte in range(1, 13):
1579 # This shouldn't blow up because of the month byte alone. If
1580 # the implementation changes to do more-careful checking, it may
1581 # blow up because other fields are insane.
1582 self.theclass(base[:2] + bytes([ord_byte]) + base[3:])
1583
1584#############################################################################
1585# datetime tests
1586
1587class SubclassDatetime(datetime):
1588 sub_var = 1
1589
1590class TestDateTime(TestDate):
1591
1592 theclass = datetime
1593
1594 def test_basic_attributes(self):
1595 dt = self.theclass(2002, 3, 1, 12, 0)
1596 self.assertEqual(dt.year, 2002)
1597 self.assertEqual(dt.month, 3)
1598 self.assertEqual(dt.day, 1)
1599 self.assertEqual(dt.hour, 12)
1600 self.assertEqual(dt.minute, 0)
1601 self.assertEqual(dt.second, 0)
1602 self.assertEqual(dt.microsecond, 0)
1603
1604 def test_basic_attributes_nonzero(self):
1605 # Make sure all attributes are non-zero so bugs in
1606 # bit-shifting access show up.
1607 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1608 self.assertEqual(dt.year, 2002)
1609 self.assertEqual(dt.month, 3)
1610 self.assertEqual(dt.day, 1)
1611 self.assertEqual(dt.hour, 12)
1612 self.assertEqual(dt.minute, 59)
1613 self.assertEqual(dt.second, 59)
1614 self.assertEqual(dt.microsecond, 8000)
1615
1616 def test_roundtrip(self):
1617 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1618 self.theclass.now()):
1619 # Verify dt -> string -> datetime identity.
1620 s = repr(dt)
1621 self.assertTrue(s.startswith('datetime.'))
1622 s = s[9:]
1623 dt2 = eval(s)
1624 self.assertEqual(dt, dt2)
1625
1626 # Verify identity via reconstructing from pieces.
1627 dt2 = self.theclass(dt.year, dt.month, dt.day,
1628 dt.hour, dt.minute, dt.second,
1629 dt.microsecond)
1630 self.assertEqual(dt, dt2)
1631
1632 def test_isoformat(self):
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001633 t = self.theclass(1, 2, 3, 4, 5, 1, 123)
1634 self.assertEqual(t.isoformat(), "0001-02-03T04:05:01.000123")
1635 self.assertEqual(t.isoformat('T'), "0001-02-03T04:05:01.000123")
1636 self.assertEqual(t.isoformat(' '), "0001-02-03 04:05:01.000123")
1637 self.assertEqual(t.isoformat('\x00'), "0001-02-03\x0004:05:01.000123")
1638 self.assertEqual(t.isoformat(timespec='hours'), "0001-02-03T04")
1639 self.assertEqual(t.isoformat(timespec='minutes'), "0001-02-03T04:05")
1640 self.assertEqual(t.isoformat(timespec='seconds'), "0001-02-03T04:05:01")
1641 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1642 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000123")
1643 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01.000123")
1644 self.assertEqual(t.isoformat(sep=' ', timespec='minutes'), "0001-02-03 04:05")
1645 self.assertRaises(ValueError, t.isoformat, timespec='foo')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001646 # str is ISO format with the separator forced to a blank.
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001647 self.assertEqual(str(t), "0001-02-03 04:05:01.000123")
1648
1649 t = self.theclass(1, 2, 3, 4, 5, 1, 999500, tzinfo=timezone.utc)
1650 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999+00:00")
1651
1652 t = self.theclass(1, 2, 3, 4, 5, 1, 999500)
1653 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999")
1654
1655 t = self.theclass(1, 2, 3, 4, 5, 1)
1656 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01")
1657 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1658 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000000")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001659
1660 t = self.theclass(2, 3, 2)
1661 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1662 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1663 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1664 # str is ISO format with the separator forced to a blank.
1665 self.assertEqual(str(t), "0002-03-02 00:00:00")
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001666 # ISO format with timezone
1667 tz = FixedOffset(timedelta(seconds=16), 'XXX')
1668 t = self.theclass(2, 3, 2, tzinfo=tz)
1669 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00+00:00:16")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001670
1671 def test_format(self):
1672 dt = self.theclass(2007, 9, 10, 4, 5, 1, 123)
1673 self.assertEqual(dt.__format__(''), str(dt))
1674
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02001675 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001676 dt.__format__(123)
1677
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001678 # check that a derived class's __str__() gets called
1679 class A(self.theclass):
1680 def __str__(self):
1681 return 'A'
1682 a = A(2007, 9, 10, 4, 5, 1, 123)
1683 self.assertEqual(a.__format__(''), 'A')
1684
1685 # check that a derived class's strftime gets called
1686 class B(self.theclass):
1687 def strftime(self, format_spec):
1688 return 'B'
1689 b = B(2007, 9, 10, 4, 5, 1, 123)
1690 self.assertEqual(b.__format__(''), str(dt))
1691
1692 for fmt in ["m:%m d:%d y:%y",
1693 "m:%m d:%d y:%y H:%H M:%M S:%S",
1694 "%z %Z",
1695 ]:
1696 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1697 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1698 self.assertEqual(b.__format__(fmt), 'B')
1699
1700 def test_more_ctime(self):
1701 # Test fields that TestDate doesn't touch.
1702 import time
1703
1704 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1705 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1706 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1707 # out. The difference is that t.ctime() produces " 2" for the day,
1708 # but platform ctime() produces "02" for the day. According to
1709 # C99, t.ctime() is correct here.
1710 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1711
1712 # So test a case where that difference doesn't matter.
1713 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1714 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1715
1716 def test_tz_independent_comparing(self):
1717 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1718 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1719 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1720 self.assertEqual(dt1, dt3)
1721 self.assertTrue(dt2 > dt3)
1722
1723 # Make sure comparison doesn't forget microseconds, and isn't done
1724 # via comparing a float timestamp (an IEEE double doesn't have enough
1725 # precision to span microsecond resolution across years 1 thru 9999,
1726 # so comparing via timestamp necessarily calls some distinct values
1727 # equal).
1728 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1729 us = timedelta(microseconds=1)
1730 dt2 = dt1 + us
1731 self.assertEqual(dt2 - dt1, us)
1732 self.assertTrue(dt1 < dt2)
1733
1734 def test_strftime_with_bad_tzname_replace(self):
1735 # verify ok if tzinfo.tzname().replace() returns a non-string
1736 class MyTzInfo(FixedOffset):
1737 def tzname(self, dt):
1738 class MyStr(str):
1739 def replace(self, *args):
1740 return None
1741 return MyStr('name')
1742 t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
1743 self.assertRaises(TypeError, t.strftime, '%Z')
1744
1745 def test_bad_constructor_arguments(self):
1746 # bad years
1747 self.theclass(MINYEAR, 1, 1) # no exception
1748 self.theclass(MAXYEAR, 1, 1) # no exception
1749 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1750 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1751 # bad months
1752 self.theclass(2000, 1, 1) # no exception
1753 self.theclass(2000, 12, 1) # no exception
1754 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1755 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1756 # bad days
1757 self.theclass(2000, 2, 29) # no exception
1758 self.theclass(2004, 2, 29) # no exception
1759 self.theclass(2400, 2, 29) # no exception
1760 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1761 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1762 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1763 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1764 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1765 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1766 # bad hours
1767 self.theclass(2000, 1, 31, 0) # no exception
1768 self.theclass(2000, 1, 31, 23) # no exception
1769 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1770 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1771 # bad minutes
1772 self.theclass(2000, 1, 31, 23, 0) # no exception
1773 self.theclass(2000, 1, 31, 23, 59) # no exception
1774 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1775 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1776 # bad seconds
1777 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1778 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1779 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1780 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1781 # bad microseconds
1782 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1783 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1784 self.assertRaises(ValueError, self.theclass,
1785 2000, 1, 31, 23, 59, 59, -1)
1786 self.assertRaises(ValueError, self.theclass,
1787 2000, 1, 31, 23, 59, 59,
1788 1000000)
Alexander Belopolsky47649ab2016-08-08 17:05:40 -04001789 # bad fold
1790 self.assertRaises(ValueError, self.theclass,
1791 2000, 1, 31, fold=-1)
1792 self.assertRaises(ValueError, self.theclass,
1793 2000, 1, 31, fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001794 # Positional fold:
1795 self.assertRaises(TypeError, self.theclass,
1796 2000, 1, 31, 23, 59, 59, 0, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04001797
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001798 def test_hash_equality(self):
1799 d = self.theclass(2000, 12, 31, 23, 30, 17)
1800 e = self.theclass(2000, 12, 31, 23, 30, 17)
1801 self.assertEqual(d, e)
1802 self.assertEqual(hash(d), hash(e))
1803
1804 dic = {d: 1}
1805 dic[e] = 2
1806 self.assertEqual(len(dic), 1)
1807 self.assertEqual(dic[d], 2)
1808 self.assertEqual(dic[e], 2)
1809
1810 d = self.theclass(2001, 1, 1, 0, 5, 17)
1811 e = self.theclass(2001, 1, 1, 0, 5, 17)
1812 self.assertEqual(d, e)
1813 self.assertEqual(hash(d), hash(e))
1814
1815 dic = {d: 1}
1816 dic[e] = 2
1817 self.assertEqual(len(dic), 1)
1818 self.assertEqual(dic[d], 2)
1819 self.assertEqual(dic[e], 2)
1820
1821 def test_computations(self):
1822 a = self.theclass(2002, 1, 31)
1823 b = self.theclass(1956, 1, 31)
1824 diff = a-b
1825 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1826 self.assertEqual(diff.seconds, 0)
1827 self.assertEqual(diff.microseconds, 0)
1828 a = self.theclass(2002, 3, 2, 17, 6)
1829 millisec = timedelta(0, 0, 1000)
1830 hour = timedelta(0, 3600)
1831 day = timedelta(1)
1832 week = timedelta(7)
1833 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1834 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1835 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1836 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1837 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1838 self.assertEqual(a - hour, a + -hour)
1839 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1840 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1841 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1842 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1843 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1844 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1845 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1846 self.assertEqual((a + week) - a, week)
1847 self.assertEqual((a + day) - a, day)
1848 self.assertEqual((a + hour) - a, hour)
1849 self.assertEqual((a + millisec) - a, millisec)
1850 self.assertEqual((a - week) - a, -week)
1851 self.assertEqual((a - day) - a, -day)
1852 self.assertEqual((a - hour) - a, -hour)
1853 self.assertEqual((a - millisec) - a, -millisec)
1854 self.assertEqual(a - (a + week), -week)
1855 self.assertEqual(a - (a + day), -day)
1856 self.assertEqual(a - (a + hour), -hour)
1857 self.assertEqual(a - (a + millisec), -millisec)
1858 self.assertEqual(a - (a - week), week)
1859 self.assertEqual(a - (a - day), day)
1860 self.assertEqual(a - (a - hour), hour)
1861 self.assertEqual(a - (a - millisec), millisec)
1862 self.assertEqual(a + (week + day + hour + millisec),
1863 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1864 self.assertEqual(a + (week + day + hour + millisec),
1865 (((a + week) + day) + hour) + millisec)
1866 self.assertEqual(a - (week + day + hour + millisec),
1867 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1868 self.assertEqual(a - (week + day + hour + millisec),
1869 (((a - week) - day) - hour) - millisec)
1870 # Add/sub ints or floats should be illegal
1871 for i in 1, 1.0:
1872 self.assertRaises(TypeError, lambda: a+i)
1873 self.assertRaises(TypeError, lambda: a-i)
1874 self.assertRaises(TypeError, lambda: i+a)
1875 self.assertRaises(TypeError, lambda: i-a)
1876
1877 # delta - datetime is senseless.
1878 self.assertRaises(TypeError, lambda: day - a)
1879 # mixing datetime and (delta or datetime) via * or // is senseless
1880 self.assertRaises(TypeError, lambda: day * a)
1881 self.assertRaises(TypeError, lambda: a * day)
1882 self.assertRaises(TypeError, lambda: day // a)
1883 self.assertRaises(TypeError, lambda: a // day)
1884 self.assertRaises(TypeError, lambda: a * a)
1885 self.assertRaises(TypeError, lambda: a // a)
1886 # datetime + datetime is senseless
1887 self.assertRaises(TypeError, lambda: a + a)
1888
1889 def test_pickling(self):
1890 args = 6, 7, 23, 20, 59, 1, 64**2
1891 orig = self.theclass(*args)
1892 for pickler, unpickler, proto in pickle_choices:
1893 green = pickler.dumps(orig, proto)
1894 derived = unpickler.loads(green)
1895 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02001896 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001897
1898 def test_more_pickling(self):
1899 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
Serhiy Storchakabad12572014-12-15 14:03:42 +02001900 for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1901 s = pickle.dumps(a, proto)
1902 b = pickle.loads(s)
1903 self.assertEqual(b.year, 2003)
1904 self.assertEqual(b.month, 2)
1905 self.assertEqual(b.day, 7)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001906
1907 def test_pickling_subclass_datetime(self):
1908 args = 6, 7, 23, 20, 59, 1, 64**2
1909 orig = SubclassDatetime(*args)
1910 for pickler, unpickler, proto in pickle_choices:
1911 green = pickler.dumps(orig, proto)
1912 derived = unpickler.loads(green)
1913 self.assertEqual(orig, derived)
1914
1915 def test_more_compare(self):
1916 # The test_compare() inherited from TestDate covers the error cases.
1917 # We just want to test lexicographic ordering on the members datetime
1918 # has that date lacks.
1919 args = [2000, 11, 29, 20, 58, 16, 999998]
1920 t1 = self.theclass(*args)
1921 t2 = self.theclass(*args)
1922 self.assertEqual(t1, t2)
1923 self.assertTrue(t1 <= t2)
1924 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001925 self.assertFalse(t1 != t2)
1926 self.assertFalse(t1 < t2)
1927 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001928
1929 for i in range(len(args)):
1930 newargs = args[:]
1931 newargs[i] = args[i] + 1
1932 t2 = self.theclass(*newargs) # this is larger than t1
1933 self.assertTrue(t1 < t2)
1934 self.assertTrue(t2 > t1)
1935 self.assertTrue(t1 <= t2)
1936 self.assertTrue(t2 >= t1)
1937 self.assertTrue(t1 != t2)
1938 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001939 self.assertFalse(t1 == t2)
1940 self.assertFalse(t2 == t1)
1941 self.assertFalse(t1 > t2)
1942 self.assertFalse(t2 < t1)
1943 self.assertFalse(t1 >= t2)
1944 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001945
1946
1947 # A helper for timestamp constructor tests.
1948 def verify_field_equality(self, expected, got):
1949 self.assertEqual(expected.tm_year, got.year)
1950 self.assertEqual(expected.tm_mon, got.month)
1951 self.assertEqual(expected.tm_mday, got.day)
1952 self.assertEqual(expected.tm_hour, got.hour)
1953 self.assertEqual(expected.tm_min, got.minute)
1954 self.assertEqual(expected.tm_sec, got.second)
1955
1956 def test_fromtimestamp(self):
1957 import time
1958
1959 ts = time.time()
1960 expected = time.localtime(ts)
1961 got = self.theclass.fromtimestamp(ts)
1962 self.verify_field_equality(expected, got)
1963
1964 def test_utcfromtimestamp(self):
1965 import time
1966
1967 ts = time.time()
1968 expected = time.gmtime(ts)
1969 got = self.theclass.utcfromtimestamp(ts)
1970 self.verify_field_equality(expected, got)
1971
Alexander Belopolskya4415142012-06-08 12:33:09 -04001972 # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in
1973 # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0).
1974 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
1975 def test_timestamp_naive(self):
1976 t = self.theclass(1970, 1, 1)
1977 self.assertEqual(t.timestamp(), 18000.0)
1978 t = self.theclass(1970, 1, 1, 1, 2, 3, 4)
1979 self.assertEqual(t.timestamp(),
1980 18000.0 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001981 # Missing hour
1982 t0 = self.theclass(2012, 3, 11, 2, 30)
1983 t1 = t0.replace(fold=1)
1984 self.assertEqual(self.theclass.fromtimestamp(t1.timestamp()),
1985 t0 - timedelta(hours=1))
1986 self.assertEqual(self.theclass.fromtimestamp(t0.timestamp()),
1987 t1 + timedelta(hours=1))
Alexander Belopolskya4415142012-06-08 12:33:09 -04001988 # Ambiguous hour defaults to DST
1989 t = self.theclass(2012, 11, 4, 1, 30)
1990 self.assertEqual(self.theclass.fromtimestamp(t.timestamp()), t)
1991
1992 # Timestamp may raise an overflow error on some platforms
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001993 # XXX: Do we care to support the first and last year?
1994 for t in [self.theclass(2,1,1), self.theclass(9998,12,12)]:
Alexander Belopolskya4415142012-06-08 12:33:09 -04001995 try:
1996 s = t.timestamp()
1997 except OverflowError:
1998 pass
1999 else:
2000 self.assertEqual(self.theclass.fromtimestamp(s), t)
2001
2002 def test_timestamp_aware(self):
2003 t = self.theclass(1970, 1, 1, tzinfo=timezone.utc)
2004 self.assertEqual(t.timestamp(), 0.0)
2005 t = self.theclass(1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone.utc)
2006 self.assertEqual(t.timestamp(),
2007 3600 + 2*60 + 3 + 4*1e-6)
2008 t = self.theclass(1970, 1, 1, 1, 2, 3, 4,
2009 tzinfo=timezone(timedelta(hours=-5), 'EST'))
2010 self.assertEqual(t.timestamp(),
2011 18000 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002012
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002013 @support.run_with_tz('MSK-03') # Something east of Greenwich
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002014 def test_microsecond_rounding(self):
Victor Stinner5ebfe422015-09-18 14:52:15 +02002015 for fts in [self.theclass.fromtimestamp,
2016 self.theclass.utcfromtimestamp]:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002017 zero = fts(0)
2018 self.assertEqual(zero.second, 0)
2019 self.assertEqual(zero.microsecond, 0)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002020 one = fts(1e-6)
Victor Stinner8050ca92012-03-14 00:17:05 +01002021 try:
2022 minus_one = fts(-1e-6)
2023 except OSError:
2024 # localtime(-1) and gmtime(-1) is not supported on Windows
2025 pass
2026 else:
2027 self.assertEqual(minus_one.second, 59)
2028 self.assertEqual(minus_one.microsecond, 999999)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002029
Victor Stinner8050ca92012-03-14 00:17:05 +01002030 t = fts(-1e-8)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002031 self.assertEqual(t, zero)
Victor Stinner8050ca92012-03-14 00:17:05 +01002032 t = fts(-9e-7)
2033 self.assertEqual(t, minus_one)
2034 t = fts(-1e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002035 self.assertEqual(t, zero)
Victor Stinner8820a352015-09-05 10:50:20 +02002036 t = fts(-1/2**7)
2037 self.assertEqual(t.second, 59)
Victor Stinner7667f582015-09-09 01:02:23 +02002038 self.assertEqual(t.microsecond, 992188)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002039
2040 t = fts(1e-7)
2041 self.assertEqual(t, zero)
2042 t = fts(9e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002043 self.assertEqual(t, one)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002044 t = fts(0.99999949)
2045 self.assertEqual(t.second, 0)
2046 self.assertEqual(t.microsecond, 999999)
2047 t = fts(0.9999999)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002048 self.assertEqual(t.second, 1)
2049 self.assertEqual(t.microsecond, 0)
Victor Stinneradfefa52015-09-04 23:57:25 +02002050 t = fts(1/2**7)
2051 self.assertEqual(t.second, 0)
Victor Stinner7667f582015-09-09 01:02:23 +02002052 self.assertEqual(t.microsecond, 7812)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002053
Victor Stinnerb67f0962017-02-10 10:34:02 +01002054 def test_timestamp_limits(self):
2055 # minimum timestamp
2056 min_dt = self.theclass.min.replace(tzinfo=timezone.utc)
2057 min_ts = min_dt.timestamp()
Victor Stinner6f37e362017-02-10 11:45:14 +01002058 try:
2059 # date 0001-01-01 00:00:00+00:00: timestamp=-62135596800
2060 self.assertEqual(self.theclass.fromtimestamp(min_ts, tz=timezone.utc),
2061 min_dt)
Victor Stinner2a35c932017-02-10 12:37:21 +01002062 except (OverflowError, OSError) as exc:
2063 # the date 0001-01-01 doesn't fit into 32-bit time_t,
2064 # or platform doesn't support such very old date
Victor Stinner6f37e362017-02-10 11:45:14 +01002065 self.skipTest(str(exc))
Victor Stinnerb67f0962017-02-10 10:34:02 +01002066
2067 # maximum timestamp: set seconds to zero to avoid rounding issues
2068 max_dt = self.theclass.max.replace(tzinfo=timezone.utc,
2069 second=0, microsecond=0)
2070 max_ts = max_dt.timestamp()
2071 # date 9999-12-31 23:59:00+00:00: timestamp 253402300740
2072 self.assertEqual(self.theclass.fromtimestamp(max_ts, tz=timezone.utc),
2073 max_dt)
2074
2075 # number of seconds greater than 1 year: make sure that the new date
2076 # is not valid in datetime.datetime limits
2077 delta = 3600 * 24 * 400
2078
2079 # too small
2080 ts = min_ts - delta
2081 # converting a Python int to C time_t can raise a OverflowError,
2082 # especially on 32-bit platforms.
2083 with self.assertRaises((ValueError, OverflowError)):
2084 self.theclass.fromtimestamp(ts)
2085 with self.assertRaises((ValueError, OverflowError)):
2086 self.theclass.utcfromtimestamp(ts)
2087
2088 # too big
2089 ts = max_dt.timestamp() + delta
2090 with self.assertRaises((ValueError, OverflowError)):
2091 self.theclass.fromtimestamp(ts)
2092 with self.assertRaises((ValueError, OverflowError)):
2093 self.theclass.utcfromtimestamp(ts)
2094
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002095 def test_insane_fromtimestamp(self):
2096 # It's possible that some platform maps time_t to double,
2097 # and that this test will fail there. This test should
2098 # exempt such platforms (provided they return reasonable
2099 # results!).
2100 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002101 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002102 insane)
2103
2104 def test_insane_utcfromtimestamp(self):
2105 # It's possible that some platform maps time_t to double,
2106 # and that this test will fail there. This test should
2107 # exempt such platforms (provided they return reasonable
2108 # results!).
2109 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002110 self.assertRaises(OverflowError, self.theclass.utcfromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002111 insane)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002112
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002113 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2114 def test_negative_float_fromtimestamp(self):
2115 # The result is tz-dependent; at least test that this doesn't
2116 # fail (like it did before bug 1646728 was fixed).
2117 self.theclass.fromtimestamp(-1.05)
2118
2119 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2120 def test_negative_float_utcfromtimestamp(self):
2121 d = self.theclass.utcfromtimestamp(-1.05)
2122 self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
2123
2124 def test_utcnow(self):
2125 import time
2126
2127 # Call it a success if utcnow() and utcfromtimestamp() are within
2128 # a second of each other.
2129 tolerance = timedelta(seconds=1)
2130 for dummy in range(3):
2131 from_now = self.theclass.utcnow()
2132 from_timestamp = self.theclass.utcfromtimestamp(time.time())
2133 if abs(from_timestamp - from_now) <= tolerance:
2134 break
2135 # Else try again a few times.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002136 self.assertLessEqual(abs(from_timestamp - from_now), tolerance)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002137
2138 def test_strptime(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002139 string = '2004-12-01 13:02:47.197'
2140 format = '%Y-%m-%d %H:%M:%S.%f'
2141 expected = _strptime._strptime_datetime(self.theclass, string, format)
2142 got = self.theclass.strptime(string, format)
2143 self.assertEqual(expected, got)
2144 self.assertIs(type(expected), self.theclass)
2145 self.assertIs(type(got), self.theclass)
2146
2147 strptime = self.theclass.strptime
2148 self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
2149 self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
Mario Corchero32318932017-10-26 01:35:41 +01002150 self.assertEqual(
2151 strptime("-00:02:01.000003", "%z").utcoffset(),
2152 -timedelta(minutes=2, seconds=1, microseconds=3)
2153 )
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002154 # Only local timezone and UTC are supported
2155 for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
2156 (-_time.timezone, _time.tzname[0])):
2157 if tzseconds < 0:
2158 sign = '-'
2159 seconds = -tzseconds
2160 else:
2161 sign ='+'
2162 seconds = tzseconds
2163 hours, minutes = divmod(seconds//60, 60)
2164 dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
Martin Panterfca22322015-11-16 09:22:19 +00002165 dt = strptime(dtstr, "%z %Z")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002166 self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
2167 self.assertEqual(dt.tzname(), tzname)
2168 # Can produce inconsistent datetime
2169 dtstr, fmt = "+1234 UTC", "%z %Z"
2170 dt = strptime(dtstr, fmt)
2171 self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE)
2172 self.assertEqual(dt.tzname(), 'UTC')
2173 # yet will roundtrip
2174 self.assertEqual(dt.strftime(fmt), dtstr)
2175
2176 # Produce naive datetime if no %z is provided
2177 self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
2178
2179 with self.assertRaises(ValueError): strptime("-2400", "%z")
2180 with self.assertRaises(ValueError): strptime("-000", "%z")
2181
2182 def test_more_timetuple(self):
2183 # This tests fields beyond those tested by the TestDate.test_timetuple.
2184 t = self.theclass(2004, 12, 31, 6, 22, 33)
2185 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
2186 self.assertEqual(t.timetuple(),
2187 (t.year, t.month, t.day,
2188 t.hour, t.minute, t.second,
2189 t.weekday(),
2190 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
2191 -1))
2192 tt = t.timetuple()
2193 self.assertEqual(tt.tm_year, t.year)
2194 self.assertEqual(tt.tm_mon, t.month)
2195 self.assertEqual(tt.tm_mday, t.day)
2196 self.assertEqual(tt.tm_hour, t.hour)
2197 self.assertEqual(tt.tm_min, t.minute)
2198 self.assertEqual(tt.tm_sec, t.second)
2199 self.assertEqual(tt.tm_wday, t.weekday())
2200 self.assertEqual(tt.tm_yday, t.toordinal() -
2201 date(t.year, 1, 1).toordinal() + 1)
2202 self.assertEqual(tt.tm_isdst, -1)
2203
2204 def test_more_strftime(self):
2205 # This tests fields beyond those tested by the TestDate.test_strftime.
2206 t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
2207 self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
2208 "12 31 04 000047 33 22 06 366")
Alexander Belopolsky018d3532017-07-31 10:26:50 -04002209 tz = timezone(-timedelta(hours=2, seconds=33, microseconds=123))
2210 t = t.replace(tzinfo=tz)
2211 self.assertEqual(t.strftime("%z"), "-020033.000123")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002212
2213 def test_extract(self):
2214 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2215 self.assertEqual(dt.date(), date(2002, 3, 4))
2216 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2217
2218 def test_combine(self):
2219 d = date(2002, 3, 4)
2220 t = time(18, 45, 3, 1234)
2221 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2222 combine = self.theclass.combine
2223 dt = combine(d, t)
2224 self.assertEqual(dt, expected)
2225
2226 dt = combine(time=t, date=d)
2227 self.assertEqual(dt, expected)
2228
2229 self.assertEqual(d, dt.date())
2230 self.assertEqual(t, dt.time())
2231 self.assertEqual(dt, combine(dt.date(), dt.time()))
2232
2233 self.assertRaises(TypeError, combine) # need an arg
2234 self.assertRaises(TypeError, combine, d) # need two args
2235 self.assertRaises(TypeError, combine, t, d) # args reversed
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002236 self.assertRaises(TypeError, combine, d, t, 1) # wrong tzinfo type
2237 self.assertRaises(TypeError, combine, d, t, 1, 2) # too many args
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002238 self.assertRaises(TypeError, combine, "date", "time") # wrong types
2239 self.assertRaises(TypeError, combine, d, "time") # wrong type
2240 self.assertRaises(TypeError, combine, "date", t) # wrong type
2241
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002242 # tzinfo= argument
2243 dt = combine(d, t, timezone.utc)
2244 self.assertIs(dt.tzinfo, timezone.utc)
2245 dt = combine(d, t, tzinfo=timezone.utc)
2246 self.assertIs(dt.tzinfo, timezone.utc)
2247 t = time()
2248 dt = combine(dt, t)
2249 self.assertEqual(dt.date(), d)
2250 self.assertEqual(dt.time(), t)
2251
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002252 def test_replace(self):
2253 cls = self.theclass
2254 args = [1, 2, 3, 4, 5, 6, 7]
2255 base = cls(*args)
2256 self.assertEqual(base, base.replace())
2257
2258 i = 0
2259 for name, newval in (("year", 2),
2260 ("month", 3),
2261 ("day", 4),
2262 ("hour", 5),
2263 ("minute", 6),
2264 ("second", 7),
2265 ("microsecond", 8)):
2266 newargs = args[:]
2267 newargs[i] = newval
2268 expected = cls(*newargs)
2269 got = base.replace(**{name: newval})
2270 self.assertEqual(expected, got)
2271 i += 1
2272
2273 # Out of bounds.
2274 base = cls(2000, 2, 29)
2275 self.assertRaises(ValueError, base.replace, year=2001)
2276
2277 def test_astimezone(self):
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002278 return # The rest is no longer applicable
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002279 # Pretty boring! The TZ test is more interesting here. astimezone()
2280 # simply can't be applied to a naive object.
2281 dt = self.theclass.now()
2282 f = FixedOffset(44, "")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04002283 self.assertRaises(ValueError, dt.astimezone) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002284 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
2285 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
2286 self.assertRaises(ValueError, dt.astimezone, f) # naive
2287 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
2288
2289 class Bogus(tzinfo):
2290 def utcoffset(self, dt): return None
2291 def dst(self, dt): return timedelta(0)
2292 bog = Bogus()
2293 self.assertRaises(ValueError, dt.astimezone, bog) # naive
2294 self.assertRaises(ValueError,
2295 dt.replace(tzinfo=bog).astimezone, f)
2296
2297 class AlsoBogus(tzinfo):
2298 def utcoffset(self, dt): return timedelta(0)
2299 def dst(self, dt): return None
2300 alsobog = AlsoBogus()
2301 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
2302
2303 def test_subclass_datetime(self):
2304
2305 class C(self.theclass):
2306 theAnswer = 42
2307
2308 def __new__(cls, *args, **kws):
2309 temp = kws.copy()
2310 extra = temp.pop('extra')
2311 result = self.theclass.__new__(cls, *args, **temp)
2312 result.extra = extra
2313 return result
2314
2315 def newmeth(self, start):
2316 return start + self.year + self.month + self.second
2317
2318 args = 2003, 4, 14, 12, 13, 41
2319
2320 dt1 = self.theclass(*args)
2321 dt2 = C(*args, **{'extra': 7})
2322
2323 self.assertEqual(dt2.__class__, C)
2324 self.assertEqual(dt2.theAnswer, 42)
2325 self.assertEqual(dt2.extra, 7)
2326 self.assertEqual(dt1.toordinal(), dt2.toordinal())
2327 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
2328 dt1.second - 7)
2329
2330class TestSubclassDateTime(TestDateTime):
2331 theclass = SubclassDatetime
2332 # Override tests not designed for subclass
Zachary Ware9fe6d862013-12-08 00:20:35 -06002333 @unittest.skip('not appropriate for subclasses')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002334 def test_roundtrip(self):
2335 pass
2336
2337class SubclassTime(time):
2338 sub_var = 1
2339
2340class TestTime(HarmlessMixedComparison, unittest.TestCase):
2341
2342 theclass = time
2343
2344 def test_basic_attributes(self):
2345 t = self.theclass(12, 0)
2346 self.assertEqual(t.hour, 12)
2347 self.assertEqual(t.minute, 0)
2348 self.assertEqual(t.second, 0)
2349 self.assertEqual(t.microsecond, 0)
2350
2351 def test_basic_attributes_nonzero(self):
2352 # Make sure all attributes are non-zero so bugs in
2353 # bit-shifting access show up.
2354 t = self.theclass(12, 59, 59, 8000)
2355 self.assertEqual(t.hour, 12)
2356 self.assertEqual(t.minute, 59)
2357 self.assertEqual(t.second, 59)
2358 self.assertEqual(t.microsecond, 8000)
2359
2360 def test_roundtrip(self):
2361 t = self.theclass(1, 2, 3, 4)
2362
2363 # Verify t -> string -> time identity.
2364 s = repr(t)
2365 self.assertTrue(s.startswith('datetime.'))
2366 s = s[9:]
2367 t2 = eval(s)
2368 self.assertEqual(t, t2)
2369
2370 # Verify identity via reconstructing from pieces.
2371 t2 = self.theclass(t.hour, t.minute, t.second,
2372 t.microsecond)
2373 self.assertEqual(t, t2)
2374
2375 def test_comparing(self):
2376 args = [1, 2, 3, 4]
2377 t1 = self.theclass(*args)
2378 t2 = self.theclass(*args)
2379 self.assertEqual(t1, t2)
2380 self.assertTrue(t1 <= t2)
2381 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002382 self.assertFalse(t1 != t2)
2383 self.assertFalse(t1 < t2)
2384 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002385
2386 for i in range(len(args)):
2387 newargs = args[:]
2388 newargs[i] = args[i] + 1
2389 t2 = self.theclass(*newargs) # this is larger than t1
2390 self.assertTrue(t1 < t2)
2391 self.assertTrue(t2 > t1)
2392 self.assertTrue(t1 <= t2)
2393 self.assertTrue(t2 >= t1)
2394 self.assertTrue(t1 != t2)
2395 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002396 self.assertFalse(t1 == t2)
2397 self.assertFalse(t2 == t1)
2398 self.assertFalse(t1 > t2)
2399 self.assertFalse(t2 < t1)
2400 self.assertFalse(t1 >= t2)
2401 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002402
2403 for badarg in OTHERSTUFF:
2404 self.assertEqual(t1 == badarg, False)
2405 self.assertEqual(t1 != badarg, True)
2406 self.assertEqual(badarg == t1, False)
2407 self.assertEqual(badarg != t1, True)
2408
2409 self.assertRaises(TypeError, lambda: t1 <= badarg)
2410 self.assertRaises(TypeError, lambda: t1 < badarg)
2411 self.assertRaises(TypeError, lambda: t1 > badarg)
2412 self.assertRaises(TypeError, lambda: t1 >= badarg)
2413 self.assertRaises(TypeError, lambda: badarg <= t1)
2414 self.assertRaises(TypeError, lambda: badarg < t1)
2415 self.assertRaises(TypeError, lambda: badarg > t1)
2416 self.assertRaises(TypeError, lambda: badarg >= t1)
2417
2418 def test_bad_constructor_arguments(self):
2419 # bad hours
2420 self.theclass(0, 0) # no exception
2421 self.theclass(23, 0) # no exception
2422 self.assertRaises(ValueError, self.theclass, -1, 0)
2423 self.assertRaises(ValueError, self.theclass, 24, 0)
2424 # bad minutes
2425 self.theclass(23, 0) # no exception
2426 self.theclass(23, 59) # no exception
2427 self.assertRaises(ValueError, self.theclass, 23, -1)
2428 self.assertRaises(ValueError, self.theclass, 23, 60)
2429 # bad seconds
2430 self.theclass(23, 59, 0) # no exception
2431 self.theclass(23, 59, 59) # no exception
2432 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
2433 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
2434 # bad microseconds
2435 self.theclass(23, 59, 59, 0) # no exception
2436 self.theclass(23, 59, 59, 999999) # no exception
2437 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
2438 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
2439
2440 def test_hash_equality(self):
2441 d = self.theclass(23, 30, 17)
2442 e = self.theclass(23, 30, 17)
2443 self.assertEqual(d, e)
2444 self.assertEqual(hash(d), hash(e))
2445
2446 dic = {d: 1}
2447 dic[e] = 2
2448 self.assertEqual(len(dic), 1)
2449 self.assertEqual(dic[d], 2)
2450 self.assertEqual(dic[e], 2)
2451
2452 d = self.theclass(0, 5, 17)
2453 e = self.theclass(0, 5, 17)
2454 self.assertEqual(d, e)
2455 self.assertEqual(hash(d), hash(e))
2456
2457 dic = {d: 1}
2458 dic[e] = 2
2459 self.assertEqual(len(dic), 1)
2460 self.assertEqual(dic[d], 2)
2461 self.assertEqual(dic[e], 2)
2462
2463 def test_isoformat(self):
2464 t = self.theclass(4, 5, 1, 123)
2465 self.assertEqual(t.isoformat(), "04:05:01.000123")
2466 self.assertEqual(t.isoformat(), str(t))
2467
2468 t = self.theclass()
2469 self.assertEqual(t.isoformat(), "00:00:00")
2470 self.assertEqual(t.isoformat(), str(t))
2471
2472 t = self.theclass(microsecond=1)
2473 self.assertEqual(t.isoformat(), "00:00:00.000001")
2474 self.assertEqual(t.isoformat(), str(t))
2475
2476 t = self.theclass(microsecond=10)
2477 self.assertEqual(t.isoformat(), "00:00:00.000010")
2478 self.assertEqual(t.isoformat(), str(t))
2479
2480 t = self.theclass(microsecond=100)
2481 self.assertEqual(t.isoformat(), "00:00:00.000100")
2482 self.assertEqual(t.isoformat(), str(t))
2483
2484 t = self.theclass(microsecond=1000)
2485 self.assertEqual(t.isoformat(), "00:00:00.001000")
2486 self.assertEqual(t.isoformat(), str(t))
2487
2488 t = self.theclass(microsecond=10000)
2489 self.assertEqual(t.isoformat(), "00:00:00.010000")
2490 self.assertEqual(t.isoformat(), str(t))
2491
2492 t = self.theclass(microsecond=100000)
2493 self.assertEqual(t.isoformat(), "00:00:00.100000")
2494 self.assertEqual(t.isoformat(), str(t))
2495
Alexander Belopolskya2998a62016-03-06 14:58:43 -05002496 t = self.theclass(hour=12, minute=34, second=56, microsecond=123456)
2497 self.assertEqual(t.isoformat(timespec='hours'), "12")
2498 self.assertEqual(t.isoformat(timespec='minutes'), "12:34")
2499 self.assertEqual(t.isoformat(timespec='seconds'), "12:34:56")
2500 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.123")
2501 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.123456")
2502 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56.123456")
2503 self.assertRaises(ValueError, t.isoformat, timespec='monkey')
2504
2505 t = self.theclass(hour=12, minute=34, second=56, microsecond=999500)
2506 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.999")
2507
2508 t = self.theclass(hour=12, minute=34, second=56, microsecond=0)
2509 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.000")
2510 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.000000")
2511 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56")
2512
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002513 def test_1653736(self):
2514 # verify it doesn't accept extra keyword arguments
2515 t = self.theclass(second=1)
2516 self.assertRaises(TypeError, t.isoformat, foo=3)
2517
2518 def test_strftime(self):
2519 t = self.theclass(1, 2, 3, 4)
2520 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
2521 # A naive object replaces %z and %Z with empty strings.
2522 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
2523
2524 def test_format(self):
2525 t = self.theclass(1, 2, 3, 4)
2526 self.assertEqual(t.__format__(''), str(t))
2527
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02002528 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002529 t.__format__(123)
2530
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002531 # check that a derived class's __str__() gets called
2532 class A(self.theclass):
2533 def __str__(self):
2534 return 'A'
2535 a = A(1, 2, 3, 4)
2536 self.assertEqual(a.__format__(''), 'A')
2537
2538 # check that a derived class's strftime gets called
2539 class B(self.theclass):
2540 def strftime(self, format_spec):
2541 return 'B'
2542 b = B(1, 2, 3, 4)
2543 self.assertEqual(b.__format__(''), str(t))
2544
2545 for fmt in ['%H %M %S',
2546 ]:
2547 self.assertEqual(t.__format__(fmt), t.strftime(fmt))
2548 self.assertEqual(a.__format__(fmt), t.strftime(fmt))
2549 self.assertEqual(b.__format__(fmt), 'B')
2550
2551 def test_str(self):
2552 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
2553 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
2554 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
2555 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
2556 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
2557
2558 def test_repr(self):
2559 name = 'datetime.' + self.theclass.__name__
2560 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
2561 "%s(1, 2, 3, 4)" % name)
2562 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
2563 "%s(10, 2, 3, 4000)" % name)
2564 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
2565 "%s(0, 2, 3, 400000)" % name)
2566 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
2567 "%s(12, 2, 3)" % name)
2568 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
2569 "%s(23, 15)" % name)
2570
2571 def test_resolution_info(self):
2572 self.assertIsInstance(self.theclass.min, self.theclass)
2573 self.assertIsInstance(self.theclass.max, self.theclass)
2574 self.assertIsInstance(self.theclass.resolution, timedelta)
2575 self.assertTrue(self.theclass.max > self.theclass.min)
2576
2577 def test_pickling(self):
2578 args = 20, 59, 16, 64**2
2579 orig = self.theclass(*args)
2580 for pickler, unpickler, proto in pickle_choices:
2581 green = pickler.dumps(orig, proto)
2582 derived = unpickler.loads(green)
2583 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02002584 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002585
2586 def test_pickling_subclass_time(self):
2587 args = 20, 59, 16, 64**2
2588 orig = SubclassTime(*args)
2589 for pickler, unpickler, proto in pickle_choices:
2590 green = pickler.dumps(orig, proto)
2591 derived = unpickler.loads(green)
2592 self.assertEqual(orig, derived)
2593
2594 def test_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002595 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002596 cls = self.theclass
2597 self.assertTrue(cls(1))
2598 self.assertTrue(cls(0, 1))
2599 self.assertTrue(cls(0, 0, 1))
2600 self.assertTrue(cls(0, 0, 0, 1))
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002601 self.assertTrue(cls(0))
2602 self.assertTrue(cls())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002603
2604 def test_replace(self):
2605 cls = self.theclass
2606 args = [1, 2, 3, 4]
2607 base = cls(*args)
2608 self.assertEqual(base, base.replace())
2609
2610 i = 0
2611 for name, newval in (("hour", 5),
2612 ("minute", 6),
2613 ("second", 7),
2614 ("microsecond", 8)):
2615 newargs = args[:]
2616 newargs[i] = newval
2617 expected = cls(*newargs)
2618 got = base.replace(**{name: newval})
2619 self.assertEqual(expected, got)
2620 i += 1
2621
2622 # Out of bounds.
2623 base = cls(1)
2624 self.assertRaises(ValueError, base.replace, hour=24)
2625 self.assertRaises(ValueError, base.replace, minute=-1)
2626 self.assertRaises(ValueError, base.replace, second=100)
2627 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2628
2629 def test_subclass_time(self):
2630
2631 class C(self.theclass):
2632 theAnswer = 42
2633
2634 def __new__(cls, *args, **kws):
2635 temp = kws.copy()
2636 extra = temp.pop('extra')
2637 result = self.theclass.__new__(cls, *args, **temp)
2638 result.extra = extra
2639 return result
2640
2641 def newmeth(self, start):
2642 return start + self.hour + self.second
2643
2644 args = 4, 5, 6
2645
2646 dt1 = self.theclass(*args)
2647 dt2 = C(*args, **{'extra': 7})
2648
2649 self.assertEqual(dt2.__class__, C)
2650 self.assertEqual(dt2.theAnswer, 42)
2651 self.assertEqual(dt2.extra, 7)
2652 self.assertEqual(dt1.isoformat(), dt2.isoformat())
2653 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2654
2655 def test_backdoor_resistance(self):
2656 # see TestDate.test_backdoor_resistance().
2657 base = '2:59.0'
2658 for hour_byte in ' ', '9', chr(24), '\xff':
2659 self.assertRaises(TypeError, self.theclass,
2660 hour_byte + base[1:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002661 # Good bytes, but bad tzinfo:
2662 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
2663 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002664
2665# A mixin for classes with a tzinfo= argument. Subclasses must define
Martin Panter46f50722016-05-26 05:35:26 +00002666# theclass as a class attribute, and theclass(1, 1, 1, tzinfo=whatever)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002667# must be legit (which is true for time and datetime).
2668class TZInfoBase:
2669
2670 def test_argument_passing(self):
2671 cls = self.theclass
2672 # A datetime passes itself on, a time passes None.
2673 class introspective(tzinfo):
2674 def tzname(self, dt): return dt and "real" or "none"
2675 def utcoffset(self, dt):
2676 return timedelta(minutes = dt and 42 or -42)
2677 dst = utcoffset
2678
2679 obj = cls(1, 2, 3, tzinfo=introspective())
2680
2681 expected = cls is time and "none" or "real"
2682 self.assertEqual(obj.tzname(), expected)
2683
2684 expected = timedelta(minutes=(cls is time and -42 or 42))
2685 self.assertEqual(obj.utcoffset(), expected)
2686 self.assertEqual(obj.dst(), expected)
2687
2688 def test_bad_tzinfo_classes(self):
2689 cls = self.theclass
2690 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
2691
2692 class NiceTry(object):
2693 def __init__(self): pass
2694 def utcoffset(self, dt): pass
2695 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
2696
2697 class BetterTry(tzinfo):
2698 def __init__(self): pass
2699 def utcoffset(self, dt): pass
2700 b = BetterTry()
2701 t = cls(1, 1, 1, tzinfo=b)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002702 self.assertIs(t.tzinfo, b)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002703
2704 def test_utc_offset_out_of_bounds(self):
2705 class Edgy(tzinfo):
2706 def __init__(self, offset):
2707 self.offset = timedelta(minutes=offset)
2708 def utcoffset(self, dt):
2709 return self.offset
2710
2711 cls = self.theclass
2712 for offset, legit in ((-1440, False),
2713 (-1439, True),
2714 (1439, True),
2715 (1440, False)):
2716 if cls is time:
2717 t = cls(1, 2, 3, tzinfo=Edgy(offset))
2718 elif cls is datetime:
2719 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
2720 else:
2721 assert 0, "impossible"
2722 if legit:
2723 aofs = abs(offset)
2724 h, m = divmod(aofs, 60)
2725 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
2726 if isinstance(t, datetime):
2727 t = t.timetz()
2728 self.assertEqual(str(t), "01:02:03" + tag)
2729 else:
2730 self.assertRaises(ValueError, str, t)
2731
2732 def test_tzinfo_classes(self):
2733 cls = self.theclass
2734 class C1(tzinfo):
2735 def utcoffset(self, dt): return None
2736 def dst(self, dt): return None
2737 def tzname(self, dt): return None
2738 for t in (cls(1, 1, 1),
2739 cls(1, 1, 1, tzinfo=None),
2740 cls(1, 1, 1, tzinfo=C1())):
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002741 self.assertIsNone(t.utcoffset())
2742 self.assertIsNone(t.dst())
2743 self.assertIsNone(t.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002744
2745 class C3(tzinfo):
2746 def utcoffset(self, dt): return timedelta(minutes=-1439)
2747 def dst(self, dt): return timedelta(minutes=1439)
2748 def tzname(self, dt): return "aname"
2749 t = cls(1, 1, 1, tzinfo=C3())
2750 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
2751 self.assertEqual(t.dst(), timedelta(minutes=1439))
2752 self.assertEqual(t.tzname(), "aname")
2753
2754 # Wrong types.
2755 class C4(tzinfo):
2756 def utcoffset(self, dt): return "aname"
2757 def dst(self, dt): return 7
2758 def tzname(self, dt): return 0
2759 t = cls(1, 1, 1, tzinfo=C4())
2760 self.assertRaises(TypeError, t.utcoffset)
2761 self.assertRaises(TypeError, t.dst)
2762 self.assertRaises(TypeError, t.tzname)
2763
2764 # Offset out of range.
2765 class C6(tzinfo):
2766 def utcoffset(self, dt): return timedelta(hours=-24)
2767 def dst(self, dt): return timedelta(hours=24)
2768 t = cls(1, 1, 1, tzinfo=C6())
2769 self.assertRaises(ValueError, t.utcoffset)
2770 self.assertRaises(ValueError, t.dst)
2771
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002772 # Not a whole number of seconds.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002773 class C7(tzinfo):
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002774 def utcoffset(self, dt): return timedelta(microseconds=61)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002775 def dst(self, dt): return timedelta(microseconds=-81)
2776 t = cls(1, 1, 1, tzinfo=C7())
Alexander Belopolsky018d3532017-07-31 10:26:50 -04002777 self.assertEqual(t.utcoffset(), timedelta(microseconds=61))
2778 self.assertEqual(t.dst(), timedelta(microseconds=-81))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002779
2780 def test_aware_compare(self):
2781 cls = self.theclass
2782
2783 # Ensure that utcoffset() gets ignored if the comparands have
2784 # the same tzinfo member.
2785 class OperandDependentOffset(tzinfo):
2786 def utcoffset(self, t):
2787 if t.minute < 10:
2788 # d0 and d1 equal after adjustment
2789 return timedelta(minutes=t.minute)
2790 else:
2791 # d2 off in the weeds
2792 return timedelta(minutes=59)
2793
2794 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
2795 d0 = base.replace(minute=3)
2796 d1 = base.replace(minute=9)
2797 d2 = base.replace(minute=11)
2798 for x in d0, d1, d2:
2799 for y in d0, d1, d2:
2800 for op in lt, le, gt, ge, eq, ne:
2801 got = op(x, y)
2802 expected = op(x.minute, y.minute)
2803 self.assertEqual(got, expected)
2804
2805 # However, if they're different members, uctoffset is not ignored.
2806 # Note that a time can't actually have an operand-depedent offset,
2807 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
2808 # so skip this test for time.
2809 if cls is not time:
2810 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2811 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2812 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2813 for x in d0, d1, d2:
2814 for y in d0, d1, d2:
2815 got = (x > y) - (x < y)
2816 if (x is d0 or x is d1) and (y is d0 or y is d1):
2817 expected = 0
2818 elif x is y is d2:
2819 expected = 0
2820 elif x is d2:
2821 expected = -1
2822 else:
2823 assert y is d2
2824 expected = 1
2825 self.assertEqual(got, expected)
2826
2827
2828# Testing time objects with a non-None tzinfo.
2829class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
2830 theclass = time
2831
2832 def test_empty(self):
2833 t = self.theclass()
2834 self.assertEqual(t.hour, 0)
2835 self.assertEqual(t.minute, 0)
2836 self.assertEqual(t.second, 0)
2837 self.assertEqual(t.microsecond, 0)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002838 self.assertIsNone(t.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002839
2840 def test_zones(self):
2841 est = FixedOffset(-300, "EST", 1)
2842 utc = FixedOffset(0, "UTC", -2)
2843 met = FixedOffset(60, "MET", 3)
2844 t1 = time( 7, 47, tzinfo=est)
2845 t2 = time(12, 47, tzinfo=utc)
2846 t3 = time(13, 47, tzinfo=met)
2847 t4 = time(microsecond=40)
2848 t5 = time(microsecond=40, tzinfo=utc)
2849
2850 self.assertEqual(t1.tzinfo, est)
2851 self.assertEqual(t2.tzinfo, utc)
2852 self.assertEqual(t3.tzinfo, met)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002853 self.assertIsNone(t4.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002854 self.assertEqual(t5.tzinfo, utc)
2855
2856 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2857 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2858 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002859 self.assertIsNone(t4.utcoffset())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002860 self.assertRaises(TypeError, t1.utcoffset, "no args")
2861
2862 self.assertEqual(t1.tzname(), "EST")
2863 self.assertEqual(t2.tzname(), "UTC")
2864 self.assertEqual(t3.tzname(), "MET")
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002865 self.assertIsNone(t4.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002866 self.assertRaises(TypeError, t1.tzname, "no args")
2867
2868 self.assertEqual(t1.dst(), timedelta(minutes=1))
2869 self.assertEqual(t2.dst(), timedelta(minutes=-2))
2870 self.assertEqual(t3.dst(), timedelta(minutes=3))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002871 self.assertIsNone(t4.dst())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002872 self.assertRaises(TypeError, t1.dst, "no args")
2873
2874 self.assertEqual(hash(t1), hash(t2))
2875 self.assertEqual(hash(t1), hash(t3))
2876 self.assertEqual(hash(t2), hash(t3))
2877
2878 self.assertEqual(t1, t2)
2879 self.assertEqual(t1, t3)
2880 self.assertEqual(t2, t3)
Alexander Belopolsky08313822012-06-15 20:19:47 -04002881 self.assertNotEqual(t4, t5) # mixed tz-aware & naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002882 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
2883 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
2884
2885 self.assertEqual(str(t1), "07:47:00-05:00")
2886 self.assertEqual(str(t2), "12:47:00+00:00")
2887 self.assertEqual(str(t3), "13:47:00+01:00")
2888 self.assertEqual(str(t4), "00:00:00.000040")
2889 self.assertEqual(str(t5), "00:00:00.000040+00:00")
2890
2891 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
2892 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
2893 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
2894 self.assertEqual(t4.isoformat(), "00:00:00.000040")
2895 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
2896
2897 d = 'datetime.time'
2898 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
2899 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
2900 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
2901 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
2902 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
2903
2904 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
2905 "07:47:00 %Z=EST %z=-0500")
2906 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
2907 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
2908
2909 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
2910 t1 = time(23, 59, tzinfo=yuck)
2911 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
2912 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
2913
2914 # Check that an invalid tzname result raises an exception.
2915 class Badtzname(tzinfo):
Alexander Belopolskye239d232010-12-08 23:31:48 +00002916 tz = 42
2917 def tzname(self, dt): return self.tz
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002918 t = time(2, 3, 4, tzinfo=Badtzname())
2919 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
2920 self.assertRaises(TypeError, t.strftime, "%Z")
2921
Alexander Belopolskye239d232010-12-08 23:31:48 +00002922 # Issue #6697:
Utkarsh Upadhyay287c5592017-07-21 02:14:54 +02002923 if '_Fast' in self.__class__.__name__:
Alexander Belopolskye239d232010-12-08 23:31:48 +00002924 Badtzname.tz = '\ud800'
2925 self.assertRaises(ValueError, t.strftime, "%Z")
2926
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002927 def test_hash_edge_cases(self):
2928 # Offsets that overflow a basic time.
2929 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2930 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2931 self.assertEqual(hash(t1), hash(t2))
2932
2933 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2934 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2935 self.assertEqual(hash(t1), hash(t2))
2936
2937 def test_pickling(self):
2938 # Try one without a tzinfo.
2939 args = 20, 59, 16, 64**2
2940 orig = self.theclass(*args)
2941 for pickler, unpickler, proto in pickle_choices:
2942 green = pickler.dumps(orig, proto)
2943 derived = unpickler.loads(green)
2944 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02002945 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002946
2947 # Try one with a tzinfo.
2948 tinfo = PicklableFixedOffset(-300, 'cookie')
2949 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
2950 for pickler, unpickler, proto in pickle_choices:
2951 green = pickler.dumps(orig, proto)
2952 derived = unpickler.loads(green)
2953 self.assertEqual(orig, derived)
2954 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
2955 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2956 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02002957 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002958
2959 def test_more_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002960 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002961 cls = self.theclass
2962
2963 t = cls(0, tzinfo=FixedOffset(-300, ""))
2964 self.assertTrue(t)
2965
2966 t = cls(5, tzinfo=FixedOffset(-300, ""))
2967 self.assertTrue(t)
2968
2969 t = cls(5, tzinfo=FixedOffset(300, ""))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002970 self.assertTrue(t)
2971
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002972 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2973 self.assertTrue(t)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002974
2975 def test_replace(self):
2976 cls = self.theclass
2977 z100 = FixedOffset(100, "+100")
2978 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2979 args = [1, 2, 3, 4, z100]
2980 base = cls(*args)
2981 self.assertEqual(base, base.replace())
2982
2983 i = 0
2984 for name, newval in (("hour", 5),
2985 ("minute", 6),
2986 ("second", 7),
2987 ("microsecond", 8),
2988 ("tzinfo", zm200)):
2989 newargs = args[:]
2990 newargs[i] = newval
2991 expected = cls(*newargs)
2992 got = base.replace(**{name: newval})
2993 self.assertEqual(expected, got)
2994 i += 1
2995
2996 # Ensure we can get rid of a tzinfo.
2997 self.assertEqual(base.tzname(), "+100")
2998 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002999 self.assertIsNone(base2.tzinfo)
3000 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003001
3002 # Ensure we can add one.
3003 base3 = base2.replace(tzinfo=z100)
3004 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003005 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003006
3007 # Out of bounds.
3008 base = cls(1)
3009 self.assertRaises(ValueError, base.replace, hour=24)
3010 self.assertRaises(ValueError, base.replace, minute=-1)
3011 self.assertRaises(ValueError, base.replace, second=100)
3012 self.assertRaises(ValueError, base.replace, microsecond=1000000)
3013
3014 def test_mixed_compare(self):
3015 t1 = time(1, 2, 3)
3016 t2 = time(1, 2, 3)
3017 self.assertEqual(t1, t2)
3018 t2 = t2.replace(tzinfo=None)
3019 self.assertEqual(t1, t2)
3020 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3021 self.assertEqual(t1, t2)
3022 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04003023 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003024
3025 # In time w/ identical tzinfo objects, utcoffset is ignored.
3026 class Varies(tzinfo):
3027 def __init__(self):
3028 self.offset = timedelta(minutes=22)
3029 def utcoffset(self, t):
3030 self.offset += timedelta(minutes=1)
3031 return self.offset
3032
3033 v = Varies()
3034 t1 = t2.replace(tzinfo=v)
3035 t2 = t2.replace(tzinfo=v)
3036 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3037 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3038 self.assertEqual(t1, t2)
3039
3040 # But if they're not identical, it isn't ignored.
3041 t2 = t2.replace(tzinfo=Varies())
3042 self.assertTrue(t1 < t2) # t1's offset counter still going up
3043
3044 def test_subclass_timetz(self):
3045
3046 class C(self.theclass):
3047 theAnswer = 42
3048
3049 def __new__(cls, *args, **kws):
3050 temp = kws.copy()
3051 extra = temp.pop('extra')
3052 result = self.theclass.__new__(cls, *args, **temp)
3053 result.extra = extra
3054 return result
3055
3056 def newmeth(self, start):
3057 return start + self.hour + self.second
3058
3059 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3060
3061 dt1 = self.theclass(*args)
3062 dt2 = C(*args, **{'extra': 7})
3063
3064 self.assertEqual(dt2.__class__, C)
3065 self.assertEqual(dt2.theAnswer, 42)
3066 self.assertEqual(dt2.extra, 7)
3067 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3068 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
3069
3070
3071# Testing datetime objects with a non-None tzinfo.
3072
3073class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
3074 theclass = datetime
3075
3076 def test_trivial(self):
3077 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
3078 self.assertEqual(dt.year, 1)
3079 self.assertEqual(dt.month, 2)
3080 self.assertEqual(dt.day, 3)
3081 self.assertEqual(dt.hour, 4)
3082 self.assertEqual(dt.minute, 5)
3083 self.assertEqual(dt.second, 6)
3084 self.assertEqual(dt.microsecond, 7)
3085 self.assertEqual(dt.tzinfo, None)
3086
3087 def test_even_more_compare(self):
3088 # The test_compare() and test_more_compare() inherited from TestDate
3089 # and TestDateTime covered non-tzinfo cases.
3090
3091 # Smallest possible after UTC adjustment.
3092 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
3093 # Largest possible after UTC adjustment.
3094 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3095 tzinfo=FixedOffset(-1439, ""))
3096
3097 # Make sure those compare correctly, and w/o overflow.
3098 self.assertTrue(t1 < t2)
3099 self.assertTrue(t1 != t2)
3100 self.assertTrue(t2 > t1)
3101
3102 self.assertEqual(t1, t1)
3103 self.assertEqual(t2, t2)
3104
3105 # Equal afer adjustment.
3106 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
3107 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
3108 self.assertEqual(t1, t2)
3109
3110 # Change t1 not to subtract a minute, and t1 should be larger.
3111 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
3112 self.assertTrue(t1 > t2)
3113
3114 # Change t1 to subtract 2 minutes, and t1 should be smaller.
3115 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
3116 self.assertTrue(t1 < t2)
3117
3118 # Back to the original t1, but make seconds resolve it.
3119 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3120 second=1)
3121 self.assertTrue(t1 > t2)
3122
3123 # Likewise, but make microseconds resolve it.
3124 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3125 microsecond=1)
3126 self.assertTrue(t1 > t2)
3127
Alexander Belopolsky08313822012-06-15 20:19:47 -04003128 # Make t2 naive and it should differ.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003129 t2 = self.theclass.min
Alexander Belopolsky08313822012-06-15 20:19:47 -04003130 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003131 self.assertEqual(t2, t2)
3132
3133 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
3134 class Naive(tzinfo):
3135 def utcoffset(self, dt): return None
3136 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
Alexander Belopolsky08313822012-06-15 20:19:47 -04003137 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003138 self.assertEqual(t2, t2)
3139
3140 # OTOH, it's OK to compare two of these mixing the two ways of being
3141 # naive.
3142 t1 = self.theclass(5, 6, 7)
3143 self.assertEqual(t1, t2)
3144
3145 # Try a bogus uctoffset.
3146 class Bogus(tzinfo):
3147 def utcoffset(self, dt):
3148 return timedelta(minutes=1440) # out of bounds
3149 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
3150 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
3151 self.assertRaises(ValueError, lambda: t1 == t2)
3152
3153 def test_pickling(self):
3154 # Try one without a tzinfo.
3155 args = 6, 7, 23, 20, 59, 1, 64**2
3156 orig = self.theclass(*args)
3157 for pickler, unpickler, proto in pickle_choices:
3158 green = pickler.dumps(orig, proto)
3159 derived = unpickler.loads(green)
3160 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003161 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003162
3163 # Try one with a tzinfo.
3164 tinfo = PicklableFixedOffset(-300, 'cookie')
3165 orig = self.theclass(*args, **{'tzinfo': tinfo})
3166 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
3167 for pickler, unpickler, proto in pickle_choices:
3168 green = pickler.dumps(orig, proto)
3169 derived = unpickler.loads(green)
3170 self.assertEqual(orig, derived)
3171 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3172 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3173 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003174 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003175
3176 def test_extreme_hashes(self):
3177 # If an attempt is made to hash these via subtracting the offset
3178 # then hashing a datetime object, OverflowError results. The
3179 # Python implementation used to blow up here.
3180 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
3181 hash(t)
3182 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3183 tzinfo=FixedOffset(-1439, ""))
3184 hash(t)
3185
3186 # OTOH, an OOB offset should blow up.
3187 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
3188 self.assertRaises(ValueError, hash, t)
3189
3190 def test_zones(self):
3191 est = FixedOffset(-300, "EST")
3192 utc = FixedOffset(0, "UTC")
3193 met = FixedOffset(60, "MET")
3194 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
3195 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
3196 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
3197 self.assertEqual(t1.tzinfo, est)
3198 self.assertEqual(t2.tzinfo, utc)
3199 self.assertEqual(t3.tzinfo, met)
3200 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
3201 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
3202 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
3203 self.assertEqual(t1.tzname(), "EST")
3204 self.assertEqual(t2.tzname(), "UTC")
3205 self.assertEqual(t3.tzname(), "MET")
3206 self.assertEqual(hash(t1), hash(t2))
3207 self.assertEqual(hash(t1), hash(t3))
3208 self.assertEqual(hash(t2), hash(t3))
3209 self.assertEqual(t1, t2)
3210 self.assertEqual(t1, t3)
3211 self.assertEqual(t2, t3)
3212 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
3213 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
3214 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
3215 d = 'datetime.datetime(2002, 3, 19, '
3216 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
3217 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
3218 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
3219
3220 def test_combine(self):
3221 met = FixedOffset(60, "MET")
3222 d = date(2002, 3, 4)
3223 tz = time(18, 45, 3, 1234, tzinfo=met)
3224 dt = datetime.combine(d, tz)
3225 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
3226 tzinfo=met))
3227
3228 def test_extract(self):
3229 met = FixedOffset(60, "MET")
3230 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
3231 self.assertEqual(dt.date(), date(2002, 3, 4))
3232 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
3233 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
3234
3235 def test_tz_aware_arithmetic(self):
3236 import random
3237
3238 now = self.theclass.now()
3239 tz55 = FixedOffset(-330, "west 5:30")
3240 timeaware = now.time().replace(tzinfo=tz55)
3241 nowaware = self.theclass.combine(now.date(), timeaware)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003242 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003243 self.assertEqual(nowaware.timetz(), timeaware)
3244
3245 # Can't mix aware and non-aware.
3246 self.assertRaises(TypeError, lambda: now - nowaware)
3247 self.assertRaises(TypeError, lambda: nowaware - now)
3248
3249 # And adding datetime's doesn't make sense, aware or not.
3250 self.assertRaises(TypeError, lambda: now + nowaware)
3251 self.assertRaises(TypeError, lambda: nowaware + now)
3252 self.assertRaises(TypeError, lambda: nowaware + nowaware)
3253
3254 # Subtracting should yield 0.
3255 self.assertEqual(now - now, timedelta(0))
3256 self.assertEqual(nowaware - nowaware, timedelta(0))
3257
3258 # Adding a delta should preserve tzinfo.
3259 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
3260 nowawareplus = nowaware + delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003261 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003262 nowawareplus2 = delta + nowaware
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003263 self.assertIs(nowawareplus2.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003264 self.assertEqual(nowawareplus, nowawareplus2)
3265
3266 # that - delta should be what we started with, and that - what we
3267 # started with should be delta.
3268 diff = nowawareplus - delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003269 self.assertIs(diff.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003270 self.assertEqual(nowaware, diff)
3271 self.assertRaises(TypeError, lambda: delta - nowawareplus)
3272 self.assertEqual(nowawareplus - nowaware, delta)
3273
3274 # Make up a random timezone.
3275 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
3276 # Attach it to nowawareplus.
3277 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003278 self.assertIs(nowawareplus.tzinfo, tzr)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003279 # Make sure the difference takes the timezone adjustments into account.
3280 got = nowaware - nowawareplus
3281 # Expected: (nowaware base - nowaware offset) -
3282 # (nowawareplus base - nowawareplus offset) =
3283 # (nowaware base - nowawareplus base) +
3284 # (nowawareplus offset - nowaware offset) =
3285 # -delta + nowawareplus offset - nowaware offset
3286 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
3287 self.assertEqual(got, expected)
3288
3289 # Try max possible difference.
3290 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
3291 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3292 tzinfo=FixedOffset(-1439, "max"))
3293 maxdiff = max - min
3294 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
3295 timedelta(minutes=2*1439))
3296 # Different tzinfo, but the same offset
3297 tza = timezone(HOUR, 'A')
3298 tzb = timezone(HOUR, 'B')
3299 delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
3300 self.assertEqual(delta, self.theclass.min - self.theclass.max)
3301
3302 def test_tzinfo_now(self):
3303 meth = self.theclass.now
3304 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3305 base = meth()
3306 # Try with and without naming the keyword.
3307 off42 = FixedOffset(42, "42")
3308 another = meth(off42)
3309 again = meth(tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003310 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003311 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3312 # Bad argument with and w/o naming the keyword.
3313 self.assertRaises(TypeError, meth, 16)
3314 self.assertRaises(TypeError, meth, tzinfo=16)
3315 # Bad keyword name.
3316 self.assertRaises(TypeError, meth, tinfo=off42)
3317 # Too many args.
3318 self.assertRaises(TypeError, meth, off42, off42)
3319
3320 # We don't know which time zone we're in, and don't have a tzinfo
3321 # class to represent it, so seeing whether a tz argument actually
3322 # does a conversion is tricky.
3323 utc = FixedOffset(0, "utc", 0)
3324 for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
3325 timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
3326 for dummy in range(3):
3327 now = datetime.now(weirdtz)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003328 self.assertIs(now.tzinfo, weirdtz)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003329 utcnow = datetime.utcnow().replace(tzinfo=utc)
3330 now2 = utcnow.astimezone(weirdtz)
3331 if abs(now - now2) < timedelta(seconds=30):
3332 break
3333 # Else the code is broken, or more than 30 seconds passed between
3334 # calls; assuming the latter, just try again.
3335 else:
3336 # Three strikes and we're out.
3337 self.fail("utcnow(), now(tz), or astimezone() may be broken")
3338
3339 def test_tzinfo_fromtimestamp(self):
3340 import time
3341 meth = self.theclass.fromtimestamp
3342 ts = time.time()
3343 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3344 base = meth(ts)
3345 # Try with and without naming the keyword.
3346 off42 = FixedOffset(42, "42")
3347 another = meth(ts, off42)
3348 again = meth(ts, tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003349 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003350 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3351 # Bad argument with and w/o naming the keyword.
3352 self.assertRaises(TypeError, meth, ts, 16)
3353 self.assertRaises(TypeError, meth, ts, tzinfo=16)
3354 # Bad keyword name.
3355 self.assertRaises(TypeError, meth, ts, tinfo=off42)
3356 # Too many args.
3357 self.assertRaises(TypeError, meth, ts, off42, off42)
3358 # Too few args.
3359 self.assertRaises(TypeError, meth)
3360
3361 # Try to make sure tz= actually does some conversion.
3362 timestamp = 1000000000
3363 utcdatetime = datetime.utcfromtimestamp(timestamp)
3364 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
3365 # But on some flavor of Mac, it's nowhere near that. So we can't have
3366 # any idea here what time that actually is, we can only test that
3367 # relative changes match.
3368 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
3369 tz = FixedOffset(utcoffset, "tz", 0)
3370 expected = utcdatetime + utcoffset
3371 got = datetime.fromtimestamp(timestamp, tz)
3372 self.assertEqual(expected, got.replace(tzinfo=None))
3373
3374 def test_tzinfo_utcnow(self):
3375 meth = self.theclass.utcnow
3376 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3377 base = meth()
3378 # Try with and without naming the keyword; for whatever reason,
3379 # utcnow() doesn't accept a tzinfo argument.
3380 off42 = FixedOffset(42, "42")
3381 self.assertRaises(TypeError, meth, off42)
3382 self.assertRaises(TypeError, meth, tzinfo=off42)
3383
3384 def test_tzinfo_utcfromtimestamp(self):
3385 import time
3386 meth = self.theclass.utcfromtimestamp
3387 ts = time.time()
3388 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3389 base = meth(ts)
3390 # Try with and without naming the keyword; for whatever reason,
3391 # utcfromtimestamp() doesn't accept a tzinfo argument.
3392 off42 = FixedOffset(42, "42")
3393 self.assertRaises(TypeError, meth, ts, off42)
3394 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
3395
3396 def test_tzinfo_timetuple(self):
3397 # TestDateTime tested most of this. datetime adds a twist to the
3398 # DST flag.
3399 class DST(tzinfo):
3400 def __init__(self, dstvalue):
3401 if isinstance(dstvalue, int):
3402 dstvalue = timedelta(minutes=dstvalue)
3403 self.dstvalue = dstvalue
3404 def dst(self, dt):
3405 return self.dstvalue
3406
3407 cls = self.theclass
3408 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
3409 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
3410 t = d.timetuple()
3411 self.assertEqual(1, t.tm_year)
3412 self.assertEqual(1, t.tm_mon)
3413 self.assertEqual(1, t.tm_mday)
3414 self.assertEqual(10, t.tm_hour)
3415 self.assertEqual(20, t.tm_min)
3416 self.assertEqual(30, t.tm_sec)
3417 self.assertEqual(0, t.tm_wday)
3418 self.assertEqual(1, t.tm_yday)
3419 self.assertEqual(flag, t.tm_isdst)
3420
3421 # dst() returns wrong type.
3422 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
3423
3424 # dst() at the edge.
3425 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
3426 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
3427
3428 # dst() out of range.
3429 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
3430 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
3431
3432 def test_utctimetuple(self):
3433 class DST(tzinfo):
3434 def __init__(self, dstvalue=0):
3435 if isinstance(dstvalue, int):
3436 dstvalue = timedelta(minutes=dstvalue)
3437 self.dstvalue = dstvalue
3438 def dst(self, dt):
3439 return self.dstvalue
3440
3441 cls = self.theclass
3442 # This can't work: DST didn't implement utcoffset.
3443 self.assertRaises(NotImplementedError,
3444 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
3445
3446 class UOFS(DST):
3447 def __init__(self, uofs, dofs=None):
3448 DST.__init__(self, dofs)
3449 self.uofs = timedelta(minutes=uofs)
3450 def utcoffset(self, dt):
3451 return self.uofs
3452
3453 for dstvalue in -33, 33, 0, None:
3454 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
3455 t = d.utctimetuple()
3456 self.assertEqual(d.year, t.tm_year)
3457 self.assertEqual(d.month, t.tm_mon)
3458 self.assertEqual(d.day, t.tm_mday)
3459 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
3460 self.assertEqual(13, t.tm_min)
3461 self.assertEqual(d.second, t.tm_sec)
3462 self.assertEqual(d.weekday(), t.tm_wday)
3463 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
3464 t.tm_yday)
3465 # Ensure tm_isdst is 0 regardless of what dst() says: DST
3466 # is never in effect for a UTC time.
3467 self.assertEqual(0, t.tm_isdst)
3468
3469 # For naive datetime, utctimetuple == timetuple except for isdst
3470 d = cls(1, 2, 3, 10, 20, 30, 40)
3471 t = d.utctimetuple()
3472 self.assertEqual(t[:-1], d.timetuple()[:-1])
3473 self.assertEqual(0, t.tm_isdst)
3474 # Same if utcoffset is None
3475 class NOFS(DST):
3476 def utcoffset(self, dt):
3477 return None
3478 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
3479 t = d.utctimetuple()
3480 self.assertEqual(t[:-1], d.timetuple()[:-1])
3481 self.assertEqual(0, t.tm_isdst)
3482 # Check that bad tzinfo is detected
3483 class BOFS(DST):
3484 def utcoffset(self, dt):
3485 return "EST"
3486 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
3487 self.assertRaises(TypeError, d.utctimetuple)
3488
3489 # Check that utctimetuple() is the same as
3490 # astimezone(utc).timetuple()
3491 d = cls(2010, 11, 13, 14, 15, 16, 171819)
3492 for tz in [timezone.min, timezone.utc, timezone.max]:
3493 dtz = d.replace(tzinfo=tz)
3494 self.assertEqual(dtz.utctimetuple()[:-1],
3495 dtz.astimezone(timezone.utc).timetuple()[:-1])
3496 # At the edges, UTC adjustment can produce years out-of-range
3497 # for a datetime object. Ensure that an OverflowError is
3498 # raised.
3499 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
3500 # That goes back 1 minute less than a full day.
3501 self.assertRaises(OverflowError, tiny.utctimetuple)
3502
3503 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
3504 # That goes forward 1 minute less than a full day.
3505 self.assertRaises(OverflowError, huge.utctimetuple)
3506 # More overflow cases
3507 tiny = cls.min.replace(tzinfo=timezone(MINUTE))
3508 self.assertRaises(OverflowError, tiny.utctimetuple)
3509 huge = cls.max.replace(tzinfo=timezone(-MINUTE))
3510 self.assertRaises(OverflowError, huge.utctimetuple)
3511
3512 def test_tzinfo_isoformat(self):
3513 zero = FixedOffset(0, "+00:00")
3514 plus = FixedOffset(220, "+03:40")
3515 minus = FixedOffset(-231, "-03:51")
3516 unknown = FixedOffset(None, "")
3517
3518 cls = self.theclass
3519 datestr = '0001-02-03'
3520 for ofs in None, zero, plus, minus, unknown:
3521 for us in 0, 987001:
3522 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
3523 timestr = '04:05:59' + (us and '.987001' or '')
3524 ofsstr = ofs is not None and d.tzname() or ''
3525 tailstr = timestr + ofsstr
3526 iso = d.isoformat()
3527 self.assertEqual(iso, datestr + 'T' + tailstr)
3528 self.assertEqual(iso, d.isoformat('T'))
3529 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
3530 self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
3531 self.assertEqual(str(d), datestr + ' ' + tailstr)
3532
3533 def test_replace(self):
3534 cls = self.theclass
3535 z100 = FixedOffset(100, "+100")
3536 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3537 args = [1, 2, 3, 4, 5, 6, 7, z100]
3538 base = cls(*args)
3539 self.assertEqual(base, base.replace())
3540
3541 i = 0
3542 for name, newval in (("year", 2),
3543 ("month", 3),
3544 ("day", 4),
3545 ("hour", 5),
3546 ("minute", 6),
3547 ("second", 7),
3548 ("microsecond", 8),
3549 ("tzinfo", zm200)):
3550 newargs = args[:]
3551 newargs[i] = newval
3552 expected = cls(*newargs)
3553 got = base.replace(**{name: newval})
3554 self.assertEqual(expected, got)
3555 i += 1
3556
3557 # Ensure we can get rid of a tzinfo.
3558 self.assertEqual(base.tzname(), "+100")
3559 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003560 self.assertIsNone(base2.tzinfo)
3561 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003562
3563 # Ensure we can add one.
3564 base3 = base2.replace(tzinfo=z100)
3565 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003566 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003567
3568 # Out of bounds.
3569 base = cls(2000, 2, 29)
3570 self.assertRaises(ValueError, base.replace, year=2001)
3571
3572 def test_more_astimezone(self):
3573 # The inherited test_astimezone covered some trivial and error cases.
3574 fnone = FixedOffset(None, "None")
3575 f44m = FixedOffset(44, "44")
3576 fm5h = FixedOffset(-timedelta(hours=5), "m300")
3577
3578 dt = self.theclass.now(tz=f44m)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003579 self.assertIs(dt.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003580 # Replacing with degenerate tzinfo raises an exception.
3581 self.assertRaises(ValueError, dt.astimezone, fnone)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003582 # Replacing with same tzinfo makes no change.
3583 x = dt.astimezone(dt.tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003584 self.assertIs(x.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003585 self.assertEqual(x.date(), dt.date())
3586 self.assertEqual(x.time(), dt.time())
3587
3588 # Replacing with different tzinfo does adjust.
3589 got = dt.astimezone(fm5h)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003590 self.assertIs(got.tzinfo, fm5h)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003591 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
3592 expected = dt - dt.utcoffset() # in effect, convert to UTC
3593 expected += fm5h.utcoffset(dt) # and from there to local time
3594 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
3595 self.assertEqual(got.date(), expected.date())
3596 self.assertEqual(got.time(), expected.time())
3597 self.assertEqual(got.timetz(), expected.timetz())
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003598 self.assertIs(got.tzinfo, expected.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003599 self.assertEqual(got, expected)
3600
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003601 @support.run_with_tz('UTC')
3602 def test_astimezone_default_utc(self):
3603 dt = self.theclass.now(timezone.utc)
3604 self.assertEqual(dt.astimezone(None), dt)
3605 self.assertEqual(dt.astimezone(), dt)
3606
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003607 # Note that offset in TZ variable has the opposite sign to that
3608 # produced by %z directive.
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003609 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
3610 def test_astimezone_default_eastern(self):
3611 dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
3612 local = dt.astimezone()
3613 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003614 self.assertEqual(local.strftime("%z %Z"), "-0500 EST")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003615 dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc)
3616 local = dt.astimezone()
3617 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003618 self.assertEqual(local.strftime("%z %Z"), "-0400 EDT")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003619
Alexander Belopolsky1dcf4f92016-03-25 15:42:59 -04003620 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
3621 def test_astimezone_default_near_fold(self):
3622 # Issue #26616.
3623 u = datetime(2015, 11, 1, 5, tzinfo=timezone.utc)
3624 t = u.astimezone()
3625 s = t.astimezone()
3626 self.assertEqual(t.tzinfo, s.tzinfo)
3627
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003628 def test_aware_subtract(self):
3629 cls = self.theclass
3630
3631 # Ensure that utcoffset() is ignored when the operands have the
3632 # same tzinfo member.
3633 class OperandDependentOffset(tzinfo):
3634 def utcoffset(self, t):
3635 if t.minute < 10:
3636 # d0 and d1 equal after adjustment
3637 return timedelta(minutes=t.minute)
3638 else:
3639 # d2 off in the weeds
3640 return timedelta(minutes=59)
3641
3642 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
3643 d0 = base.replace(minute=3)
3644 d1 = base.replace(minute=9)
3645 d2 = base.replace(minute=11)
3646 for x in d0, d1, d2:
3647 for y in d0, d1, d2:
3648 got = x - y
3649 expected = timedelta(minutes=x.minute - y.minute)
3650 self.assertEqual(got, expected)
3651
3652 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
3653 # ignored.
3654 base = cls(8, 9, 10, 11, 12, 13, 14)
3655 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
3656 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
3657 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
3658 for x in d0, d1, d2:
3659 for y in d0, d1, d2:
3660 got = x - y
3661 if (x is d0 or x is d1) and (y is d0 or y is d1):
3662 expected = timedelta(0)
3663 elif x is y is d2:
3664 expected = timedelta(0)
3665 elif x is d2:
3666 expected = timedelta(minutes=(11-59)-0)
3667 else:
3668 assert y is d2
3669 expected = timedelta(minutes=0-(11-59))
3670 self.assertEqual(got, expected)
3671
3672 def test_mixed_compare(self):
3673 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
3674 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
3675 self.assertEqual(t1, t2)
3676 t2 = t2.replace(tzinfo=None)
3677 self.assertEqual(t1, t2)
3678 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3679 self.assertEqual(t1, t2)
3680 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04003681 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003682
3683 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
3684 class Varies(tzinfo):
3685 def __init__(self):
3686 self.offset = timedelta(minutes=22)
3687 def utcoffset(self, t):
3688 self.offset += timedelta(minutes=1)
3689 return self.offset
3690
3691 v = Varies()
3692 t1 = t2.replace(tzinfo=v)
3693 t2 = t2.replace(tzinfo=v)
3694 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3695 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3696 self.assertEqual(t1, t2)
3697
3698 # But if they're not identical, it isn't ignored.
3699 t2 = t2.replace(tzinfo=Varies())
3700 self.assertTrue(t1 < t2) # t1's offset counter still going up
3701
3702 def test_subclass_datetimetz(self):
3703
3704 class C(self.theclass):
3705 theAnswer = 42
3706
3707 def __new__(cls, *args, **kws):
3708 temp = kws.copy()
3709 extra = temp.pop('extra')
3710 result = self.theclass.__new__(cls, *args, **temp)
3711 result.extra = extra
3712 return result
3713
3714 def newmeth(self, start):
3715 return start + self.hour + self.year
3716
3717 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3718
3719 dt1 = self.theclass(*args)
3720 dt2 = C(*args, **{'extra': 7})
3721
3722 self.assertEqual(dt2.__class__, C)
3723 self.assertEqual(dt2.theAnswer, 42)
3724 self.assertEqual(dt2.extra, 7)
3725 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3726 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
3727
3728# Pain to set up DST-aware tzinfo classes.
3729
3730def first_sunday_on_or_after(dt):
3731 days_to_go = 6 - dt.weekday()
3732 if days_to_go:
3733 dt += timedelta(days_to_go)
3734 return dt
3735
3736ZERO = timedelta(0)
3737MINUTE = timedelta(minutes=1)
3738HOUR = timedelta(hours=1)
3739DAY = timedelta(days=1)
3740# In the US, DST starts at 2am (standard time) on the first Sunday in April.
3741DSTSTART = datetime(1, 4, 1, 2)
3742# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
3743# which is the first Sunday on or after Oct 25. Because we view 1:MM as
3744# being standard time on that day, there is no spelling in local time of
3745# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
3746DSTEND = datetime(1, 10, 25, 1)
3747
3748class USTimeZone(tzinfo):
3749
3750 def __init__(self, hours, reprname, stdname, dstname):
3751 self.stdoffset = timedelta(hours=hours)
3752 self.reprname = reprname
3753 self.stdname = stdname
3754 self.dstname = dstname
3755
3756 def __repr__(self):
3757 return self.reprname
3758
3759 def tzname(self, dt):
3760 if self.dst(dt):
3761 return self.dstname
3762 else:
3763 return self.stdname
3764
3765 def utcoffset(self, dt):
3766 return self.stdoffset + self.dst(dt)
3767
3768 def dst(self, dt):
3769 if dt is None or dt.tzinfo is None:
3770 # An exception instead may be sensible here, in one or more of
3771 # the cases.
3772 return ZERO
3773 assert dt.tzinfo is self
3774
3775 # Find first Sunday in April.
3776 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
3777 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
3778
3779 # Find last Sunday in October.
3780 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
3781 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
3782
3783 # Can't compare naive to aware objects, so strip the timezone from
3784 # dt first.
3785 if start <= dt.replace(tzinfo=None) < end:
3786 return HOUR
3787 else:
3788 return ZERO
3789
3790Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
3791Central = USTimeZone(-6, "Central", "CST", "CDT")
3792Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
3793Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
3794utc_real = FixedOffset(0, "UTC", 0)
3795# For better test coverage, we want another flavor of UTC that's west of
3796# the Eastern and Pacific timezones.
3797utc_fake = FixedOffset(-12*60, "UTCfake", 0)
3798
3799class TestTimezoneConversions(unittest.TestCase):
3800 # The DST switch times for 2002, in std time.
3801 dston = datetime(2002, 4, 7, 2)
3802 dstoff = datetime(2002, 10, 27, 1)
3803
3804 theclass = datetime
3805
3806 # Check a time that's inside DST.
3807 def checkinside(self, dt, tz, utc, dston, dstoff):
3808 self.assertEqual(dt.dst(), HOUR)
3809
3810 # Conversion to our own timezone is always an identity.
3811 self.assertEqual(dt.astimezone(tz), dt)
3812
3813 asutc = dt.astimezone(utc)
3814 there_and_back = asutc.astimezone(tz)
3815
3816 # Conversion to UTC and back isn't always an identity here,
3817 # because there are redundant spellings (in local time) of
3818 # UTC time when DST begins: the clock jumps from 1:59:59
3819 # to 3:00:00, and a local time of 2:MM:SS doesn't really
3820 # make sense then. The classes above treat 2:MM:SS as
3821 # daylight time then (it's "after 2am"), really an alias
3822 # for 1:MM:SS standard time. The latter form is what
3823 # conversion back from UTC produces.
3824 if dt.date() == dston.date() and dt.hour == 2:
3825 # We're in the redundant hour, and coming back from
3826 # UTC gives the 1:MM:SS standard-time spelling.
3827 self.assertEqual(there_and_back + HOUR, dt)
3828 # Although during was considered to be in daylight
3829 # time, there_and_back is not.
3830 self.assertEqual(there_and_back.dst(), ZERO)
3831 # They're the same times in UTC.
3832 self.assertEqual(there_and_back.astimezone(utc),
3833 dt.astimezone(utc))
3834 else:
3835 # We're not in the redundant hour.
3836 self.assertEqual(dt, there_and_back)
3837
3838 # Because we have a redundant spelling when DST begins, there is
Ezio Melotti3b3499b2011-03-16 11:35:38 +02003839 # (unfortunately) an hour when DST ends that can't be spelled at all in
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003840 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
3841 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
3842 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
3843 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
3844 # expressed in local time. Nevertheless, we want conversion back
3845 # from UTC to mimic the local clock's "repeat an hour" behavior.
3846 nexthour_utc = asutc + HOUR
3847 nexthour_tz = nexthour_utc.astimezone(tz)
3848 if dt.date() == dstoff.date() and dt.hour == 0:
3849 # We're in the hour before the last DST hour. The last DST hour
3850 # is ineffable. We want the conversion back to repeat 1:MM.
3851 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3852 nexthour_utc += HOUR
3853 nexthour_tz = nexthour_utc.astimezone(tz)
3854 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3855 else:
3856 self.assertEqual(nexthour_tz - dt, HOUR)
3857
3858 # Check a time that's outside DST.
3859 def checkoutside(self, dt, tz, utc):
3860 self.assertEqual(dt.dst(), ZERO)
3861
3862 # Conversion to our own timezone is always an identity.
3863 self.assertEqual(dt.astimezone(tz), dt)
3864
3865 # Converting to UTC and back is an identity too.
3866 asutc = dt.astimezone(utc)
3867 there_and_back = asutc.astimezone(tz)
3868 self.assertEqual(dt, there_and_back)
3869
3870 def convert_between_tz_and_utc(self, tz, utc):
3871 dston = self.dston.replace(tzinfo=tz)
3872 # Because 1:MM on the day DST ends is taken as being standard time,
3873 # there is no spelling in tz for the last hour of daylight time.
3874 # For purposes of the test, the last hour of DST is 0:MM, which is
3875 # taken as being daylight time (and 1:MM is taken as being standard
3876 # time).
3877 dstoff = self.dstoff.replace(tzinfo=tz)
3878 for delta in (timedelta(weeks=13),
3879 DAY,
3880 HOUR,
3881 timedelta(minutes=1),
3882 timedelta(microseconds=1)):
3883
3884 self.checkinside(dston, tz, utc, dston, dstoff)
3885 for during in dston + delta, dstoff - delta:
3886 self.checkinside(during, tz, utc, dston, dstoff)
3887
3888 self.checkoutside(dstoff, tz, utc)
3889 for outside in dston - delta, dstoff + delta:
3890 self.checkoutside(outside, tz, utc)
3891
3892 def test_easy(self):
3893 # Despite the name of this test, the endcases are excruciating.
3894 self.convert_between_tz_and_utc(Eastern, utc_real)
3895 self.convert_between_tz_and_utc(Pacific, utc_real)
3896 self.convert_between_tz_and_utc(Eastern, utc_fake)
3897 self.convert_between_tz_and_utc(Pacific, utc_fake)
3898 # The next is really dancing near the edge. It works because
3899 # Pacific and Eastern are far enough apart that their "problem
3900 # hours" don't overlap.
3901 self.convert_between_tz_and_utc(Eastern, Pacific)
3902 self.convert_between_tz_and_utc(Pacific, Eastern)
3903 # OTOH, these fail! Don't enable them. The difficulty is that
3904 # the edge case tests assume that every hour is representable in
3905 # the "utc" class. This is always true for a fixed-offset tzinfo
3906 # class (lke utc_real and utc_fake), but not for Eastern or Central.
3907 # For these adjacent DST-aware time zones, the range of time offsets
3908 # tested ends up creating hours in the one that aren't representable
3909 # in the other. For the same reason, we would see failures in the
3910 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
3911 # offset deltas in convert_between_tz_and_utc().
3912 #
3913 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
3914 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
3915
3916 def test_tricky(self):
3917 # 22:00 on day before daylight starts.
3918 fourback = self.dston - timedelta(hours=4)
3919 ninewest = FixedOffset(-9*60, "-0900", 0)
3920 fourback = fourback.replace(tzinfo=ninewest)
3921 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
3922 # 2", we should get the 3 spelling.
3923 # If we plug 22:00 the day before into Eastern, it "looks like std
3924 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
3925 # to 22:00 lands on 2:00, which makes no sense in local time (the
3926 # local clock jumps from 1 to 3). The point here is to make sure we
3927 # get the 3 spelling.
3928 expected = self.dston.replace(hour=3)
3929 got = fourback.astimezone(Eastern).replace(tzinfo=None)
3930 self.assertEqual(expected, got)
3931
3932 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
3933 # case we want the 1:00 spelling.
3934 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
3935 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
3936 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
3937 # spelling.
3938 expected = self.dston.replace(hour=1)
3939 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
3940 self.assertEqual(expected, got)
3941
3942 # Now on the day DST ends, we want "repeat an hour" behavior.
3943 # UTC 4:MM 5:MM 6:MM 7:MM checking these
3944 # EST 23:MM 0:MM 1:MM 2:MM
3945 # EDT 0:MM 1:MM 2:MM 3:MM
3946 # wall 0:MM 1:MM 1:MM 2:MM against these
3947 for utc in utc_real, utc_fake:
3948 for tz in Eastern, Pacific:
3949 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
3950 # Convert that to UTC.
3951 first_std_hour -= tz.utcoffset(None)
3952 # Adjust for possibly fake UTC.
3953 asutc = first_std_hour + utc.utcoffset(None)
3954 # First UTC hour to convert; this is 4:00 when utc=utc_real &
3955 # tz=Eastern.
3956 asutcbase = asutc.replace(tzinfo=utc)
3957 for tzhour in (0, 1, 1, 2):
3958 expectedbase = self.dstoff.replace(hour=tzhour)
3959 for minute in 0, 30, 59:
3960 expected = expectedbase.replace(minute=minute)
3961 asutc = asutcbase.replace(minute=minute)
3962 astz = asutc.astimezone(tz)
3963 self.assertEqual(astz.replace(tzinfo=None), expected)
3964 asutcbase += HOUR
3965
3966
3967 def test_bogus_dst(self):
3968 class ok(tzinfo):
3969 def utcoffset(self, dt): return HOUR
3970 def dst(self, dt): return HOUR
3971
3972 now = self.theclass.now().replace(tzinfo=utc_real)
3973 # Doesn't blow up.
3974 now.astimezone(ok())
3975
3976 # Does blow up.
3977 class notok(ok):
3978 def dst(self, dt): return None
3979 self.assertRaises(ValueError, now.astimezone, notok())
3980
3981 # Sometimes blow up. In the following, tzinfo.dst()
3982 # implementation may return None or not None depending on
3983 # whether DST is assumed to be in effect. In this situation,
3984 # a ValueError should be raised by astimezone().
3985 class tricky_notok(ok):
3986 def dst(self, dt):
3987 if dt.year == 2000:
3988 return None
3989 else:
3990 return 10*HOUR
3991 dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
3992 self.assertRaises(ValueError, dt.astimezone, tricky_notok())
3993
3994 def test_fromutc(self):
3995 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
3996 now = datetime.utcnow().replace(tzinfo=utc_real)
3997 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3998 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
3999 enow = Eastern.fromutc(now) # doesn't blow up
4000 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
4001 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
4002 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
4003
4004 # Always converts UTC to standard time.
4005 class FauxUSTimeZone(USTimeZone):
4006 def fromutc(self, dt):
4007 return dt + self.stdoffset
4008 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
4009
4010 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
4011 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
4012 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
4013
4014 # Check around DST start.
4015 start = self.dston.replace(hour=4, tzinfo=Eastern)
4016 fstart = start.replace(tzinfo=FEastern)
4017 for wall in 23, 0, 1, 3, 4, 5:
4018 expected = start.replace(hour=wall)
4019 if wall == 23:
4020 expected -= timedelta(days=1)
4021 got = Eastern.fromutc(start)
4022 self.assertEqual(expected, got)
4023
4024 expected = fstart + FEastern.stdoffset
4025 got = FEastern.fromutc(fstart)
4026 self.assertEqual(expected, got)
4027
4028 # Ensure astimezone() calls fromutc() too.
4029 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
4030 self.assertEqual(expected, got)
4031
4032 start += HOUR
4033 fstart += HOUR
4034
4035 # Check around DST end.
4036 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
4037 fstart = start.replace(tzinfo=FEastern)
4038 for wall in 0, 1, 1, 2, 3, 4:
4039 expected = start.replace(hour=wall)
4040 got = Eastern.fromutc(start)
4041 self.assertEqual(expected, got)
4042
4043 expected = fstart + FEastern.stdoffset
4044 got = FEastern.fromutc(fstart)
4045 self.assertEqual(expected, got)
4046
4047 # Ensure astimezone() calls fromutc() too.
4048 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
4049 self.assertEqual(expected, got)
4050
4051 start += HOUR
4052 fstart += HOUR
4053
4054
4055#############################################################################
4056# oddballs
4057
4058class Oddballs(unittest.TestCase):
4059
4060 def test_bug_1028306(self):
4061 # Trying to compare a date to a datetime should act like a mixed-
4062 # type comparison, despite that datetime is a subclass of date.
4063 as_date = date.today()
4064 as_datetime = datetime.combine(as_date, time())
4065 self.assertTrue(as_date != as_datetime)
4066 self.assertTrue(as_datetime != as_date)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004067 self.assertFalse(as_date == as_datetime)
4068 self.assertFalse(as_datetime == as_date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004069 self.assertRaises(TypeError, lambda: as_date < as_datetime)
4070 self.assertRaises(TypeError, lambda: as_datetime < as_date)
4071 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
4072 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
4073 self.assertRaises(TypeError, lambda: as_date > as_datetime)
4074 self.assertRaises(TypeError, lambda: as_datetime > as_date)
4075 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
4076 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
4077
Raymond Hettinger15f44ab2016-08-30 10:47:49 -07004078 # Nevertheless, comparison should work with the base-class (date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004079 # projection if use of a date method is forced.
4080 self.assertEqual(as_date.__eq__(as_datetime), True)
4081 different_day = (as_date.day + 1) % 20 + 1
4082 as_different = as_datetime.replace(day= different_day)
4083 self.assertEqual(as_date.__eq__(as_different), False)
4084
4085 # And date should compare with other subclasses of date. If a
4086 # subclass wants to stop this, it's up to the subclass to do so.
4087 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
4088 self.assertEqual(as_date, date_sc)
4089 self.assertEqual(date_sc, as_date)
4090
4091 # Ditto for datetimes.
4092 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
4093 as_date.day, 0, 0, 0)
4094 self.assertEqual(as_datetime, datetime_sc)
4095 self.assertEqual(datetime_sc, as_datetime)
4096
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004097 def test_extra_attributes(self):
4098 for x in [date.today(),
4099 time(),
4100 datetime.utcnow(),
4101 timedelta(),
4102 tzinfo(),
4103 timezone(timedelta())]:
4104 with self.assertRaises(AttributeError):
4105 x.abc = 1
4106
4107 def test_check_arg_types(self):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004108 class Number:
4109 def __init__(self, value):
4110 self.value = value
4111 def __int__(self):
4112 return self.value
4113
4114 for xx in [decimal.Decimal(10),
4115 decimal.Decimal('10.9'),
4116 Number(10)]:
4117 self.assertEqual(datetime(10, 10, 10, 10, 10, 10, 10),
4118 datetime(xx, xx, xx, xx, xx, xx, xx))
4119
4120 with self.assertRaisesRegex(TypeError, '^an integer is required '
R David Murray44b548d2016-09-08 13:59:53 -04004121 r'\(got type str\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004122 datetime(10, 10, '10')
4123
4124 f10 = Number(10.9)
4125 with self.assertRaisesRegex(TypeError, '^__int__ returned non-int '
R David Murray44b548d2016-09-08 13:59:53 -04004126 r'\(type float\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004127 datetime(10, 10, f10)
4128
4129 class Float(float):
4130 pass
4131 s10 = Float(10.9)
4132 with self.assertRaisesRegex(TypeError, '^integer argument expected, '
4133 'got float$'):
4134 datetime(10, 10, s10)
4135
4136 with self.assertRaises(TypeError):
4137 datetime(10., 10, 10)
4138 with self.assertRaises(TypeError):
4139 datetime(10, 10., 10)
4140 with self.assertRaises(TypeError):
4141 datetime(10, 10, 10.)
4142 with self.assertRaises(TypeError):
4143 datetime(10, 10, 10, 10.)
4144 with self.assertRaises(TypeError):
4145 datetime(10, 10, 10, 10, 10.)
4146 with self.assertRaises(TypeError):
4147 datetime(10, 10, 10, 10, 10, 10.)
4148 with self.assertRaises(TypeError):
4149 datetime(10, 10, 10, 10, 10, 10, 10.)
4150
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004151#############################################################################
4152# Local Time Disambiguation
4153
4154# An experimental reimplementation of fromutc that respects the "fold" flag.
4155
4156class tzinfo2(tzinfo):
4157
4158 def fromutc(self, dt):
4159 "datetime in UTC -> datetime in local time."
4160
4161 if not isinstance(dt, datetime):
4162 raise TypeError("fromutc() requires a datetime argument")
4163 if dt.tzinfo is not self:
4164 raise ValueError("dt.tzinfo is not self")
4165 # Returned value satisfies
4166 # dt + ldt.utcoffset() = ldt
4167 off0 = dt.replace(fold=0).utcoffset()
4168 off1 = dt.replace(fold=1).utcoffset()
4169 if off0 is None or off1 is None or dt.dst() is None:
4170 raise ValueError
4171 if off0 == off1:
4172 ldt = dt + off0
4173 off1 = ldt.utcoffset()
4174 if off0 == off1:
4175 return ldt
4176 # Now, we discovered both possible offsets, so
4177 # we can just try four possible solutions:
4178 for off in [off0, off1]:
4179 ldt = dt + off
4180 if ldt.utcoffset() == off:
4181 return ldt
4182 ldt = ldt.replace(fold=1)
4183 if ldt.utcoffset() == off:
4184 return ldt
4185
4186 raise ValueError("No suitable local time found")
4187
4188# Reimplementing simplified US timezones to respect the "fold" flag:
4189
4190class USTimeZone2(tzinfo2):
4191
4192 def __init__(self, hours, reprname, stdname, dstname):
4193 self.stdoffset = timedelta(hours=hours)
4194 self.reprname = reprname
4195 self.stdname = stdname
4196 self.dstname = dstname
4197
4198 def __repr__(self):
4199 return self.reprname
4200
4201 def tzname(self, dt):
4202 if self.dst(dt):
4203 return self.dstname
4204 else:
4205 return self.stdname
4206
4207 def utcoffset(self, dt):
4208 return self.stdoffset + self.dst(dt)
4209
4210 def dst(self, dt):
4211 if dt is None or dt.tzinfo is None:
4212 # An exception instead may be sensible here, in one or more of
4213 # the cases.
4214 return ZERO
4215 assert dt.tzinfo is self
4216
4217 # Find first Sunday in April.
4218 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
4219 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
4220
4221 # Find last Sunday in October.
4222 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
4223 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
4224
4225 # Can't compare naive to aware objects, so strip the timezone from
4226 # dt first.
4227 dt = dt.replace(tzinfo=None)
4228 if start + HOUR <= dt < end:
4229 # DST is in effect.
4230 return HOUR
4231 elif end <= dt < end + HOUR:
4232 # Fold (an ambiguous hour): use dt.fold to disambiguate.
4233 return ZERO if dt.fold else HOUR
4234 elif start <= dt < start + HOUR:
4235 # Gap (a non-existent hour): reverse the fold rule.
4236 return HOUR if dt.fold else ZERO
4237 else:
4238 # DST is off.
4239 return ZERO
4240
4241Eastern2 = USTimeZone2(-5, "Eastern2", "EST", "EDT")
4242Central2 = USTimeZone2(-6, "Central2", "CST", "CDT")
4243Mountain2 = USTimeZone2(-7, "Mountain2", "MST", "MDT")
4244Pacific2 = USTimeZone2(-8, "Pacific2", "PST", "PDT")
4245
4246# Europe_Vilnius_1941 tzinfo implementation reproduces the following
4247# 1941 transition from Olson's tzdist:
4248#
4249# Zone NAME GMTOFF RULES FORMAT [UNTIL]
4250# ZoneEurope/Vilnius 1:00 - CET 1940 Aug 3
4251# 3:00 - MSK 1941 Jun 24
4252# 1:00 C-Eur CE%sT 1944 Aug
4253#
4254# $ zdump -v Europe/Vilnius | grep 1941
4255# Europe/Vilnius Mon Jun 23 20:59:59 1941 UTC = Mon Jun 23 23:59:59 1941 MSK isdst=0 gmtoff=10800
4256# Europe/Vilnius Mon Jun 23 21:00:00 1941 UTC = Mon Jun 23 23:00:00 1941 CEST isdst=1 gmtoff=7200
4257
4258class Europe_Vilnius_1941(tzinfo):
4259 def _utc_fold(self):
4260 return [datetime(1941, 6, 23, 21, tzinfo=self), # Mon Jun 23 21:00:00 1941 UTC
4261 datetime(1941, 6, 23, 22, tzinfo=self)] # Mon Jun 23 22:00:00 1941 UTC
4262
4263 def _loc_fold(self):
4264 return [datetime(1941, 6, 23, 23, tzinfo=self), # Mon Jun 23 23:00:00 1941 MSK / CEST
4265 datetime(1941, 6, 24, 0, tzinfo=self)] # Mon Jun 24 00:00:00 1941 CEST
4266
4267 def utcoffset(self, dt):
4268 fold_start, fold_stop = self._loc_fold()
4269 if dt < fold_start:
4270 return 3 * HOUR
4271 if dt < fold_stop:
4272 return (2 if dt.fold else 3) * HOUR
4273 # if dt >= fold_stop
4274 return 2 * HOUR
4275
4276 def dst(self, dt):
4277 fold_start, fold_stop = self._loc_fold()
4278 if dt < fold_start:
4279 return 0 * HOUR
4280 if dt < fold_stop:
4281 return (1 if dt.fold else 0) * HOUR
4282 # if dt >= fold_stop
4283 return 1 * HOUR
4284
4285 def tzname(self, dt):
4286 fold_start, fold_stop = self._loc_fold()
4287 if dt < fold_start:
4288 return 'MSK'
4289 if dt < fold_stop:
4290 return ('MSK', 'CEST')[dt.fold]
4291 # if dt >= fold_stop
4292 return 'CEST'
4293
4294 def fromutc(self, dt):
4295 assert dt.fold == 0
4296 assert dt.tzinfo is self
4297 if dt.year != 1941:
4298 raise NotImplementedError
4299 fold_start, fold_stop = self._utc_fold()
4300 if dt < fold_start:
4301 return dt + 3 * HOUR
4302 if dt < fold_stop:
4303 return (dt + 2 * HOUR).replace(fold=1)
4304 # if dt >= fold_stop
4305 return dt + 2 * HOUR
4306
4307
4308class TestLocalTimeDisambiguation(unittest.TestCase):
4309
4310 def test_vilnius_1941_fromutc(self):
4311 Vilnius = Europe_Vilnius_1941()
4312
4313 gdt = datetime(1941, 6, 23, 20, 59, 59, tzinfo=timezone.utc)
4314 ldt = gdt.astimezone(Vilnius)
4315 self.assertEqual(ldt.strftime("%c %Z%z"),
4316 'Mon Jun 23 23:59:59 1941 MSK+0300')
4317 self.assertEqual(ldt.fold, 0)
4318 self.assertFalse(ldt.dst())
4319
4320 gdt = datetime(1941, 6, 23, 21, tzinfo=timezone.utc)
4321 ldt = gdt.astimezone(Vilnius)
4322 self.assertEqual(ldt.strftime("%c %Z%z"),
4323 'Mon Jun 23 23:00:00 1941 CEST+0200')
4324 self.assertEqual(ldt.fold, 1)
4325 self.assertTrue(ldt.dst())
4326
4327 gdt = datetime(1941, 6, 23, 22, tzinfo=timezone.utc)
4328 ldt = gdt.astimezone(Vilnius)
4329 self.assertEqual(ldt.strftime("%c %Z%z"),
4330 'Tue Jun 24 00:00:00 1941 CEST+0200')
4331 self.assertEqual(ldt.fold, 0)
4332 self.assertTrue(ldt.dst())
4333
4334 def test_vilnius_1941_toutc(self):
4335 Vilnius = Europe_Vilnius_1941()
4336
4337 ldt = datetime(1941, 6, 23, 22, 59, 59, tzinfo=Vilnius)
4338 gdt = ldt.astimezone(timezone.utc)
4339 self.assertEqual(gdt.strftime("%c %Z"),
4340 'Mon Jun 23 19:59:59 1941 UTC')
4341
4342 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius)
4343 gdt = ldt.astimezone(timezone.utc)
4344 self.assertEqual(gdt.strftime("%c %Z"),
4345 'Mon Jun 23 20:59:59 1941 UTC')
4346
4347 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius, fold=1)
4348 gdt = ldt.astimezone(timezone.utc)
4349 self.assertEqual(gdt.strftime("%c %Z"),
4350 'Mon Jun 23 21:59:59 1941 UTC')
4351
4352 ldt = datetime(1941, 6, 24, 0, tzinfo=Vilnius)
4353 gdt = ldt.astimezone(timezone.utc)
4354 self.assertEqual(gdt.strftime("%c %Z"),
4355 'Mon Jun 23 22:00:00 1941 UTC')
4356
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004357 def test_constructors(self):
4358 t = time(0, fold=1)
4359 dt = datetime(1, 1, 1, fold=1)
4360 self.assertEqual(t.fold, 1)
4361 self.assertEqual(dt.fold, 1)
4362 with self.assertRaises(TypeError):
4363 time(0, 0, 0, 0, None, 0)
4364
4365 def test_member(self):
4366 dt = datetime(1, 1, 1, fold=1)
4367 t = dt.time()
4368 self.assertEqual(t.fold, 1)
4369 t = dt.timetz()
4370 self.assertEqual(t.fold, 1)
4371
4372 def test_replace(self):
4373 t = time(0)
4374 dt = datetime(1, 1, 1)
4375 self.assertEqual(t.replace(fold=1).fold, 1)
4376 self.assertEqual(dt.replace(fold=1).fold, 1)
4377 self.assertEqual(t.replace(fold=0).fold, 0)
4378 self.assertEqual(dt.replace(fold=0).fold, 0)
4379 # Check that replacement of other fields does not change "fold".
4380 t = t.replace(fold=1, tzinfo=Eastern)
4381 dt = dt.replace(fold=1, tzinfo=Eastern)
4382 self.assertEqual(t.replace(tzinfo=None).fold, 1)
4383 self.assertEqual(dt.replace(tzinfo=None).fold, 1)
Serhiy Storchaka314d6fc2017-03-31 22:48:16 +03004384 # Out of bounds.
4385 with self.assertRaises(ValueError):
4386 t.replace(fold=2)
4387 with self.assertRaises(ValueError):
4388 dt.replace(fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004389 # Check that fold is a keyword-only argument
4390 with self.assertRaises(TypeError):
4391 t.replace(1, 1, 1, None, 1)
4392 with self.assertRaises(TypeError):
4393 dt.replace(1, 1, 1, 1, 1, 1, 1, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04004394
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004395 def test_comparison(self):
4396 t = time(0)
4397 dt = datetime(1, 1, 1)
4398 self.assertEqual(t, t.replace(fold=1))
4399 self.assertEqual(dt, dt.replace(fold=1))
4400
4401 def test_hash(self):
4402 t = time(0)
4403 dt = datetime(1, 1, 1)
4404 self.assertEqual(hash(t), hash(t.replace(fold=1)))
4405 self.assertEqual(hash(dt), hash(dt.replace(fold=1)))
4406
4407 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4408 def test_fromtimestamp(self):
4409 s = 1414906200
4410 dt0 = datetime.fromtimestamp(s)
4411 dt1 = datetime.fromtimestamp(s + 3600)
4412 self.assertEqual(dt0.fold, 0)
4413 self.assertEqual(dt1.fold, 1)
4414
4415 @support.run_with_tz('Australia/Lord_Howe')
4416 def test_fromtimestamp_lord_howe(self):
4417 tm = _time.localtime(1.4e9)
4418 if _time.strftime('%Z%z', tm) != 'LHST+1030':
4419 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
4420 # $ TZ=Australia/Lord_Howe date -r 1428158700
4421 # Sun Apr 5 01:45:00 LHDT 2015
4422 # $ TZ=Australia/Lord_Howe date -r 1428160500
4423 # Sun Apr 5 01:45:00 LHST 2015
4424 s = 1428158700
4425 t0 = datetime.fromtimestamp(s)
4426 t1 = datetime.fromtimestamp(s + 1800)
4427 self.assertEqual(t0, t1)
4428 self.assertEqual(t0.fold, 0)
4429 self.assertEqual(t1.fold, 1)
4430
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004431 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4432 def test_timestamp(self):
4433 dt0 = datetime(2014, 11, 2, 1, 30)
4434 dt1 = dt0.replace(fold=1)
4435 self.assertEqual(dt0.timestamp() + 3600,
4436 dt1.timestamp())
4437
4438 @support.run_with_tz('Australia/Lord_Howe')
4439 def test_timestamp_lord_howe(self):
4440 tm = _time.localtime(1.4e9)
4441 if _time.strftime('%Z%z', tm) != 'LHST+1030':
4442 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
4443 t = datetime(2015, 4, 5, 1, 45)
4444 s0 = t.replace(fold=0).timestamp()
4445 s1 = t.replace(fold=1).timestamp()
4446 self.assertEqual(s0 + 1800, s1)
4447
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004448 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4449 def test_astimezone(self):
4450 dt0 = datetime(2014, 11, 2, 1, 30)
4451 dt1 = dt0.replace(fold=1)
4452 # Convert both naive instances to aware.
4453 adt0 = dt0.astimezone()
4454 adt1 = dt1.astimezone()
4455 # Check that the first instance in DST zone and the second in STD
4456 self.assertEqual(adt0.tzname(), 'EDT')
4457 self.assertEqual(adt1.tzname(), 'EST')
4458 self.assertEqual(adt0 + HOUR, adt1)
4459 # Aware instances with fixed offset tzinfo's always have fold=0
4460 self.assertEqual(adt0.fold, 0)
4461 self.assertEqual(adt1.fold, 0)
4462
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004463 def test_pickle_fold(self):
4464 t = time(fold=1)
4465 dt = datetime(1, 1, 1, fold=1)
4466 for pickler, unpickler, proto in pickle_choices:
4467 for x in [t, dt]:
4468 s = pickler.dumps(x, proto)
4469 y = unpickler.loads(s)
4470 self.assertEqual(x, y)
4471 self.assertEqual((0 if proto < 4 else x.fold), y.fold)
4472
4473 def test_repr(self):
4474 t = time(fold=1)
4475 dt = datetime(1, 1, 1, fold=1)
4476 self.assertEqual(repr(t), 'datetime.time(0, 0, fold=1)')
4477 self.assertEqual(repr(dt),
4478 'datetime.datetime(1, 1, 1, 0, 0, fold=1)')
4479
4480 def test_dst(self):
4481 # Let's first establish that things work in regular times.
4482 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
4483 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
4484 self.assertEqual(dt_summer.dst(), HOUR)
4485 self.assertEqual(dt_winter.dst(), ZERO)
4486 # The disambiguation flag is ignored
4487 self.assertEqual(dt_summer.replace(fold=1).dst(), HOUR)
4488 self.assertEqual(dt_winter.replace(fold=1).dst(), ZERO)
4489
4490 # Pick local time in the fold.
4491 for minute in [0, 30, 59]:
4492 dt = datetime(2002, 10, 27, 1, minute, tzinfo=Eastern2)
4493 # With fold=0 (the default) it is in DST.
4494 self.assertEqual(dt.dst(), HOUR)
4495 # With fold=1 it is in STD.
4496 self.assertEqual(dt.replace(fold=1).dst(), ZERO)
4497
4498 # Pick local time in the gap.
4499 for minute in [0, 30, 59]:
4500 dt = datetime(2002, 4, 7, 2, minute, tzinfo=Eastern2)
4501 # With fold=0 (the default) it is in STD.
4502 self.assertEqual(dt.dst(), ZERO)
4503 # With fold=1 it is in DST.
4504 self.assertEqual(dt.replace(fold=1).dst(), HOUR)
4505
4506
4507 def test_utcoffset(self):
4508 # Let's first establish that things work in regular times.
4509 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
4510 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
4511 self.assertEqual(dt_summer.utcoffset(), -4 * HOUR)
4512 self.assertEqual(dt_winter.utcoffset(), -5 * HOUR)
4513 # The disambiguation flag is ignored
4514 self.assertEqual(dt_summer.replace(fold=1).utcoffset(), -4 * HOUR)
4515 self.assertEqual(dt_winter.replace(fold=1).utcoffset(), -5 * HOUR)
4516
4517 def test_fromutc(self):
4518 # Let's first establish that things work in regular times.
4519 u_summer = datetime(2002, 10, 27, 6, tzinfo=Eastern2) - timedelta.resolution
4520 u_winter = datetime(2002, 10, 27, 7, tzinfo=Eastern2)
4521 t_summer = Eastern2.fromutc(u_summer)
4522 t_winter = Eastern2.fromutc(u_winter)
4523 self.assertEqual(t_summer, u_summer - 4 * HOUR)
4524 self.assertEqual(t_winter, u_winter - 5 * HOUR)
4525 self.assertEqual(t_summer.fold, 0)
4526 self.assertEqual(t_winter.fold, 0)
4527
4528 # What happens in the fall-back fold?
4529 u = datetime(2002, 10, 27, 5, 30, tzinfo=Eastern2)
4530 t0 = Eastern2.fromutc(u)
4531 u += HOUR
4532 t1 = Eastern2.fromutc(u)
4533 self.assertEqual(t0, t1)
4534 self.assertEqual(t0.fold, 0)
4535 self.assertEqual(t1.fold, 1)
4536 # The tricky part is when u is in the local fold:
4537 u = datetime(2002, 10, 27, 1, 30, tzinfo=Eastern2)
4538 t = Eastern2.fromutc(u)
4539 self.assertEqual((t.day, t.hour), (26, 21))
4540 # .. or gets into the local fold after a standard time adjustment
4541 u = datetime(2002, 10, 27, 6, 30, tzinfo=Eastern2)
4542 t = Eastern2.fromutc(u)
4543 self.assertEqual((t.day, t.hour), (27, 1))
4544
4545 # What happens in the spring-forward gap?
4546 u = datetime(2002, 4, 7, 2, 0, tzinfo=Eastern2)
4547 t = Eastern2.fromutc(u)
4548 self.assertEqual((t.day, t.hour), (6, 21))
4549
4550 def test_mixed_compare_regular(self):
4551 t = datetime(2000, 1, 1, tzinfo=Eastern2)
4552 self.assertEqual(t, t.astimezone(timezone.utc))
4553 t = datetime(2000, 6, 1, tzinfo=Eastern2)
4554 self.assertEqual(t, t.astimezone(timezone.utc))
4555
4556 def test_mixed_compare_fold(self):
4557 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
4558 t_fold_utc = t_fold.astimezone(timezone.utc)
4559 self.assertNotEqual(t_fold, t_fold_utc)
4560
4561 def test_mixed_compare_gap(self):
4562 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
4563 t_gap_utc = t_gap.astimezone(timezone.utc)
4564 self.assertNotEqual(t_gap, t_gap_utc)
4565
4566 def test_hash_aware(self):
4567 t = datetime(2000, 1, 1, tzinfo=Eastern2)
4568 self.assertEqual(hash(t), hash(t.replace(fold=1)))
4569 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
4570 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
4571 self.assertEqual(hash(t_fold), hash(t_fold.replace(fold=1)))
4572 self.assertEqual(hash(t_gap), hash(t_gap.replace(fold=1)))
4573
4574SEC = timedelta(0, 1)
4575
4576def pairs(iterable):
4577 a, b = itertools.tee(iterable)
4578 next(b, None)
4579 return zip(a, b)
4580
4581class ZoneInfo(tzinfo):
4582 zoneroot = '/usr/share/zoneinfo'
4583 def __init__(self, ut, ti):
4584 """
4585
4586 :param ut: array
4587 Array of transition point timestamps
4588 :param ti: list
4589 A list of (offset, isdst, abbr) tuples
4590 :return: None
4591 """
4592 self.ut = ut
4593 self.ti = ti
4594 self.lt = self.invert(ut, ti)
4595
4596 @staticmethod
4597 def invert(ut, ti):
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04004598 lt = (array('q', ut), array('q', ut))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004599 if ut:
4600 offset = ti[0][0] // SEC
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04004601 lt[0][0] += offset
4602 lt[1][0] += offset
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004603 for i in range(1, len(ut)):
4604 lt[0][i] += ti[i-1][0] // SEC
4605 lt[1][i] += ti[i][0] // SEC
4606 return lt
4607
4608 @classmethod
4609 def fromfile(cls, fileobj):
4610 if fileobj.read(4).decode() != "TZif":
4611 raise ValueError("not a zoneinfo file")
4612 fileobj.seek(32)
4613 counts = array('i')
4614 counts.fromfile(fileobj, 3)
4615 if sys.byteorder != 'big':
4616 counts.byteswap()
4617
4618 ut = array('i')
4619 ut.fromfile(fileobj, counts[0])
4620 if sys.byteorder != 'big':
4621 ut.byteswap()
4622
4623 type_indices = array('B')
4624 type_indices.fromfile(fileobj, counts[0])
4625
4626 ttis = []
4627 for i in range(counts[1]):
4628 ttis.append(struct.unpack(">lbb", fileobj.read(6)))
4629
4630 abbrs = fileobj.read(counts[2])
4631
4632 # Convert ttis
4633 for i, (gmtoff, isdst, abbrind) in enumerate(ttis):
4634 abbr = abbrs[abbrind:abbrs.find(0, abbrind)].decode()
4635 ttis[i] = (timedelta(0, gmtoff), isdst, abbr)
4636
4637 ti = [None] * len(ut)
4638 for i, idx in enumerate(type_indices):
4639 ti[i] = ttis[idx]
4640
4641 self = cls(ut, ti)
4642
4643 return self
4644
4645 @classmethod
4646 def fromname(cls, name):
4647 path = os.path.join(cls.zoneroot, name)
4648 with open(path, 'rb') as f:
4649 return cls.fromfile(f)
4650
4651 EPOCHORDINAL = date(1970, 1, 1).toordinal()
4652
4653 def fromutc(self, dt):
4654 """datetime in UTC -> datetime in local time."""
4655
4656 if not isinstance(dt, datetime):
4657 raise TypeError("fromutc() requires a datetime argument")
4658 if dt.tzinfo is not self:
4659 raise ValueError("dt.tzinfo is not self")
4660
4661 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
4662 + dt.hour * 3600
4663 + dt.minute * 60
4664 + dt.second)
4665
4666 if timestamp < self.ut[1]:
4667 tti = self.ti[0]
4668 fold = 0
4669 else:
4670 idx = bisect.bisect_right(self.ut, timestamp)
4671 assert self.ut[idx-1] <= timestamp
4672 assert idx == len(self.ut) or timestamp < self.ut[idx]
4673 tti_prev, tti = self.ti[idx-2:idx]
4674 # Detect fold
4675 shift = tti_prev[0] - tti[0]
4676 fold = (shift > timedelta(0, timestamp - self.ut[idx-1]))
4677 dt += tti[0]
4678 if fold:
4679 return dt.replace(fold=1)
4680 else:
4681 return dt
4682
4683 def _find_ti(self, dt, i):
4684 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
4685 + dt.hour * 3600
4686 + dt.minute * 60
4687 + dt.second)
4688 lt = self.lt[dt.fold]
4689 idx = bisect.bisect_right(lt, timestamp)
4690
4691 return self.ti[max(0, idx - 1)][i]
4692
4693 def utcoffset(self, dt):
4694 return self._find_ti(dt, 0)
4695
4696 def dst(self, dt):
4697 isdst = self._find_ti(dt, 1)
4698 # XXX: We cannot accurately determine the "save" value,
4699 # so let's return 1h whenever DST is in effect. Since
4700 # we don't use dst() in fromutc(), it is unlikely that
4701 # it will be needed for anything more than bool(dst()).
4702 return ZERO if isdst else HOUR
4703
4704 def tzname(self, dt):
4705 return self._find_ti(dt, 2)
4706
4707 @classmethod
4708 def zonenames(cls, zonedir=None):
4709 if zonedir is None:
4710 zonedir = cls.zoneroot
Alexander Belopolsky1b8f26c2016-08-11 11:01:45 -04004711 zone_tab = os.path.join(zonedir, 'zone.tab')
4712 try:
4713 f = open(zone_tab)
4714 except OSError:
4715 return
4716 with f:
4717 for line in f:
4718 line = line.strip()
4719 if line and not line.startswith('#'):
4720 yield line.split()[2]
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004721
4722 @classmethod
4723 def stats(cls, start_year=1):
4724 count = gap_count = fold_count = zeros_count = 0
4725 min_gap = min_fold = timedelta.max
4726 max_gap = max_fold = ZERO
4727 min_gap_datetime = max_gap_datetime = datetime.min
4728 min_gap_zone = max_gap_zone = None
4729 min_fold_datetime = max_fold_datetime = datetime.min
4730 min_fold_zone = max_fold_zone = None
4731 stats_since = datetime(start_year, 1, 1) # Starting from 1970 eliminates a lot of noise
4732 for zonename in cls.zonenames():
4733 count += 1
4734 tz = cls.fromname(zonename)
4735 for dt, shift in tz.transitions():
4736 if dt < stats_since:
4737 continue
4738 if shift > ZERO:
4739 gap_count += 1
4740 if (shift, dt) > (max_gap, max_gap_datetime):
4741 max_gap = shift
4742 max_gap_zone = zonename
4743 max_gap_datetime = dt
4744 if (shift, datetime.max - dt) < (min_gap, datetime.max - min_gap_datetime):
4745 min_gap = shift
4746 min_gap_zone = zonename
4747 min_gap_datetime = dt
4748 elif shift < ZERO:
4749 fold_count += 1
4750 shift = -shift
4751 if (shift, dt) > (max_fold, max_fold_datetime):
4752 max_fold = shift
4753 max_fold_zone = zonename
4754 max_fold_datetime = dt
4755 if (shift, datetime.max - dt) < (min_fold, datetime.max - min_fold_datetime):
4756 min_fold = shift
4757 min_fold_zone = zonename
4758 min_fold_datetime = dt
4759 else:
4760 zeros_count += 1
4761 trans_counts = (gap_count, fold_count, zeros_count)
4762 print("Number of zones: %5d" % count)
4763 print("Number of transitions: %5d = %d (gaps) + %d (folds) + %d (zeros)" %
4764 ((sum(trans_counts),) + trans_counts))
4765 print("Min gap: %16s at %s in %s" % (min_gap, min_gap_datetime, min_gap_zone))
4766 print("Max gap: %16s at %s in %s" % (max_gap, max_gap_datetime, max_gap_zone))
4767 print("Min fold: %16s at %s in %s" % (min_fold, min_fold_datetime, min_fold_zone))
4768 print("Max fold: %16s at %s in %s" % (max_fold, max_fold_datetime, max_fold_zone))
4769
4770
4771 def transitions(self):
4772 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
4773 shift = ti[0] - prev_ti[0]
4774 yield datetime.utcfromtimestamp(t), shift
4775
4776 def nondst_folds(self):
4777 """Find all folds with the same value of isdst on both sides of the transition."""
4778 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
4779 shift = ti[0] - prev_ti[0]
4780 if shift < ZERO and ti[1] == prev_ti[1]:
4781 yield datetime.utcfromtimestamp(t), -shift, prev_ti[2], ti[2]
4782
4783 @classmethod
4784 def print_all_nondst_folds(cls, same_abbr=False, start_year=1):
4785 count = 0
4786 for zonename in cls.zonenames():
4787 tz = cls.fromname(zonename)
4788 for dt, shift, prev_abbr, abbr in tz.nondst_folds():
4789 if dt.year < start_year or same_abbr and prev_abbr != abbr:
4790 continue
4791 count += 1
4792 print("%3d) %-30s %s %10s %5s -> %s" %
4793 (count, zonename, dt, shift, prev_abbr, abbr))
4794
4795 def folds(self):
4796 for t, shift in self.transitions():
4797 if shift < ZERO:
4798 yield t, -shift
4799
4800 def gaps(self):
4801 for t, shift in self.transitions():
4802 if shift > ZERO:
4803 yield t, shift
4804
4805 def zeros(self):
4806 for t, shift in self.transitions():
4807 if not shift:
4808 yield t
4809
4810
4811class ZoneInfoTest(unittest.TestCase):
4812 zonename = 'America/New_York'
4813
4814 def setUp(self):
4815 if sys.platform == "win32":
4816 self.skipTest("Skipping zoneinfo tests on Windows")
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04004817 try:
4818 self.tz = ZoneInfo.fromname(self.zonename)
4819 except FileNotFoundError as err:
4820 self.skipTest("Skipping %s: %s" % (self.zonename, err))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004821
4822 def assertEquivDatetimes(self, a, b):
4823 self.assertEqual((a.replace(tzinfo=None), a.fold, id(a.tzinfo)),
4824 (b.replace(tzinfo=None), b.fold, id(b.tzinfo)))
4825
4826 def test_folds(self):
4827 tz = self.tz
4828 for dt, shift in tz.folds():
4829 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
4830 udt = dt + x
4831 ldt = tz.fromutc(udt.replace(tzinfo=tz))
4832 self.assertEqual(ldt.fold, 1)
4833 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
4834 self.assertEquivDatetimes(adt, ldt)
4835 utcoffset = ldt.utcoffset()
4836 self.assertEqual(ldt.replace(tzinfo=None), udt + utcoffset)
4837 # Round trip
4838 self.assertEquivDatetimes(ldt.astimezone(timezone.utc),
4839 udt.replace(tzinfo=timezone.utc))
4840
4841
4842 for x in [-timedelta.resolution, shift]:
4843 udt = dt + x
4844 udt = udt.replace(tzinfo=tz)
4845 ldt = tz.fromutc(udt)
4846 self.assertEqual(ldt.fold, 0)
4847
4848 def test_gaps(self):
4849 tz = self.tz
4850 for dt, shift in tz.gaps():
4851 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
4852 udt = dt + x
4853 udt = udt.replace(tzinfo=tz)
4854 ldt = tz.fromutc(udt)
4855 self.assertEqual(ldt.fold, 0)
4856 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
4857 self.assertEquivDatetimes(adt, ldt)
4858 utcoffset = ldt.utcoffset()
4859 self.assertEqual(ldt.replace(tzinfo=None), udt.replace(tzinfo=None) + utcoffset)
4860 # Create a local time inside the gap
4861 ldt = tz.fromutc(dt.replace(tzinfo=tz)) - shift + x
4862 self.assertLess(ldt.replace(fold=1).utcoffset(),
4863 ldt.replace(fold=0).utcoffset(),
4864 "At %s." % ldt)
4865
4866 for x in [-timedelta.resolution, shift]:
4867 udt = dt + x
4868 ldt = tz.fromutc(udt.replace(tzinfo=tz))
4869 self.assertEqual(ldt.fold, 0)
4870
4871 def test_system_transitions(self):
4872 if ('Riyadh8' in self.zonename or
4873 # From tzdata NEWS file:
4874 # The files solar87, solar88, and solar89 are no longer distributed.
4875 # They were a negative experiment - that is, a demonstration that
4876 # tz data can represent solar time only with some difficulty and error.
4877 # Their presence in the distribution caused confusion, as Riyadh
4878 # civil time was generally not solar time in those years.
4879 self.zonename.startswith('right/')):
4880 self.skipTest("Skipping %s" % self.zonename)
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04004881 tz = self.tz
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004882 TZ = os.environ.get('TZ')
4883 os.environ['TZ'] = self.zonename
4884 try:
4885 _time.tzset()
4886 for udt, shift in tz.transitions():
Alexander Belopolsky10c2dd22016-08-12 19:08:15 -04004887 if udt.year >= 2037:
4888 # System support for times around the end of 32-bit time_t
4889 # and later is flaky on many systems.
4890 break
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004891 s0 = (udt - datetime(1970, 1, 1)) // SEC
4892 ss = shift // SEC # shift seconds
4893 for x in [-40 * 3600, -20*3600, -1, 0,
4894 ss - 1, ss + 20 * 3600, ss + 40 * 3600]:
4895 s = s0 + x
4896 sdt = datetime.fromtimestamp(s)
4897 tzdt = datetime.fromtimestamp(s, tz).replace(tzinfo=None)
4898 self.assertEquivDatetimes(sdt, tzdt)
4899 s1 = sdt.timestamp()
4900 self.assertEqual(s, s1)
4901 if ss > 0: # gap
4902 # Create local time inside the gap
4903 dt = datetime.fromtimestamp(s0) - shift / 2
4904 ts0 = dt.timestamp()
4905 ts1 = dt.replace(fold=1).timestamp()
4906 self.assertEqual(ts0, s0 + ss / 2)
4907 self.assertEqual(ts1, s0 - ss / 2)
4908 finally:
4909 if TZ is None:
4910 del os.environ['TZ']
4911 else:
4912 os.environ['TZ'] = TZ
4913 _time.tzset()
4914
4915
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04004916class ZoneInfoCompleteTest(unittest.TestSuite):
4917 def __init__(self):
4918 tests = []
4919 if is_resource_enabled('tzdata'):
4920 for name in ZoneInfo.zonenames():
4921 Test = type('ZoneInfoTest[%s]' % name, (ZoneInfoTest,), {})
4922 Test.zonename = name
4923 for method in dir(Test):
4924 if method.startswith('test_'):
4925 tests.append(Test(method))
4926 super().__init__(tests)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004927
4928# Iran had a sub-minute UTC offset before 1946.
4929class IranTest(ZoneInfoTest):
Alexander Belopolsky07e2a0a2016-07-30 11:41:02 -04004930 zonename = 'Asia/Tehran'
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004931
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04004932def load_tests(loader, standard_tests, pattern):
4933 standard_tests.addTest(ZoneInfoCompleteTest())
4934 return standard_tests
4935
4936
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004937if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -05004938 unittest.main()