blob: 8f9ebddff5a9ec0db3491bb73e4f067936c3b8c6 [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
Paul Ganssle04af5b12018-01-24 17:29:30 -050034import _testcapi
35
Alexander Belopolskycf86e362010-07-23 19:25:47 +000036# Needed by test_datetime
37import _strptime
38#
39
40
41pickle_choices = [(pickle, pickle, proto)
42 for proto in range(pickle.HIGHEST_PROTOCOL + 1)]
43assert len(pickle_choices) == pickle.HIGHEST_PROTOCOL + 1
44
45# An arbitrary collection of objects of non-datetime types, for testing
46# mixed-type comparisons.
47OTHERSTUFF = (10, 34.5, "abc", {}, [], ())
48
49
50# XXX Copied from test_float.
51INF = float("inf")
52NAN = float("nan")
53
Alexander Belopolskycf86e362010-07-23 19:25:47 +000054#############################################################################
55# module tests
56
57class TestModule(unittest.TestCase):
58
59 def test_constants(self):
60 datetime = datetime_module
61 self.assertEqual(datetime.MINYEAR, 1)
62 self.assertEqual(datetime.MAXYEAR, 9999)
63
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -040064 def test_name_cleanup(self):
Utkarsh Upadhyay287c5592017-07-21 02:14:54 +020065 if '_Pure' in self.__class__.__name__:
66 self.skipTest('Only run for Fast C implementation')
67
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -040068 datetime = datetime_module
69 names = set(name for name in dir(datetime)
70 if not name.startswith('__') and not name.endswith('__'))
71 allowed = set(['MAXYEAR', 'MINYEAR', 'date', 'datetime',
72 'datetime_CAPI', 'time', 'timedelta', 'timezone',
73 'tzinfo'])
74 self.assertEqual(names - allowed, set([]))
75
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -050076 def test_divide_and_round(self):
Utkarsh Upadhyay287c5592017-07-21 02:14:54 +020077 if '_Fast' in self.__class__.__name__:
78 self.skipTest('Only run for Pure Python implementation')
79
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -050080 dar = datetime_module._divide_and_round
81
82 self.assertEqual(dar(-10, -3), 3)
83 self.assertEqual(dar(5, -2), -2)
84
85 # four cases: (2 signs of a) x (2 signs of b)
86 self.assertEqual(dar(7, 3), 2)
87 self.assertEqual(dar(-7, 3), -2)
88 self.assertEqual(dar(7, -3), -2)
89 self.assertEqual(dar(-7, -3), 2)
90
91 # ties to even - eight cases:
92 # (2 signs of a) x (2 signs of b) x (even / odd quotient)
93 self.assertEqual(dar(10, 4), 2)
94 self.assertEqual(dar(-10, 4), -2)
95 self.assertEqual(dar(10, -4), -2)
96 self.assertEqual(dar(-10, -4), 2)
97
98 self.assertEqual(dar(6, 4), 2)
99 self.assertEqual(dar(-6, 4), -2)
100 self.assertEqual(dar(6, -4), -2)
101 self.assertEqual(dar(-6, -4), 2)
102
103
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000104#############################################################################
105# tzinfo tests
106
107class FixedOffset(tzinfo):
108
109 def __init__(self, offset, name, dstoffset=42):
110 if isinstance(offset, int):
111 offset = timedelta(minutes=offset)
112 if isinstance(dstoffset, int):
113 dstoffset = timedelta(minutes=dstoffset)
114 self.__offset = offset
115 self.__name = name
116 self.__dstoffset = dstoffset
117 def __repr__(self):
118 return self.__name.lower()
119 def utcoffset(self, dt):
120 return self.__offset
121 def tzname(self, dt):
122 return self.__name
123 def dst(self, dt):
124 return self.__dstoffset
125
126class PicklableFixedOffset(FixedOffset):
127
128 def __init__(self, offset=None, name=None, dstoffset=None):
129 FixedOffset.__init__(self, offset, name, dstoffset)
130
Berker Peksage3385b42016-03-19 13:16:32 +0200131 def __getstate__(self):
132 return self.__dict__
133
Raymond Hettinger5a2146a2014-07-25 14:59:48 -0700134class _TZInfo(tzinfo):
135 def utcoffset(self, datetime_module):
136 return random.random()
137
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000138class TestTZInfo(unittest.TestCase):
139
Raymond Hettinger5a2146a2014-07-25 14:59:48 -0700140 def test_refcnt_crash_bug_22044(self):
141 tz1 = _TZInfo()
142 dt1 = datetime(2014, 7, 21, 11, 32, 3, 0, tz1)
143 with self.assertRaises(TypeError):
144 dt1.utcoffset()
145
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000146 def test_non_abstractness(self):
147 # In order to allow subclasses to get pickled, the C implementation
148 # wasn't able to get away with having __init__ raise
149 # NotImplementedError.
150 useless = tzinfo()
151 dt = datetime.max
152 self.assertRaises(NotImplementedError, useless.tzname, dt)
153 self.assertRaises(NotImplementedError, useless.utcoffset, dt)
154 self.assertRaises(NotImplementedError, useless.dst, dt)
155
156 def test_subclass_must_override(self):
157 class NotEnough(tzinfo):
158 def __init__(self, offset, name):
159 self.__offset = offset
160 self.__name = name
161 self.assertTrue(issubclass(NotEnough, tzinfo))
162 ne = NotEnough(3, "NotByALongShot")
163 self.assertIsInstance(ne, tzinfo)
164
165 dt = datetime.now()
166 self.assertRaises(NotImplementedError, ne.tzname, dt)
167 self.assertRaises(NotImplementedError, ne.utcoffset, dt)
168 self.assertRaises(NotImplementedError, ne.dst, dt)
169
170 def test_normal(self):
171 fo = FixedOffset(3, "Three")
172 self.assertIsInstance(fo, tzinfo)
173 for dt in datetime.now(), None:
174 self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3))
175 self.assertEqual(fo.tzname(dt), "Three")
176 self.assertEqual(fo.dst(dt), timedelta(minutes=42))
177
178 def test_pickling_base(self):
179 # There's no point to pickling tzinfo objects on their own (they
180 # carry no data), but they need to be picklable anyway else
181 # concrete subclasses can't be pickled.
182 orig = tzinfo.__new__(tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200183 self.assertIs(type(orig), tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000184 for pickler, unpickler, proto in pickle_choices:
185 green = pickler.dumps(orig, proto)
186 derived = unpickler.loads(green)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200187 self.assertIs(type(derived), tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000188
189 def test_pickling_subclass(self):
190 # Make sure we can pickle/unpickle an instance of a subclass.
191 offset = timedelta(minutes=-300)
192 for otype, args in [
193 (PicklableFixedOffset, (offset, 'cookie')),
194 (timezone, (offset,)),
195 (timezone, (offset, "EST"))]:
196 orig = otype(*args)
197 oname = orig.tzname(None)
198 self.assertIsInstance(orig, tzinfo)
199 self.assertIs(type(orig), otype)
200 self.assertEqual(orig.utcoffset(None), offset)
201 self.assertEqual(orig.tzname(None), oname)
202 for pickler, unpickler, proto in pickle_choices:
203 green = pickler.dumps(orig, proto)
204 derived = unpickler.loads(green)
205 self.assertIsInstance(derived, tzinfo)
206 self.assertIs(type(derived), otype)
207 self.assertEqual(derived.utcoffset(None), offset)
208 self.assertEqual(derived.tzname(None), oname)
209
Alexander Belopolskyc79447b2015-09-27 21:41:55 -0400210 def test_issue23600(self):
211 DSTDIFF = DSTOFFSET = timedelta(hours=1)
212
213 class UKSummerTime(tzinfo):
214 """Simple time zone which pretends to always be in summer time, since
215 that's what shows the failure.
216 """
217
218 def utcoffset(self, dt):
219 return DSTOFFSET
220
221 def dst(self, dt):
222 return DSTDIFF
223
224 def tzname(self, dt):
225 return 'UKSummerTime'
226
227 tz = UKSummerTime()
228 u = datetime(2014, 4, 26, 12, 1, tzinfo=tz)
229 t = tz.fromutc(u)
230 self.assertEqual(t - t.utcoffset(), u)
231
232
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000233class TestTimeZone(unittest.TestCase):
234
235 def setUp(self):
236 self.ACDT = timezone(timedelta(hours=9.5), 'ACDT')
237 self.EST = timezone(-timedelta(hours=5), 'EST')
238 self.DT = datetime(2010, 1, 1)
239
240 def test_str(self):
241 for tz in [self.ACDT, self.EST, timezone.utc,
242 timezone.min, timezone.max]:
243 self.assertEqual(str(tz), tz.tzname(None))
244
245 def test_repr(self):
246 datetime = datetime_module
247 for tz in [self.ACDT, self.EST, timezone.utc,
248 timezone.min, timezone.max]:
249 # test round-trip
250 tzrep = repr(tz)
251 self.assertEqual(tz, eval(tzrep))
252
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000253 def test_class_members(self):
254 limit = timedelta(hours=23, minutes=59)
255 self.assertEqual(timezone.utc.utcoffset(None), ZERO)
256 self.assertEqual(timezone.min.utcoffset(None), -limit)
257 self.assertEqual(timezone.max.utcoffset(None), limit)
258
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000259 def test_constructor(self):
Alexander Belopolsky1bcbaab2010-10-14 17:03:51 +0000260 self.assertIs(timezone.utc, timezone(timedelta(0)))
261 self.assertIsNot(timezone.utc, timezone(timedelta(0), 'UTC'))
262 self.assertEqual(timezone.utc, timezone(timedelta(0), 'UTC'))
Alexander Belopolsky018d3532017-07-31 10:26:50 -0400263 for subminute in [timedelta(microseconds=1), timedelta(seconds=1)]:
264 tz = timezone(subminute)
265 self.assertNotEqual(tz.utcoffset(None) % timedelta(minutes=1), 0)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000266 # invalid offsets
Alexander Belopolsky018d3532017-07-31 10:26:50 -0400267 for invalid in [timedelta(1, 1), timedelta(1)]:
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000268 self.assertRaises(ValueError, timezone, invalid)
269 self.assertRaises(ValueError, timezone, -invalid)
270
271 with self.assertRaises(TypeError): timezone(None)
272 with self.assertRaises(TypeError): timezone(42)
273 with self.assertRaises(TypeError): timezone(ZERO, None)
274 with self.assertRaises(TypeError): timezone(ZERO, 42)
275 with self.assertRaises(TypeError): timezone(ZERO, 'ABC', 'extra')
276
277 def test_inheritance(self):
278 self.assertIsInstance(timezone.utc, tzinfo)
279 self.assertIsInstance(self.EST, tzinfo)
280
281 def test_utcoffset(self):
282 dummy = self.DT
283 for h in [0, 1.5, 12]:
284 offset = h * HOUR
285 self.assertEqual(offset, timezone(offset).utcoffset(dummy))
286 self.assertEqual(-offset, timezone(-offset).utcoffset(dummy))
287
288 with self.assertRaises(TypeError): self.EST.utcoffset('')
289 with self.assertRaises(TypeError): self.EST.utcoffset(5)
290
291
292 def test_dst(self):
Raymond Hettinger7beae8a2011-01-06 05:34:17 +0000293 self.assertIsNone(timezone.utc.dst(self.DT))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000294
295 with self.assertRaises(TypeError): self.EST.dst('')
296 with self.assertRaises(TypeError): self.EST.dst(5)
297
298 def test_tzname(self):
Alexander Belopolsky7827a5b2015-09-06 13:07:21 -0400299 self.assertEqual('UTC', timezone.utc.tzname(None))
300 self.assertEqual('UTC', timezone(ZERO).tzname(None))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000301 self.assertEqual('UTC-05:00', timezone(-5 * HOUR).tzname(None))
302 self.assertEqual('UTC+09:30', timezone(9.5 * HOUR).tzname(None))
303 self.assertEqual('UTC-00:01', timezone(timedelta(minutes=-1)).tzname(None))
304 self.assertEqual('XYZ', timezone(-5 * HOUR, 'XYZ').tzname(None))
305
Alexander Belopolsky018d3532017-07-31 10:26:50 -0400306 # Sub-minute offsets:
307 self.assertEqual('UTC+01:06:40', timezone(timedelta(0, 4000)).tzname(None))
308 self.assertEqual('UTC-01:06:40',
309 timezone(-timedelta(0, 4000)).tzname(None))
310 self.assertEqual('UTC+01:06:40.000001',
311 timezone(timedelta(0, 4000, 1)).tzname(None))
312 self.assertEqual('UTC-01:06:40.000001',
313 timezone(-timedelta(0, 4000, 1)).tzname(None))
314
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000315 with self.assertRaises(TypeError): self.EST.tzname('')
316 with self.assertRaises(TypeError): self.EST.tzname(5)
317
318 def test_fromutc(self):
319 with self.assertRaises(ValueError):
320 timezone.utc.fromutc(self.DT)
321 with self.assertRaises(TypeError):
322 timezone.utc.fromutc('not datetime')
323 for tz in [self.EST, self.ACDT, Eastern]:
324 utctime = self.DT.replace(tzinfo=tz)
325 local = tz.fromutc(utctime)
326 self.assertEqual(local - utctime, tz.utcoffset(local))
327 self.assertEqual(local,
328 self.DT.replace(tzinfo=timezone.utc))
329
330 def test_comparison(self):
331 self.assertNotEqual(timezone(ZERO), timezone(HOUR))
332 self.assertEqual(timezone(HOUR), timezone(HOUR))
333 self.assertEqual(timezone(-5 * HOUR), timezone(-5 * HOUR, 'EST'))
334 with self.assertRaises(TypeError): timezone(ZERO) < timezone(ZERO)
335 self.assertIn(timezone(ZERO), {timezone(ZERO)})
Georg Brandl0085a242012-09-22 09:23:12 +0200336 self.assertTrue(timezone(ZERO) != None)
337 self.assertFalse(timezone(ZERO) == None)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000338
339 def test_aware_datetime(self):
340 # test that timezone instances can be used by datetime
341 t = datetime(1, 1, 1)
342 for tz in [timezone.min, timezone.max, timezone.utc]:
343 self.assertEqual(tz.tzname(t),
344 t.replace(tzinfo=tz).tzname())
345 self.assertEqual(tz.utcoffset(t),
346 t.replace(tzinfo=tz).utcoffset())
347 self.assertEqual(tz.dst(t),
348 t.replace(tzinfo=tz).dst())
349
Serhiy Storchakae28209f2015-11-16 11:12:58 +0200350 def test_pickle(self):
351 for tz in self.ACDT, self.EST, timezone.min, timezone.max:
352 for pickler, unpickler, proto in pickle_choices:
353 tz_copy = unpickler.loads(pickler.dumps(tz, proto))
354 self.assertEqual(tz_copy, tz)
355 tz = timezone.utc
356 for pickler, unpickler, proto in pickle_choices:
357 tz_copy = unpickler.loads(pickler.dumps(tz, proto))
358 self.assertIs(tz_copy, tz)
359
360 def test_copy(self):
361 for tz in self.ACDT, self.EST, timezone.min, timezone.max:
362 tz_copy = copy.copy(tz)
363 self.assertEqual(tz_copy, tz)
364 tz = timezone.utc
365 tz_copy = copy.copy(tz)
366 self.assertIs(tz_copy, tz)
367
368 def test_deepcopy(self):
369 for tz in self.ACDT, self.EST, timezone.min, timezone.max:
370 tz_copy = copy.deepcopy(tz)
371 self.assertEqual(tz_copy, tz)
372 tz = timezone.utc
373 tz_copy = copy.deepcopy(tz)
374 self.assertIs(tz_copy, tz)
375
376
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000377#############################################################################
Ezio Melotti85a86292013-08-17 16:57:41 +0300378# Base class for testing a particular aspect of timedelta, time, date and
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000379# datetime comparisons.
380
381class HarmlessMixedComparison:
382 # Test that __eq__ and __ne__ don't complain for mixed-type comparisons.
383
384 # Subclasses must define 'theclass', and theclass(1, 1, 1) must be a
385 # legit constructor.
386
387 def test_harmless_mixed_comparison(self):
388 me = self.theclass(1, 1, 1)
389
390 self.assertFalse(me == ())
391 self.assertTrue(me != ())
392 self.assertFalse(() == me)
393 self.assertTrue(() != me)
394
395 self.assertIn(me, [1, 20, [], me])
396 self.assertIn([], [me, 1, 20, []])
397
398 def test_harmful_mixed_comparison(self):
399 me = self.theclass(1, 1, 1)
400
401 self.assertRaises(TypeError, lambda: me < ())
402 self.assertRaises(TypeError, lambda: me <= ())
403 self.assertRaises(TypeError, lambda: me > ())
404 self.assertRaises(TypeError, lambda: me >= ())
405
406 self.assertRaises(TypeError, lambda: () < me)
407 self.assertRaises(TypeError, lambda: () <= me)
408 self.assertRaises(TypeError, lambda: () > me)
409 self.assertRaises(TypeError, lambda: () >= me)
410
411#############################################################################
412# timedelta tests
413
414class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
415
416 theclass = timedelta
417
418 def test_constructor(self):
419 eq = self.assertEqual
420 td = timedelta
421
422 # Check keyword args to constructor
423 eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0,
424 milliseconds=0, microseconds=0))
425 eq(td(1), td(days=1))
426 eq(td(0, 1), td(seconds=1))
427 eq(td(0, 0, 1), td(microseconds=1))
428 eq(td(weeks=1), td(days=7))
429 eq(td(days=1), td(hours=24))
430 eq(td(hours=1), td(minutes=60))
431 eq(td(minutes=1), td(seconds=60))
432 eq(td(seconds=1), td(milliseconds=1000))
433 eq(td(milliseconds=1), td(microseconds=1000))
434
435 # Check float args to constructor
436 eq(td(weeks=1.0/7), td(days=1))
437 eq(td(days=1.0/24), td(hours=1))
438 eq(td(hours=1.0/60), td(minutes=1))
439 eq(td(minutes=1.0/60), td(seconds=1))
440 eq(td(seconds=0.001), td(milliseconds=1))
441 eq(td(milliseconds=0.001), td(microseconds=1))
442
443 def test_computations(self):
444 eq = self.assertEqual
445 td = timedelta
446
447 a = td(7) # One week
448 b = td(0, 60) # One minute
449 c = td(0, 0, 1000) # One millisecond
450 eq(a+b+c, td(7, 60, 1000))
451 eq(a-b, td(6, 24*3600 - 60))
452 eq(b.__rsub__(a), td(6, 24*3600 - 60))
453 eq(-a, td(-7))
454 eq(+a, td(7))
455 eq(-b, td(-1, 24*3600 - 60))
456 eq(-c, td(-1, 24*3600 - 1, 999000))
457 eq(abs(a), a)
458 eq(abs(-a), a)
459 eq(td(6, 24*3600), a)
460 eq(td(0, 0, 60*1000000), b)
461 eq(a*10, td(70))
462 eq(a*10, 10*a)
463 eq(a*10, 10*a)
464 eq(b*10, td(0, 600))
465 eq(10*b, td(0, 600))
466 eq(b*10, td(0, 600))
467 eq(c*10, td(0, 0, 10000))
468 eq(10*c, td(0, 0, 10000))
469 eq(c*10, td(0, 0, 10000))
470 eq(a*-1, -a)
471 eq(b*-2, -b-b)
472 eq(c*-2, -c+-c)
473 eq(b*(60*24), (b*60)*24)
474 eq(b*(60*24), (60*b)*24)
475 eq(c*1000, td(0, 1))
476 eq(1000*c, td(0, 1))
477 eq(a//7, td(1))
478 eq(b//10, td(0, 6))
479 eq(c//1000, td(0, 0, 1))
480 eq(a//10, td(0, 7*24*360))
481 eq(a//3600000, td(0, 0, 7*24*1000))
482 eq(a/0.5, td(14))
483 eq(b/0.5, td(0, 120))
484 eq(a/7, td(1))
485 eq(b/10, td(0, 6))
486 eq(c/1000, td(0, 0, 1))
487 eq(a/10, td(0, 7*24*360))
488 eq(a/3600000, td(0, 0, 7*24*1000))
489
490 # Multiplication by float
491 us = td(microseconds=1)
492 eq((3*us) * 0.5, 2*us)
493 eq((5*us) * 0.5, 2*us)
494 eq(0.5 * (3*us), 2*us)
495 eq(0.5 * (5*us), 2*us)
496 eq((-3*us) * 0.5, -2*us)
497 eq((-5*us) * 0.5, -2*us)
498
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -0500499 # Issue #23521
500 eq(td(seconds=1) * 0.123456, td(microseconds=123456))
501 eq(td(seconds=1) * 0.6112295, td(microseconds=611229))
502
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000503 # Division by int and float
504 eq((3*us) / 2, 2*us)
505 eq((5*us) / 2, 2*us)
506 eq((-3*us) / 2.0, -2*us)
507 eq((-5*us) / 2.0, -2*us)
508 eq((3*us) / -2, -2*us)
509 eq((5*us) / -2, -2*us)
510 eq((3*us) / -2.0, -2*us)
511 eq((5*us) / -2.0, -2*us)
512 for i in range(-10, 10):
513 eq((i*us/3)//us, round(i/3))
514 for i in range(-10, 10):
515 eq((i*us/-3)//us, round(i/-3))
516
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -0500517 # Issue #23521
518 eq(td(seconds=1) / (1 / 0.6112295), td(microseconds=611229))
519
Alexander Belopolskyb6f5ec72011-04-05 20:07:38 -0400520 # Issue #11576
521 eq(td(999999999, 86399, 999999) - td(999999999, 86399, 999998),
522 td(0, 0, 1))
523 eq(td(999999999, 1, 1) - td(999999999, 1, 0),
524 td(0, 0, 1))
525
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000526 def test_disallowed_computations(self):
527 a = timedelta(42)
528
529 # Add/sub ints or floats should be illegal
530 for i in 1, 1.0:
531 self.assertRaises(TypeError, lambda: a+i)
532 self.assertRaises(TypeError, lambda: a-i)
533 self.assertRaises(TypeError, lambda: i+a)
534 self.assertRaises(TypeError, lambda: i-a)
535
536 # Division of int by timedelta doesn't make sense.
537 # Division by zero doesn't make sense.
538 zero = 0
539 self.assertRaises(TypeError, lambda: zero // a)
540 self.assertRaises(ZeroDivisionError, lambda: a // zero)
541 self.assertRaises(ZeroDivisionError, lambda: a / zero)
542 self.assertRaises(ZeroDivisionError, lambda: a / 0.0)
543 self.assertRaises(TypeError, lambda: a / '')
544
Eric Smith3ab08ca2010-12-04 15:17:38 +0000545 @support.requires_IEEE_754
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000546 def test_disallowed_special(self):
547 a = timedelta(42)
548 self.assertRaises(ValueError, a.__mul__, NAN)
549 self.assertRaises(ValueError, a.__truediv__, NAN)
550
551 def test_basic_attributes(self):
552 days, seconds, us = 1, 7, 31
553 td = timedelta(days, seconds, us)
554 self.assertEqual(td.days, days)
555 self.assertEqual(td.seconds, seconds)
556 self.assertEqual(td.microseconds, us)
557
558 def test_total_seconds(self):
559 td = timedelta(days=365)
560 self.assertEqual(td.total_seconds(), 31536000.0)
561 for total_seconds in [123456.789012, -123456.789012, 0.123456, 0, 1e6]:
562 td = timedelta(seconds=total_seconds)
563 self.assertEqual(td.total_seconds(), total_seconds)
564 # Issue8644: Test that td.total_seconds() has the same
565 # accuracy as td / timedelta(seconds=1).
566 for ms in [-1, -2, -123]:
567 td = timedelta(microseconds=ms)
568 self.assertEqual(td.total_seconds(), td / timedelta(seconds=1))
569
570 def test_carries(self):
571 t1 = timedelta(days=100,
572 weeks=-7,
573 hours=-24*(100-49),
574 minutes=-3,
575 seconds=12,
576 microseconds=(3*60 - 12) * 1e6 + 1)
577 t2 = timedelta(microseconds=1)
578 self.assertEqual(t1, t2)
579
580 def test_hash_equality(self):
581 t1 = timedelta(days=100,
582 weeks=-7,
583 hours=-24*(100-49),
584 minutes=-3,
585 seconds=12,
586 microseconds=(3*60 - 12) * 1000000)
587 t2 = timedelta()
588 self.assertEqual(hash(t1), hash(t2))
589
590 t1 += timedelta(weeks=7)
591 t2 += timedelta(days=7*7)
592 self.assertEqual(t1, t2)
593 self.assertEqual(hash(t1), hash(t2))
594
595 d = {t1: 1}
596 d[t2] = 2
597 self.assertEqual(len(d), 1)
598 self.assertEqual(d[t1], 2)
599
600 def test_pickling(self):
601 args = 12, 34, 56
602 orig = timedelta(*args)
603 for pickler, unpickler, proto in pickle_choices:
604 green = pickler.dumps(orig, proto)
605 derived = unpickler.loads(green)
606 self.assertEqual(orig, derived)
607
608 def test_compare(self):
609 t1 = timedelta(2, 3, 4)
610 t2 = timedelta(2, 3, 4)
611 self.assertEqual(t1, t2)
612 self.assertTrue(t1 <= t2)
613 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200614 self.assertFalse(t1 != t2)
615 self.assertFalse(t1 < t2)
616 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000617
618 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
619 t2 = timedelta(*args) # this is larger than t1
620 self.assertTrue(t1 < t2)
621 self.assertTrue(t2 > t1)
622 self.assertTrue(t1 <= t2)
623 self.assertTrue(t2 >= t1)
624 self.assertTrue(t1 != t2)
625 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200626 self.assertFalse(t1 == t2)
627 self.assertFalse(t2 == t1)
628 self.assertFalse(t1 > t2)
629 self.assertFalse(t2 < t1)
630 self.assertFalse(t1 >= t2)
631 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000632
633 for badarg in OTHERSTUFF:
634 self.assertEqual(t1 == badarg, False)
635 self.assertEqual(t1 != badarg, True)
636 self.assertEqual(badarg == t1, False)
637 self.assertEqual(badarg != t1, True)
638
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: t1 >= badarg)
643 self.assertRaises(TypeError, lambda: badarg <= t1)
644 self.assertRaises(TypeError, lambda: badarg < t1)
645 self.assertRaises(TypeError, lambda: badarg > t1)
646 self.assertRaises(TypeError, lambda: badarg >= t1)
647
648 def test_str(self):
649 td = timedelta
650 eq = self.assertEqual
651
652 eq(str(td(1)), "1 day, 0:00:00")
653 eq(str(td(-1)), "-1 day, 0:00:00")
654 eq(str(td(2)), "2 days, 0:00:00")
655 eq(str(td(-2)), "-2 days, 0:00:00")
656
657 eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
658 eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
659 eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
660 "-210 days, 23:12:34")
661
662 eq(str(td(milliseconds=1)), "0:00:00.001000")
663 eq(str(td(microseconds=3)), "0:00:00.000003")
664
665 eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
666 microseconds=999999)),
667 "999999999 days, 23:59:59.999999")
668
669 def test_repr(self):
670 name = 'datetime.' + self.theclass.__name__
671 self.assertEqual(repr(self.theclass(1)),
Utkarsh Upadhyaycc5a65c2017-07-25 23:51:33 +0200672 "%s(days=1)" % name)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000673 self.assertEqual(repr(self.theclass(10, 2)),
Utkarsh Upadhyaycc5a65c2017-07-25 23:51:33 +0200674 "%s(days=10, seconds=2)" % name)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000675 self.assertEqual(repr(self.theclass(-10, 2, 400000)),
Utkarsh Upadhyaycc5a65c2017-07-25 23:51:33 +0200676 "%s(days=-10, seconds=2, microseconds=400000)" % name)
677 self.assertEqual(repr(self.theclass(seconds=60)),
678 "%s(seconds=60)" % name)
679 self.assertEqual(repr(self.theclass()),
680 "%s(0)" % name)
681 self.assertEqual(repr(self.theclass(microseconds=100)),
682 "%s(microseconds=100)" % name)
683 self.assertEqual(repr(self.theclass(days=1, microseconds=100)),
684 "%s(days=1, microseconds=100)" % name)
685 self.assertEqual(repr(self.theclass(seconds=1, microseconds=100)),
686 "%s(seconds=1, microseconds=100)" % name)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000687
688 def test_roundtrip(self):
689 for td in (timedelta(days=999999999, hours=23, minutes=59,
690 seconds=59, microseconds=999999),
691 timedelta(days=-999999999),
692 timedelta(days=-999999999, seconds=1),
693 timedelta(days=1, seconds=2, microseconds=3)):
694
695 # Verify td -> string -> td identity.
696 s = repr(td)
697 self.assertTrue(s.startswith('datetime.'))
698 s = s[9:]
699 td2 = eval(s)
700 self.assertEqual(td, td2)
701
702 # Verify identity via reconstructing from pieces.
703 td2 = timedelta(td.days, td.seconds, td.microseconds)
704 self.assertEqual(td, td2)
705
706 def test_resolution_info(self):
707 self.assertIsInstance(timedelta.min, timedelta)
708 self.assertIsInstance(timedelta.max, timedelta)
709 self.assertIsInstance(timedelta.resolution, timedelta)
710 self.assertTrue(timedelta.max > timedelta.min)
711 self.assertEqual(timedelta.min, timedelta(-999999999))
712 self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
713 self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
714
715 def test_overflow(self):
716 tiny = timedelta.resolution
717
718 td = timedelta.min + tiny
719 td -= tiny # no problem
720 self.assertRaises(OverflowError, td.__sub__, tiny)
721 self.assertRaises(OverflowError, td.__add__, -tiny)
722
723 td = timedelta.max - tiny
724 td += tiny # no problem
725 self.assertRaises(OverflowError, td.__add__, tiny)
726 self.assertRaises(OverflowError, td.__sub__, -tiny)
727
728 self.assertRaises(OverflowError, lambda: -timedelta.max)
729
730 day = timedelta(1)
731 self.assertRaises(OverflowError, day.__mul__, 10**9)
732 self.assertRaises(OverflowError, day.__mul__, 1e9)
733 self.assertRaises(OverflowError, day.__truediv__, 1e-20)
734 self.assertRaises(OverflowError, day.__truediv__, 1e-10)
735 self.assertRaises(OverflowError, day.__truediv__, 9e-10)
736
Eric Smith3ab08ca2010-12-04 15:17:38 +0000737 @support.requires_IEEE_754
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000738 def _test_overflow_special(self):
739 day = timedelta(1)
740 self.assertRaises(OverflowError, day.__mul__, INF)
741 self.assertRaises(OverflowError, day.__mul__, -INF)
742
743 def test_microsecond_rounding(self):
744 td = timedelta
745 eq = self.assertEqual
746
747 # Single-field rounding.
748 eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
749 eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
Victor Stinner69cc4872015-09-08 23:58:54 +0200750 eq(td(milliseconds=0.5/1000), td(microseconds=0))
751 eq(td(milliseconds=-0.5/1000), td(microseconds=-0))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000752 eq(td(milliseconds=0.6/1000), td(microseconds=1))
753 eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
Victor Stinner69cc4872015-09-08 23:58:54 +0200754 eq(td(milliseconds=1.5/1000), td(microseconds=2))
755 eq(td(milliseconds=-1.5/1000), td(microseconds=-2))
756 eq(td(seconds=0.5/10**6), td(microseconds=0))
757 eq(td(seconds=-0.5/10**6), td(microseconds=-0))
758 eq(td(seconds=1/2**7), td(microseconds=7812))
759 eq(td(seconds=-1/2**7), td(microseconds=-7812))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000760
761 # Rounding due to contributions from more than one field.
762 us_per_hour = 3600e6
763 us_per_day = us_per_hour * 24
764 eq(td(days=.4/us_per_day), td(0))
765 eq(td(hours=.2/us_per_hour), td(0))
Victor Stinnercd5d7652015-09-09 01:09:21 +0200766 eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000767
768 eq(td(days=-.4/us_per_day), td(0))
769 eq(td(hours=-.2/us_per_hour), td(0))
770 eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
771
Victor Stinner69cc4872015-09-08 23:58:54 +0200772 # Test for a patch in Issue 8860
773 eq(td(microseconds=0.5), 0.5*td(microseconds=1.0))
774 eq(td(microseconds=0.5)//td.resolution, 0.5*td.resolution//td.resolution)
775
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000776 def test_massive_normalization(self):
777 td = timedelta(microseconds=-1)
778 self.assertEqual((td.days, td.seconds, td.microseconds),
779 (-1, 24*3600-1, 999999))
780
781 def test_bool(self):
782 self.assertTrue(timedelta(1))
783 self.assertTrue(timedelta(0, 1))
784 self.assertTrue(timedelta(0, 0, 1))
785 self.assertTrue(timedelta(microseconds=1))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200786 self.assertFalse(timedelta(0))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000787
788 def test_subclass_timedelta(self):
789
790 class T(timedelta):
791 @staticmethod
792 def from_td(td):
793 return T(td.days, td.seconds, td.microseconds)
794
795 def as_hours(self):
796 sum = (self.days * 24 +
797 self.seconds / 3600.0 +
798 self.microseconds / 3600e6)
799 return round(sum)
800
801 t1 = T(days=1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200802 self.assertIs(type(t1), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000803 self.assertEqual(t1.as_hours(), 24)
804
805 t2 = T(days=-1, seconds=-3600)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200806 self.assertIs(type(t2), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000807 self.assertEqual(t2.as_hours(), -25)
808
809 t3 = t1 + t2
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200810 self.assertIs(type(t3), timedelta)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000811 t4 = T.from_td(t3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200812 self.assertIs(type(t4), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000813 self.assertEqual(t3.days, t4.days)
814 self.assertEqual(t3.seconds, t4.seconds)
815 self.assertEqual(t3.microseconds, t4.microseconds)
816 self.assertEqual(str(t3), str(t4))
817 self.assertEqual(t4.as_hours(), -1)
818
819 def test_division(self):
820 t = timedelta(hours=1, minutes=24, seconds=19)
821 second = timedelta(seconds=1)
822 self.assertEqual(t / second, 5059.0)
823 self.assertEqual(t // second, 5059)
824
825 t = timedelta(minutes=2, seconds=30)
826 minute = timedelta(minutes=1)
827 self.assertEqual(t / minute, 2.5)
828 self.assertEqual(t // minute, 2)
829
830 zerotd = timedelta(0)
831 self.assertRaises(ZeroDivisionError, truediv, t, zerotd)
832 self.assertRaises(ZeroDivisionError, floordiv, t, zerotd)
833
834 # self.assertRaises(TypeError, truediv, t, 2)
835 # note: floor division of a timedelta by an integer *is*
836 # currently permitted.
837
838 def test_remainder(self):
839 t = timedelta(minutes=2, seconds=30)
840 minute = timedelta(minutes=1)
841 r = t % minute
842 self.assertEqual(r, timedelta(seconds=30))
843
844 t = timedelta(minutes=-2, seconds=30)
845 r = t % minute
846 self.assertEqual(r, timedelta(seconds=30))
847
848 zerotd = timedelta(0)
849 self.assertRaises(ZeroDivisionError, mod, t, zerotd)
850
851 self.assertRaises(TypeError, mod, t, 10)
852
853 def test_divmod(self):
854 t = timedelta(minutes=2, seconds=30)
855 minute = timedelta(minutes=1)
856 q, r = divmod(t, minute)
857 self.assertEqual(q, 2)
858 self.assertEqual(r, timedelta(seconds=30))
859
860 t = timedelta(minutes=-2, seconds=30)
861 q, r = divmod(t, minute)
862 self.assertEqual(q, -2)
863 self.assertEqual(r, timedelta(seconds=30))
864
865 zerotd = timedelta(0)
866 self.assertRaises(ZeroDivisionError, divmod, t, zerotd)
867
868 self.assertRaises(TypeError, divmod, t, 10)
869
Oren Milman865e4b42017-09-19 15:58:11 +0300870 def test_issue31293(self):
871 # The interpreter shouldn't crash in case a timedelta is divided or
872 # multiplied by a float with a bad as_integer_ratio() method.
873 def get_bad_float(bad_ratio):
874 class BadFloat(float):
875 def as_integer_ratio(self):
876 return bad_ratio
877 return BadFloat()
878
879 with self.assertRaises(TypeError):
880 timedelta() / get_bad_float(1 << 1000)
881 with self.assertRaises(TypeError):
882 timedelta() * get_bad_float(1 << 1000)
883
884 for bad_ratio in [(), (42, ), (1, 2, 3)]:
885 with self.assertRaises(ValueError):
886 timedelta() / get_bad_float(bad_ratio)
887 with self.assertRaises(ValueError):
888 timedelta() * get_bad_float(bad_ratio)
889
Serhiy Storchaka4ffd4652017-10-23 17:12:28 +0300890 def test_issue31752(self):
891 # The interpreter shouldn't crash because divmod() returns negative
892 # remainder.
893 class BadInt(int):
894 def __mul__(self, other):
895 return Prod()
896
897 class Prod:
898 def __radd__(self, other):
899 return Sum()
900
901 class Sum(int):
902 def __divmod__(self, other):
903 # negative remainder
904 return (0, -1)
905
906 timedelta(microseconds=BadInt(1))
907 timedelta(hours=BadInt(1))
908 timedelta(weeks=BadInt(1))
909
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000910
911#############################################################################
912# date tests
913
914class TestDateOnly(unittest.TestCase):
915 # Tests here won't pass if also run on datetime objects, so don't
916 # subclass this to test datetimes too.
917
918 def test_delta_non_days_ignored(self):
919 dt = date(2000, 1, 2)
920 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
921 microseconds=5)
922 days = timedelta(delta.days)
923 self.assertEqual(days, timedelta(1))
924
925 dt2 = dt + delta
926 self.assertEqual(dt2, dt + days)
927
928 dt2 = delta + dt
929 self.assertEqual(dt2, dt + days)
930
931 dt2 = dt - delta
932 self.assertEqual(dt2, dt - days)
933
934 delta = -delta
935 days = timedelta(delta.days)
936 self.assertEqual(days, timedelta(-2))
937
938 dt2 = dt + delta
939 self.assertEqual(dt2, dt + days)
940
941 dt2 = delta + dt
942 self.assertEqual(dt2, dt + days)
943
944 dt2 = dt - delta
945 self.assertEqual(dt2, dt - days)
946
947class SubclassDate(date):
948 sub_var = 1
949
950class TestDate(HarmlessMixedComparison, unittest.TestCase):
951 # Tests here should pass for both dates and datetimes, except for a
952 # few tests that TestDateTime overrides.
953
954 theclass = date
955
956 def test_basic_attributes(self):
957 dt = self.theclass(2002, 3, 1)
958 self.assertEqual(dt.year, 2002)
959 self.assertEqual(dt.month, 3)
960 self.assertEqual(dt.day, 1)
961
962 def test_roundtrip(self):
963 for dt in (self.theclass(1, 2, 3),
964 self.theclass.today()):
965 # Verify dt -> string -> date identity.
966 s = repr(dt)
967 self.assertTrue(s.startswith('datetime.'))
968 s = s[9:]
969 dt2 = eval(s)
970 self.assertEqual(dt, dt2)
971
972 # Verify identity via reconstructing from pieces.
973 dt2 = self.theclass(dt.year, dt.month, dt.day)
974 self.assertEqual(dt, dt2)
975
976 def test_ordinal_conversions(self):
977 # Check some fixed values.
978 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
979 (1, 12, 31, 365),
980 (2, 1, 1, 366),
981 # first example from "Calendrical Calculations"
982 (1945, 11, 12, 710347)]:
983 d = self.theclass(y, m, d)
984 self.assertEqual(n, d.toordinal())
985 fromord = self.theclass.fromordinal(n)
986 self.assertEqual(d, fromord)
987 if hasattr(fromord, "hour"):
988 # if we're checking something fancier than a date, verify
989 # the extra fields have been zeroed out
990 self.assertEqual(fromord.hour, 0)
991 self.assertEqual(fromord.minute, 0)
992 self.assertEqual(fromord.second, 0)
993 self.assertEqual(fromord.microsecond, 0)
994
995 # Check first and last days of year spottily across the whole
996 # range of years supported.
997 for year in range(MINYEAR, MAXYEAR+1, 7):
998 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
999 d = self.theclass(year, 1, 1)
1000 n = d.toordinal()
1001 d2 = self.theclass.fromordinal(n)
1002 self.assertEqual(d, d2)
1003 # Verify that moving back a day gets to the end of year-1.
1004 if year > 1:
1005 d = self.theclass.fromordinal(n-1)
1006 d2 = self.theclass(year-1, 12, 31)
1007 self.assertEqual(d, d2)
1008 self.assertEqual(d2.toordinal(), n-1)
1009
1010 # Test every day in a leap-year and a non-leap year.
1011 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
1012 for year, isleap in (2000, True), (2002, False):
1013 n = self.theclass(year, 1, 1).toordinal()
1014 for month, maxday in zip(range(1, 13), dim):
1015 if month == 2 and isleap:
1016 maxday += 1
1017 for day in range(1, maxday+1):
1018 d = self.theclass(year, month, day)
1019 self.assertEqual(d.toordinal(), n)
1020 self.assertEqual(d, self.theclass.fromordinal(n))
1021 n += 1
1022
1023 def test_extreme_ordinals(self):
1024 a = self.theclass.min
1025 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
1026 aord = a.toordinal()
1027 b = a.fromordinal(aord)
1028 self.assertEqual(a, b)
1029
1030 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
1031
1032 b = a + timedelta(days=1)
1033 self.assertEqual(b.toordinal(), aord + 1)
1034 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
1035
1036 a = self.theclass.max
1037 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
1038 aord = a.toordinal()
1039 b = a.fromordinal(aord)
1040 self.assertEqual(a, b)
1041
1042 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
1043
1044 b = a - timedelta(days=1)
1045 self.assertEqual(b.toordinal(), aord - 1)
1046 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
1047
1048 def test_bad_constructor_arguments(self):
1049 # bad years
1050 self.theclass(MINYEAR, 1, 1) # no exception
1051 self.theclass(MAXYEAR, 1, 1) # no exception
1052 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1053 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1054 # bad months
1055 self.theclass(2000, 1, 1) # no exception
1056 self.theclass(2000, 12, 1) # no exception
1057 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1058 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1059 # bad days
1060 self.theclass(2000, 2, 29) # no exception
1061 self.theclass(2004, 2, 29) # no exception
1062 self.theclass(2400, 2, 29) # no exception
1063 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1064 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1065 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1066 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1067 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1068 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1069
1070 def test_hash_equality(self):
1071 d = self.theclass(2000, 12, 31)
1072 # same thing
1073 e = self.theclass(2000, 12, 31)
1074 self.assertEqual(d, e)
1075 self.assertEqual(hash(d), hash(e))
1076
1077 dic = {d: 1}
1078 dic[e] = 2
1079 self.assertEqual(len(dic), 1)
1080 self.assertEqual(dic[d], 2)
1081 self.assertEqual(dic[e], 2)
1082
1083 d = self.theclass(2001, 1, 1)
1084 # same thing
1085 e = self.theclass(2001, 1, 1)
1086 self.assertEqual(d, e)
1087 self.assertEqual(hash(d), hash(e))
1088
1089 dic = {d: 1}
1090 dic[e] = 2
1091 self.assertEqual(len(dic), 1)
1092 self.assertEqual(dic[d], 2)
1093 self.assertEqual(dic[e], 2)
1094
1095 def test_computations(self):
1096 a = self.theclass(2002, 1, 31)
1097 b = self.theclass(1956, 1, 31)
1098 c = self.theclass(2001,2,1)
1099
1100 diff = a-b
1101 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1102 self.assertEqual(diff.seconds, 0)
1103 self.assertEqual(diff.microseconds, 0)
1104
1105 day = timedelta(1)
1106 week = timedelta(7)
1107 a = self.theclass(2002, 3, 2)
1108 self.assertEqual(a + day, self.theclass(2002, 3, 3))
1109 self.assertEqual(day + a, self.theclass(2002, 3, 3))
1110 self.assertEqual(a - day, self.theclass(2002, 3, 1))
1111 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
1112 self.assertEqual(a + week, self.theclass(2002, 3, 9))
1113 self.assertEqual(a - week, self.theclass(2002, 2, 23))
1114 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
1115 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
1116 self.assertEqual((a + week) - a, week)
1117 self.assertEqual((a + day) - a, day)
1118 self.assertEqual((a - week) - a, -week)
1119 self.assertEqual((a - day) - a, -day)
1120 self.assertEqual(a - (a + week), -week)
1121 self.assertEqual(a - (a + day), -day)
1122 self.assertEqual(a - (a - week), week)
1123 self.assertEqual(a - (a - day), day)
1124 self.assertEqual(c - (c - day), day)
1125
1126 # Add/sub ints or floats should be illegal
1127 for i in 1, 1.0:
1128 self.assertRaises(TypeError, lambda: a+i)
1129 self.assertRaises(TypeError, lambda: a-i)
1130 self.assertRaises(TypeError, lambda: i+a)
1131 self.assertRaises(TypeError, lambda: i-a)
1132
1133 # delta - date is senseless.
1134 self.assertRaises(TypeError, lambda: day - a)
1135 # mixing date and (delta or date) via * or // is senseless
1136 self.assertRaises(TypeError, lambda: day * a)
1137 self.assertRaises(TypeError, lambda: a * day)
1138 self.assertRaises(TypeError, lambda: day // a)
1139 self.assertRaises(TypeError, lambda: a // day)
1140 self.assertRaises(TypeError, lambda: a * a)
1141 self.assertRaises(TypeError, lambda: a // a)
1142 # date + date is senseless
1143 self.assertRaises(TypeError, lambda: a + a)
1144
1145 def test_overflow(self):
1146 tiny = self.theclass.resolution
1147
1148 for delta in [tiny, timedelta(1), timedelta(2)]:
1149 dt = self.theclass.min + delta
1150 dt -= delta # no problem
1151 self.assertRaises(OverflowError, dt.__sub__, delta)
1152 self.assertRaises(OverflowError, dt.__add__, -delta)
1153
1154 dt = self.theclass.max - delta
1155 dt += delta # no problem
1156 self.assertRaises(OverflowError, dt.__add__, delta)
1157 self.assertRaises(OverflowError, dt.__sub__, -delta)
1158
1159 def test_fromtimestamp(self):
1160 import time
1161
1162 # Try an arbitrary fixed value.
1163 year, month, day = 1999, 9, 19
1164 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
1165 d = self.theclass.fromtimestamp(ts)
1166 self.assertEqual(d.year, year)
1167 self.assertEqual(d.month, month)
1168 self.assertEqual(d.day, day)
1169
1170 def test_insane_fromtimestamp(self):
1171 # It's possible that some platform maps time_t to double,
1172 # and that this test will fail there. This test should
1173 # exempt such platforms (provided they return reasonable
1174 # results!).
1175 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001176 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001177 insane)
1178
1179 def test_today(self):
1180 import time
1181
1182 # We claim that today() is like fromtimestamp(time.time()), so
1183 # prove it.
1184 for dummy in range(3):
1185 today = self.theclass.today()
1186 ts = time.time()
1187 todayagain = self.theclass.fromtimestamp(ts)
1188 if today == todayagain:
1189 break
1190 # There are several legit reasons that could fail:
1191 # 1. It recently became midnight, between the today() and the
1192 # time() calls.
1193 # 2. The platform time() has such fine resolution that we'll
1194 # never get the same value twice.
1195 # 3. The platform time() has poor resolution, and we just
1196 # happened to call today() right before a resolution quantum
1197 # boundary.
1198 # 4. The system clock got fiddled between calls.
1199 # In any case, wait a little while and try again.
1200 time.sleep(0.1)
1201
1202 # It worked or it didn't. If it didn't, assume it's reason #2, and
1203 # let the test pass if they're within half a second of each other.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001204 if today != todayagain:
1205 self.assertAlmostEqual(todayagain, today,
1206 delta=timedelta(seconds=0.5))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001207
1208 def test_weekday(self):
1209 for i in range(7):
1210 # March 4, 2002 is a Monday
1211 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
1212 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
1213 # January 2, 1956 is a Monday
1214 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
1215 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
1216
1217 def test_isocalendar(self):
1218 # Check examples from
1219 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1220 for i in range(7):
1221 d = self.theclass(2003, 12, 22+i)
1222 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
1223 d = self.theclass(2003, 12, 29) + timedelta(i)
1224 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
1225 d = self.theclass(2004, 1, 5+i)
1226 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
1227 d = self.theclass(2009, 12, 21+i)
1228 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
1229 d = self.theclass(2009, 12, 28) + timedelta(i)
1230 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
1231 d = self.theclass(2010, 1, 4+i)
1232 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
1233
1234 def test_iso_long_years(self):
1235 # Calculate long ISO years and compare to table from
1236 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1237 ISO_LONG_YEARS_TABLE = """
1238 4 32 60 88
1239 9 37 65 93
1240 15 43 71 99
1241 20 48 76
1242 26 54 82
1243
1244 105 133 161 189
1245 111 139 167 195
1246 116 144 172
1247 122 150 178
1248 128 156 184
1249
1250 201 229 257 285
1251 207 235 263 291
1252 212 240 268 296
1253 218 246 274
1254 224 252 280
1255
1256 303 331 359 387
1257 308 336 364 392
1258 314 342 370 398
1259 320 348 376
1260 325 353 381
1261 """
1262 iso_long_years = sorted(map(int, ISO_LONG_YEARS_TABLE.split()))
1263 L = []
1264 for i in range(400):
1265 d = self.theclass(2000+i, 12, 31)
1266 d1 = self.theclass(1600+i, 12, 31)
1267 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
1268 if d.isocalendar()[1] == 53:
1269 L.append(i)
1270 self.assertEqual(L, iso_long_years)
1271
1272 def test_isoformat(self):
1273 t = self.theclass(2, 3, 2)
1274 self.assertEqual(t.isoformat(), "0002-03-02")
1275
1276 def test_ctime(self):
1277 t = self.theclass(2002, 3, 2)
1278 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
1279
1280 def test_strftime(self):
1281 t = self.theclass(2005, 3, 2)
1282 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
1283 self.assertEqual(t.strftime(""), "") # SF bug #761337
1284 self.assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784
1285
1286 self.assertRaises(TypeError, t.strftime) # needs an arg
1287 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
1288 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
1289
1290 # test that unicode input is allowed (issue 2782)
1291 self.assertEqual(t.strftime("%m"), "03")
1292
1293 # A naive object replaces %z and %Z w/ empty strings.
1294 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1295
1296 #make sure that invalid format specifiers are handled correctly
1297 #self.assertRaises(ValueError, t.strftime, "%e")
1298 #self.assertRaises(ValueError, t.strftime, "%")
1299 #self.assertRaises(ValueError, t.strftime, "%#")
1300
1301 #oh well, some systems just ignore those invalid ones.
Martin Panter46f50722016-05-26 05:35:26 +00001302 #at least, exercise them to make sure that no crashes
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001303 #are generated
1304 for f in ["%e", "%", "%#"]:
1305 try:
1306 t.strftime(f)
1307 except ValueError:
1308 pass
1309
1310 #check that this standard extension works
1311 t.strftime("%f")
1312
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001313 def test_format(self):
1314 dt = self.theclass(2007, 9, 10)
1315 self.assertEqual(dt.__format__(''), str(dt))
1316
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02001317 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001318 dt.__format__(123)
1319
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001320 # check that a derived class's __str__() gets called
1321 class A(self.theclass):
1322 def __str__(self):
1323 return 'A'
1324 a = A(2007, 9, 10)
1325 self.assertEqual(a.__format__(''), 'A')
1326
1327 # check that a derived class's strftime gets called
1328 class B(self.theclass):
1329 def strftime(self, format_spec):
1330 return 'B'
1331 b = B(2007, 9, 10)
1332 self.assertEqual(b.__format__(''), str(dt))
1333
1334 for fmt in ["m:%m d:%d y:%y",
1335 "m:%m d:%d y:%y H:%H M:%M S:%S",
1336 "%z %Z",
1337 ]:
1338 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1339 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1340 self.assertEqual(b.__format__(fmt), 'B')
1341
1342 def test_resolution_info(self):
1343 # XXX: Should min and max respect subclassing?
1344 if issubclass(self.theclass, datetime):
1345 expected_class = datetime
1346 else:
1347 expected_class = date
1348 self.assertIsInstance(self.theclass.min, expected_class)
1349 self.assertIsInstance(self.theclass.max, expected_class)
1350 self.assertIsInstance(self.theclass.resolution, timedelta)
1351 self.assertTrue(self.theclass.max > self.theclass.min)
1352
1353 def test_extreme_timedelta(self):
1354 big = self.theclass.max - self.theclass.min
1355 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
1356 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
1357 # n == 315537897599999999 ~= 2**58.13
1358 justasbig = timedelta(0, 0, n)
1359 self.assertEqual(big, justasbig)
1360 self.assertEqual(self.theclass.min + big, self.theclass.max)
1361 self.assertEqual(self.theclass.max - big, self.theclass.min)
1362
1363 def test_timetuple(self):
1364 for i in range(7):
1365 # January 2, 1956 is a Monday (0)
1366 d = self.theclass(1956, 1, 2+i)
1367 t = d.timetuple()
1368 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
1369 # February 1, 1956 is a Wednesday (2)
1370 d = self.theclass(1956, 2, 1+i)
1371 t = d.timetuple()
1372 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
1373 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
1374 # of the year.
1375 d = self.theclass(1956, 3, 1+i)
1376 t = d.timetuple()
1377 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
1378 self.assertEqual(t.tm_year, 1956)
1379 self.assertEqual(t.tm_mon, 3)
1380 self.assertEqual(t.tm_mday, 1+i)
1381 self.assertEqual(t.tm_hour, 0)
1382 self.assertEqual(t.tm_min, 0)
1383 self.assertEqual(t.tm_sec, 0)
1384 self.assertEqual(t.tm_wday, (3+i)%7)
1385 self.assertEqual(t.tm_yday, 61+i)
1386 self.assertEqual(t.tm_isdst, -1)
1387
1388 def test_pickling(self):
1389 args = 6, 7, 23
1390 orig = self.theclass(*args)
1391 for pickler, unpickler, proto in pickle_choices:
1392 green = pickler.dumps(orig, proto)
1393 derived = unpickler.loads(green)
1394 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02001395 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001396
1397 def test_compare(self):
1398 t1 = self.theclass(2, 3, 4)
1399 t2 = self.theclass(2, 3, 4)
1400 self.assertEqual(t1, t2)
1401 self.assertTrue(t1 <= t2)
1402 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001403 self.assertFalse(t1 != t2)
1404 self.assertFalse(t1 < t2)
1405 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001406
1407 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
1408 t2 = self.theclass(*args) # this is larger than t1
1409 self.assertTrue(t1 < t2)
1410 self.assertTrue(t2 > t1)
1411 self.assertTrue(t1 <= t2)
1412 self.assertTrue(t2 >= t1)
1413 self.assertTrue(t1 != t2)
1414 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001415 self.assertFalse(t1 == t2)
1416 self.assertFalse(t2 == t1)
1417 self.assertFalse(t1 > t2)
1418 self.assertFalse(t2 < t1)
1419 self.assertFalse(t1 >= t2)
1420 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001421
1422 for badarg in OTHERSTUFF:
1423 self.assertEqual(t1 == badarg, False)
1424 self.assertEqual(t1 != badarg, True)
1425 self.assertEqual(badarg == t1, False)
1426 self.assertEqual(badarg != t1, True)
1427
1428 self.assertRaises(TypeError, lambda: t1 < badarg)
1429 self.assertRaises(TypeError, lambda: t1 > badarg)
1430 self.assertRaises(TypeError, lambda: t1 >= badarg)
1431 self.assertRaises(TypeError, lambda: badarg <= t1)
1432 self.assertRaises(TypeError, lambda: badarg < t1)
1433 self.assertRaises(TypeError, lambda: badarg > t1)
1434 self.assertRaises(TypeError, lambda: badarg >= t1)
1435
1436 def test_mixed_compare(self):
1437 our = self.theclass(2000, 4, 5)
1438
1439 # Our class can be compared for equality to other classes
1440 self.assertEqual(our == 1, False)
1441 self.assertEqual(1 == our, False)
1442 self.assertEqual(our != 1, True)
1443 self.assertEqual(1 != our, True)
1444
1445 # But the ordering is undefined
1446 self.assertRaises(TypeError, lambda: our < 1)
1447 self.assertRaises(TypeError, lambda: 1 < our)
1448
1449 # Repeat those tests with a different class
1450
1451 class SomeClass:
1452 pass
1453
1454 their = SomeClass()
1455 self.assertEqual(our == their, False)
1456 self.assertEqual(their == our, False)
1457 self.assertEqual(our != their, True)
1458 self.assertEqual(their != our, True)
1459 self.assertRaises(TypeError, lambda: our < their)
1460 self.assertRaises(TypeError, lambda: their < our)
1461
1462 # However, if the other class explicitly defines ordering
1463 # relative to our class, it is allowed to do so
1464
1465 class LargerThanAnything:
1466 def __lt__(self, other):
1467 return False
1468 def __le__(self, other):
1469 return isinstance(other, LargerThanAnything)
1470 def __eq__(self, other):
1471 return isinstance(other, LargerThanAnything)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001472 def __gt__(self, other):
1473 return not isinstance(other, LargerThanAnything)
1474 def __ge__(self, other):
1475 return True
1476
1477 their = LargerThanAnything()
1478 self.assertEqual(our == their, False)
1479 self.assertEqual(their == our, False)
1480 self.assertEqual(our != their, True)
1481 self.assertEqual(their != our, True)
1482 self.assertEqual(our < their, True)
1483 self.assertEqual(their < our, False)
1484
1485 def test_bool(self):
1486 # All dates are considered true.
1487 self.assertTrue(self.theclass.min)
1488 self.assertTrue(self.theclass.max)
1489
Alexander Belopolsky89da3492011-05-02 13:14:24 -04001490 def test_strftime_y2k(self):
1491 for y in (1, 49, 70, 99, 100, 999, 1000, 1970):
Florent Xicluna49ce0682011-11-01 12:56:14 +01001492 d = self.theclass(y, 1, 1)
1493 # Issue 13305: For years < 1000, the value is not always
1494 # padded to 4 digits across platforms. The C standard
1495 # assumes year >= 1900, so it does not specify the number
1496 # of digits.
1497 if d.strftime("%Y") != '%04d' % y:
1498 # Year 42 returns '42', not padded
1499 self.assertEqual(d.strftime("%Y"), '%d' % y)
Victor Stinner15a83e82016-03-12 08:16:48 +01001500 # '0042' is obtained anyway
1501 self.assertEqual(d.strftime("%4Y"), '%04d' % y)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001502
1503 def test_replace(self):
1504 cls = self.theclass
1505 args = [1, 2, 3]
1506 base = cls(*args)
1507 self.assertEqual(base, base.replace())
1508
1509 i = 0
1510 for name, newval in (("year", 2),
1511 ("month", 3),
1512 ("day", 4)):
1513 newargs = args[:]
1514 newargs[i] = newval
1515 expected = cls(*newargs)
1516 got = base.replace(**{name: newval})
1517 self.assertEqual(expected, got)
1518 i += 1
1519
1520 # Out of bounds.
1521 base = cls(2000, 2, 29)
1522 self.assertRaises(ValueError, base.replace, year=2001)
1523
Paul Ganssle191e9932017-11-09 16:34:29 -05001524 def test_subclass_replace(self):
1525 class DateSubclass(self.theclass):
1526 pass
1527
1528 dt = DateSubclass(2012, 1, 1)
1529 self.assertIs(type(dt.replace(year=2013)), DateSubclass)
1530
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001531 def test_subclass_date(self):
1532
1533 class C(self.theclass):
1534 theAnswer = 42
1535
1536 def __new__(cls, *args, **kws):
1537 temp = kws.copy()
1538 extra = temp.pop('extra')
1539 result = self.theclass.__new__(cls, *args, **temp)
1540 result.extra = extra
1541 return result
1542
1543 def newmeth(self, start):
1544 return start + self.year + self.month
1545
1546 args = 2003, 4, 14
1547
1548 dt1 = self.theclass(*args)
1549 dt2 = C(*args, **{'extra': 7})
1550
1551 self.assertEqual(dt2.__class__, C)
1552 self.assertEqual(dt2.theAnswer, 42)
1553 self.assertEqual(dt2.extra, 7)
1554 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1555 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1556
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05001557 def test_subclass_alternate_constructors(self):
1558 # Test that alternate constructors call the constructor
1559 class DateSubclass(self.theclass):
1560 def __new__(cls, *args, **kwargs):
1561 result = self.theclass.__new__(cls, *args, **kwargs)
1562 result.extra = 7
1563
1564 return result
1565
1566 args = (2003, 4, 14)
1567 d_ord = 731319 # Equivalent ordinal date
1568 d_isoformat = '2003-04-14' # Equivalent isoformat()
1569
1570 base_d = DateSubclass(*args)
1571 self.assertIsInstance(base_d, DateSubclass)
1572 self.assertEqual(base_d.extra, 7)
1573
1574 # Timestamp depends on time zone, so we'll calculate the equivalent here
1575 ts = datetime.combine(base_d, time(0)).timestamp()
1576
1577 test_cases = [
1578 ('fromordinal', (d_ord,)),
1579 ('fromtimestamp', (ts,)),
1580 ('fromisoformat', (d_isoformat,)),
1581 ]
1582
1583 for constr_name, constr_args in test_cases:
1584 for base_obj in (DateSubclass, base_d):
1585 # Test both the classmethod and method
1586 with self.subTest(base_obj_type=type(base_obj),
1587 constr_name=constr_name):
1588 constr = getattr(base_obj, constr_name)
1589
1590 dt = constr(*constr_args)
1591
1592 # Test that it creates the right subclass
1593 self.assertIsInstance(dt, DateSubclass)
1594
1595 # Test that it's equal to the base object
1596 self.assertEqual(dt, base_d)
1597
1598 # Test that it called the constructor
1599 self.assertEqual(dt.extra, 7)
1600
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001601 def test_pickling_subclass_date(self):
1602
1603 args = 6, 7, 23
1604 orig = SubclassDate(*args)
1605 for pickler, unpickler, proto in pickle_choices:
1606 green = pickler.dumps(orig, proto)
1607 derived = unpickler.loads(green)
1608 self.assertEqual(orig, derived)
1609
1610 def test_backdoor_resistance(self):
1611 # For fast unpickling, the constructor accepts a pickle byte string.
1612 # This is a low-overhead backdoor. A user can (by intent or
1613 # mistake) pass a string directly, which (if it's the right length)
1614 # will get treated like a pickle, and bypass the normal sanity
1615 # checks in the constructor. This can create insane objects.
1616 # The constructor doesn't want to burn the time to validate all
1617 # fields, but does check the month field. This stops, e.g.,
1618 # datetime.datetime('1995-03-25') from yielding an insane object.
1619 base = b'1995-03-25'
1620 if not issubclass(self.theclass, datetime):
1621 base = base[:4]
1622 for month_byte in b'9', b'\0', b'\r', b'\xff':
1623 self.assertRaises(TypeError, self.theclass,
1624 base[:2] + month_byte + base[3:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001625 if issubclass(self.theclass, datetime):
1626 # Good bytes, but bad tzinfo:
1627 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
1628 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001629
1630 for ord_byte in range(1, 13):
1631 # This shouldn't blow up because of the month byte alone. If
1632 # the implementation changes to do more-careful checking, it may
1633 # blow up because other fields are insane.
1634 self.theclass(base[:2] + bytes([ord_byte]) + base[3:])
1635
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001636 def test_fromisoformat(self):
1637 # Test that isoformat() is reversible
1638 base_dates = [
1639 (1, 1, 1),
1640 (1000, 2, 14),
1641 (1900, 1, 1),
1642 (2000, 2, 29),
1643 (2004, 11, 12),
1644 (2004, 4, 3),
1645 (2017, 5, 30)
1646 ]
1647
1648 for dt_tuple in base_dates:
1649 dt = self.theclass(*dt_tuple)
1650 dt_str = dt.isoformat()
1651 with self.subTest(dt_str=dt_str):
1652 dt_rt = self.theclass.fromisoformat(dt.isoformat())
1653
1654 self.assertEqual(dt, dt_rt)
1655
1656 def test_fromisoformat_subclass(self):
1657 class DateSubclass(self.theclass):
1658 pass
1659
1660 dt = DateSubclass(2014, 12, 14)
1661
1662 dt_rt = DateSubclass.fromisoformat(dt.isoformat())
1663
1664 self.assertIsInstance(dt_rt, DateSubclass)
1665
1666 def test_fromisoformat_fails(self):
1667 # Test that fromisoformat() fails on invalid values
1668 bad_strs = [
1669 '', # Empty string
1670 '009-03-04', # Not 10 characters
1671 '123456789', # Not a date
1672 '200a-12-04', # Invalid character in year
1673 '2009-1a-04', # Invalid character in month
1674 '2009-12-0a', # Invalid character in day
1675 '2009-01-32', # Invalid day
1676 '2009-02-29', # Invalid leap day
1677 '20090228', # Valid ISO8601 output not from isoformat()
1678 ]
1679
1680 for bad_str in bad_strs:
1681 with self.assertRaises(ValueError):
1682 self.theclass.fromisoformat(bad_str)
1683
1684 def test_fromisoformat_fails_typeerror(self):
1685 # Test that fromisoformat fails when passed the wrong type
1686 import io
1687
1688 bad_types = [b'2009-03-01', None, io.StringIO('2009-03-01')]
1689 for bad_type in bad_types:
1690 with self.assertRaises(TypeError):
1691 self.theclass.fromisoformat(bad_type)
1692
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001693#############################################################################
1694# datetime tests
1695
1696class SubclassDatetime(datetime):
1697 sub_var = 1
1698
1699class TestDateTime(TestDate):
1700
1701 theclass = datetime
1702
1703 def test_basic_attributes(self):
1704 dt = self.theclass(2002, 3, 1, 12, 0)
1705 self.assertEqual(dt.year, 2002)
1706 self.assertEqual(dt.month, 3)
1707 self.assertEqual(dt.day, 1)
1708 self.assertEqual(dt.hour, 12)
1709 self.assertEqual(dt.minute, 0)
1710 self.assertEqual(dt.second, 0)
1711 self.assertEqual(dt.microsecond, 0)
1712
1713 def test_basic_attributes_nonzero(self):
1714 # Make sure all attributes are non-zero so bugs in
1715 # bit-shifting access show up.
1716 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1717 self.assertEqual(dt.year, 2002)
1718 self.assertEqual(dt.month, 3)
1719 self.assertEqual(dt.day, 1)
1720 self.assertEqual(dt.hour, 12)
1721 self.assertEqual(dt.minute, 59)
1722 self.assertEqual(dt.second, 59)
1723 self.assertEqual(dt.microsecond, 8000)
1724
1725 def test_roundtrip(self):
1726 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1727 self.theclass.now()):
1728 # Verify dt -> string -> datetime identity.
1729 s = repr(dt)
1730 self.assertTrue(s.startswith('datetime.'))
1731 s = s[9:]
1732 dt2 = eval(s)
1733 self.assertEqual(dt, dt2)
1734
1735 # Verify identity via reconstructing from pieces.
1736 dt2 = self.theclass(dt.year, dt.month, dt.day,
1737 dt.hour, dt.minute, dt.second,
1738 dt.microsecond)
1739 self.assertEqual(dt, dt2)
1740
1741 def test_isoformat(self):
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001742 t = self.theclass(1, 2, 3, 4, 5, 1, 123)
1743 self.assertEqual(t.isoformat(), "0001-02-03T04:05:01.000123")
1744 self.assertEqual(t.isoformat('T'), "0001-02-03T04:05:01.000123")
1745 self.assertEqual(t.isoformat(' '), "0001-02-03 04:05:01.000123")
1746 self.assertEqual(t.isoformat('\x00'), "0001-02-03\x0004:05:01.000123")
1747 self.assertEqual(t.isoformat(timespec='hours'), "0001-02-03T04")
1748 self.assertEqual(t.isoformat(timespec='minutes'), "0001-02-03T04:05")
1749 self.assertEqual(t.isoformat(timespec='seconds'), "0001-02-03T04:05:01")
1750 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1751 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000123")
1752 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01.000123")
1753 self.assertEqual(t.isoformat(sep=' ', timespec='minutes'), "0001-02-03 04:05")
1754 self.assertRaises(ValueError, t.isoformat, timespec='foo')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001755 # str is ISO format with the separator forced to a blank.
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001756 self.assertEqual(str(t), "0001-02-03 04:05:01.000123")
1757
1758 t = self.theclass(1, 2, 3, 4, 5, 1, 999500, tzinfo=timezone.utc)
1759 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999+00:00")
1760
1761 t = self.theclass(1, 2, 3, 4, 5, 1, 999500)
1762 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999")
1763
1764 t = self.theclass(1, 2, 3, 4, 5, 1)
1765 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01")
1766 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1767 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000000")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001768
1769 t = self.theclass(2, 3, 2)
1770 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1771 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1772 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1773 # str is ISO format with the separator forced to a blank.
1774 self.assertEqual(str(t), "0002-03-02 00:00:00")
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001775 # ISO format with timezone
1776 tz = FixedOffset(timedelta(seconds=16), 'XXX')
1777 t = self.theclass(2, 3, 2, tzinfo=tz)
1778 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00+00:00:16")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001779
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001780 def test_isoformat_timezone(self):
1781 tzoffsets = [
1782 ('05:00', timedelta(hours=5)),
1783 ('02:00', timedelta(hours=2)),
1784 ('06:27', timedelta(hours=6, minutes=27)),
1785 ('12:32:30', timedelta(hours=12, minutes=32, seconds=30)),
1786 ('02:04:09.123456', timedelta(hours=2, minutes=4, seconds=9, microseconds=123456))
1787 ]
1788
1789 tzinfos = [
1790 ('', None),
1791 ('+00:00', timezone.utc),
1792 ('+00:00', timezone(timedelta(0))),
1793 ]
1794
1795 tzinfos += [
1796 (prefix + expected, timezone(sign * td))
1797 for expected, td in tzoffsets
1798 for prefix, sign in [('-', -1), ('+', 1)]
1799 ]
1800
1801 dt_base = self.theclass(2016, 4, 1, 12, 37, 9)
1802 exp_base = '2016-04-01T12:37:09'
1803
1804 for exp_tz, tzi in tzinfos:
1805 dt = dt_base.replace(tzinfo=tzi)
1806 exp = exp_base + exp_tz
1807 with self.subTest(tzi=tzi):
1808 assert dt.isoformat() == exp
1809
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001810 def test_format(self):
1811 dt = self.theclass(2007, 9, 10, 4, 5, 1, 123)
1812 self.assertEqual(dt.__format__(''), str(dt))
1813
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02001814 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001815 dt.__format__(123)
1816
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001817 # check that a derived class's __str__() gets called
1818 class A(self.theclass):
1819 def __str__(self):
1820 return 'A'
1821 a = A(2007, 9, 10, 4, 5, 1, 123)
1822 self.assertEqual(a.__format__(''), 'A')
1823
1824 # check that a derived class's strftime gets called
1825 class B(self.theclass):
1826 def strftime(self, format_spec):
1827 return 'B'
1828 b = B(2007, 9, 10, 4, 5, 1, 123)
1829 self.assertEqual(b.__format__(''), str(dt))
1830
1831 for fmt in ["m:%m d:%d y:%y",
1832 "m:%m d:%d y:%y H:%H M:%M S:%S",
1833 "%z %Z",
1834 ]:
1835 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1836 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1837 self.assertEqual(b.__format__(fmt), 'B')
1838
1839 def test_more_ctime(self):
1840 # Test fields that TestDate doesn't touch.
1841 import time
1842
1843 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1844 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1845 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1846 # out. The difference is that t.ctime() produces " 2" for the day,
1847 # but platform ctime() produces "02" for the day. According to
1848 # C99, t.ctime() is correct here.
1849 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1850
1851 # So test a case where that difference doesn't matter.
1852 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1853 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1854
1855 def test_tz_independent_comparing(self):
1856 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1857 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1858 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1859 self.assertEqual(dt1, dt3)
1860 self.assertTrue(dt2 > dt3)
1861
1862 # Make sure comparison doesn't forget microseconds, and isn't done
1863 # via comparing a float timestamp (an IEEE double doesn't have enough
Miss Islington (bot)e86db342018-02-03 17:41:43 -08001864 # precision to span microsecond resolution across years 1 through 9999,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001865 # so comparing via timestamp necessarily calls some distinct values
1866 # equal).
1867 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1868 us = timedelta(microseconds=1)
1869 dt2 = dt1 + us
1870 self.assertEqual(dt2 - dt1, us)
1871 self.assertTrue(dt1 < dt2)
1872
1873 def test_strftime_with_bad_tzname_replace(self):
1874 # verify ok if tzinfo.tzname().replace() returns a non-string
1875 class MyTzInfo(FixedOffset):
1876 def tzname(self, dt):
1877 class MyStr(str):
1878 def replace(self, *args):
1879 return None
1880 return MyStr('name')
1881 t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
1882 self.assertRaises(TypeError, t.strftime, '%Z')
1883
1884 def test_bad_constructor_arguments(self):
1885 # bad years
1886 self.theclass(MINYEAR, 1, 1) # no exception
1887 self.theclass(MAXYEAR, 1, 1) # no exception
1888 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1889 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1890 # bad months
1891 self.theclass(2000, 1, 1) # no exception
1892 self.theclass(2000, 12, 1) # no exception
1893 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1894 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1895 # bad days
1896 self.theclass(2000, 2, 29) # no exception
1897 self.theclass(2004, 2, 29) # no exception
1898 self.theclass(2400, 2, 29) # no exception
1899 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1900 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1901 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1902 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1903 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1904 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1905 # bad hours
1906 self.theclass(2000, 1, 31, 0) # no exception
1907 self.theclass(2000, 1, 31, 23) # no exception
1908 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1909 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1910 # bad minutes
1911 self.theclass(2000, 1, 31, 23, 0) # no exception
1912 self.theclass(2000, 1, 31, 23, 59) # no exception
1913 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1914 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1915 # bad seconds
1916 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1917 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1918 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1919 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1920 # bad microseconds
1921 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1922 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1923 self.assertRaises(ValueError, self.theclass,
1924 2000, 1, 31, 23, 59, 59, -1)
1925 self.assertRaises(ValueError, self.theclass,
1926 2000, 1, 31, 23, 59, 59,
1927 1000000)
Alexander Belopolsky47649ab2016-08-08 17:05:40 -04001928 # bad fold
1929 self.assertRaises(ValueError, self.theclass,
1930 2000, 1, 31, fold=-1)
1931 self.assertRaises(ValueError, self.theclass,
1932 2000, 1, 31, fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001933 # Positional fold:
1934 self.assertRaises(TypeError, self.theclass,
1935 2000, 1, 31, 23, 59, 59, 0, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04001936
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001937 def test_hash_equality(self):
1938 d = self.theclass(2000, 12, 31, 23, 30, 17)
1939 e = self.theclass(2000, 12, 31, 23, 30, 17)
1940 self.assertEqual(d, e)
1941 self.assertEqual(hash(d), hash(e))
1942
1943 dic = {d: 1}
1944 dic[e] = 2
1945 self.assertEqual(len(dic), 1)
1946 self.assertEqual(dic[d], 2)
1947 self.assertEqual(dic[e], 2)
1948
1949 d = self.theclass(2001, 1, 1, 0, 5, 17)
1950 e = self.theclass(2001, 1, 1, 0, 5, 17)
1951 self.assertEqual(d, e)
1952 self.assertEqual(hash(d), hash(e))
1953
1954 dic = {d: 1}
1955 dic[e] = 2
1956 self.assertEqual(len(dic), 1)
1957 self.assertEqual(dic[d], 2)
1958 self.assertEqual(dic[e], 2)
1959
1960 def test_computations(self):
1961 a = self.theclass(2002, 1, 31)
1962 b = self.theclass(1956, 1, 31)
1963 diff = a-b
1964 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1965 self.assertEqual(diff.seconds, 0)
1966 self.assertEqual(diff.microseconds, 0)
1967 a = self.theclass(2002, 3, 2, 17, 6)
1968 millisec = timedelta(0, 0, 1000)
1969 hour = timedelta(0, 3600)
1970 day = timedelta(1)
1971 week = timedelta(7)
1972 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1973 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1974 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1975 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1976 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1977 self.assertEqual(a - hour, a + -hour)
1978 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1979 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1980 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1981 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1982 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1983 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1984 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1985 self.assertEqual((a + week) - a, week)
1986 self.assertEqual((a + day) - a, day)
1987 self.assertEqual((a + hour) - a, hour)
1988 self.assertEqual((a + millisec) - a, millisec)
1989 self.assertEqual((a - week) - a, -week)
1990 self.assertEqual((a - day) - a, -day)
1991 self.assertEqual((a - hour) - a, -hour)
1992 self.assertEqual((a - millisec) - a, -millisec)
1993 self.assertEqual(a - (a + week), -week)
1994 self.assertEqual(a - (a + day), -day)
1995 self.assertEqual(a - (a + hour), -hour)
1996 self.assertEqual(a - (a + millisec), -millisec)
1997 self.assertEqual(a - (a - week), week)
1998 self.assertEqual(a - (a - day), day)
1999 self.assertEqual(a - (a - hour), hour)
2000 self.assertEqual(a - (a - millisec), millisec)
2001 self.assertEqual(a + (week + day + hour + millisec),
2002 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
2003 self.assertEqual(a + (week + day + hour + millisec),
2004 (((a + week) + day) + hour) + millisec)
2005 self.assertEqual(a - (week + day + hour + millisec),
2006 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
2007 self.assertEqual(a - (week + day + hour + millisec),
2008 (((a - week) - day) - hour) - millisec)
2009 # Add/sub ints or floats should be illegal
2010 for i in 1, 1.0:
2011 self.assertRaises(TypeError, lambda: a+i)
2012 self.assertRaises(TypeError, lambda: a-i)
2013 self.assertRaises(TypeError, lambda: i+a)
2014 self.assertRaises(TypeError, lambda: i-a)
2015
2016 # delta - datetime is senseless.
2017 self.assertRaises(TypeError, lambda: day - a)
2018 # mixing datetime and (delta or datetime) via * or // is senseless
2019 self.assertRaises(TypeError, lambda: day * a)
2020 self.assertRaises(TypeError, lambda: a * day)
2021 self.assertRaises(TypeError, lambda: day // a)
2022 self.assertRaises(TypeError, lambda: a // day)
2023 self.assertRaises(TypeError, lambda: a * a)
2024 self.assertRaises(TypeError, lambda: a // a)
2025 # datetime + datetime is senseless
2026 self.assertRaises(TypeError, lambda: a + a)
2027
2028 def test_pickling(self):
2029 args = 6, 7, 23, 20, 59, 1, 64**2
2030 orig = self.theclass(*args)
2031 for pickler, unpickler, proto in pickle_choices:
2032 green = pickler.dumps(orig, proto)
2033 derived = unpickler.loads(green)
2034 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02002035 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002036
2037 def test_more_pickling(self):
2038 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
Serhiy Storchakabad12572014-12-15 14:03:42 +02002039 for proto in range(pickle.HIGHEST_PROTOCOL + 1):
2040 s = pickle.dumps(a, proto)
2041 b = pickle.loads(s)
2042 self.assertEqual(b.year, 2003)
2043 self.assertEqual(b.month, 2)
2044 self.assertEqual(b.day, 7)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002045
2046 def test_pickling_subclass_datetime(self):
2047 args = 6, 7, 23, 20, 59, 1, 64**2
2048 orig = SubclassDatetime(*args)
2049 for pickler, unpickler, proto in pickle_choices:
2050 green = pickler.dumps(orig, proto)
2051 derived = unpickler.loads(green)
2052 self.assertEqual(orig, derived)
2053
2054 def test_more_compare(self):
2055 # The test_compare() inherited from TestDate covers the error cases.
2056 # We just want to test lexicographic ordering on the members datetime
2057 # has that date lacks.
2058 args = [2000, 11, 29, 20, 58, 16, 999998]
2059 t1 = self.theclass(*args)
2060 t2 = self.theclass(*args)
2061 self.assertEqual(t1, t2)
2062 self.assertTrue(t1 <= t2)
2063 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002064 self.assertFalse(t1 != t2)
2065 self.assertFalse(t1 < t2)
2066 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002067
2068 for i in range(len(args)):
2069 newargs = args[:]
2070 newargs[i] = args[i] + 1
2071 t2 = self.theclass(*newargs) # this is larger than t1
2072 self.assertTrue(t1 < t2)
2073 self.assertTrue(t2 > t1)
2074 self.assertTrue(t1 <= t2)
2075 self.assertTrue(t2 >= t1)
2076 self.assertTrue(t1 != t2)
2077 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002078 self.assertFalse(t1 == t2)
2079 self.assertFalse(t2 == t1)
2080 self.assertFalse(t1 > t2)
2081 self.assertFalse(t2 < t1)
2082 self.assertFalse(t1 >= t2)
2083 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002084
2085
2086 # A helper for timestamp constructor tests.
2087 def verify_field_equality(self, expected, got):
2088 self.assertEqual(expected.tm_year, got.year)
2089 self.assertEqual(expected.tm_mon, got.month)
2090 self.assertEqual(expected.tm_mday, got.day)
2091 self.assertEqual(expected.tm_hour, got.hour)
2092 self.assertEqual(expected.tm_min, got.minute)
2093 self.assertEqual(expected.tm_sec, got.second)
2094
2095 def test_fromtimestamp(self):
2096 import time
2097
2098 ts = time.time()
2099 expected = time.localtime(ts)
2100 got = self.theclass.fromtimestamp(ts)
2101 self.verify_field_equality(expected, got)
2102
2103 def test_utcfromtimestamp(self):
2104 import time
2105
2106 ts = time.time()
2107 expected = time.gmtime(ts)
2108 got = self.theclass.utcfromtimestamp(ts)
2109 self.verify_field_equality(expected, got)
2110
Alexander Belopolskya4415142012-06-08 12:33:09 -04002111 # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in
2112 # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0).
2113 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
2114 def test_timestamp_naive(self):
2115 t = self.theclass(1970, 1, 1)
2116 self.assertEqual(t.timestamp(), 18000.0)
2117 t = self.theclass(1970, 1, 1, 1, 2, 3, 4)
2118 self.assertEqual(t.timestamp(),
2119 18000.0 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002120 # Missing hour
2121 t0 = self.theclass(2012, 3, 11, 2, 30)
2122 t1 = t0.replace(fold=1)
2123 self.assertEqual(self.theclass.fromtimestamp(t1.timestamp()),
2124 t0 - timedelta(hours=1))
2125 self.assertEqual(self.theclass.fromtimestamp(t0.timestamp()),
2126 t1 + timedelta(hours=1))
Alexander Belopolskya4415142012-06-08 12:33:09 -04002127 # Ambiguous hour defaults to DST
2128 t = self.theclass(2012, 11, 4, 1, 30)
2129 self.assertEqual(self.theclass.fromtimestamp(t.timestamp()), t)
2130
2131 # Timestamp may raise an overflow error on some platforms
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002132 # XXX: Do we care to support the first and last year?
2133 for t in [self.theclass(2,1,1), self.theclass(9998,12,12)]:
Alexander Belopolskya4415142012-06-08 12:33:09 -04002134 try:
2135 s = t.timestamp()
2136 except OverflowError:
2137 pass
2138 else:
2139 self.assertEqual(self.theclass.fromtimestamp(s), t)
2140
2141 def test_timestamp_aware(self):
2142 t = self.theclass(1970, 1, 1, tzinfo=timezone.utc)
2143 self.assertEqual(t.timestamp(), 0.0)
2144 t = self.theclass(1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone.utc)
2145 self.assertEqual(t.timestamp(),
2146 3600 + 2*60 + 3 + 4*1e-6)
2147 t = self.theclass(1970, 1, 1, 1, 2, 3, 4,
2148 tzinfo=timezone(timedelta(hours=-5), 'EST'))
2149 self.assertEqual(t.timestamp(),
2150 18000 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002151
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002152 @support.run_with_tz('MSK-03') # Something east of Greenwich
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002153 def test_microsecond_rounding(self):
Victor Stinner5ebfe422015-09-18 14:52:15 +02002154 for fts in [self.theclass.fromtimestamp,
2155 self.theclass.utcfromtimestamp]:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002156 zero = fts(0)
2157 self.assertEqual(zero.second, 0)
2158 self.assertEqual(zero.microsecond, 0)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002159 one = fts(1e-6)
Victor Stinner8050ca92012-03-14 00:17:05 +01002160 try:
2161 minus_one = fts(-1e-6)
2162 except OSError:
2163 # localtime(-1) and gmtime(-1) is not supported on Windows
2164 pass
2165 else:
2166 self.assertEqual(minus_one.second, 59)
2167 self.assertEqual(minus_one.microsecond, 999999)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002168
Victor Stinner8050ca92012-03-14 00:17:05 +01002169 t = fts(-1e-8)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002170 self.assertEqual(t, zero)
Victor Stinner8050ca92012-03-14 00:17:05 +01002171 t = fts(-9e-7)
2172 self.assertEqual(t, minus_one)
2173 t = fts(-1e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002174 self.assertEqual(t, zero)
Victor Stinner8820a352015-09-05 10:50:20 +02002175 t = fts(-1/2**7)
2176 self.assertEqual(t.second, 59)
Victor Stinner7667f582015-09-09 01:02:23 +02002177 self.assertEqual(t.microsecond, 992188)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002178
2179 t = fts(1e-7)
2180 self.assertEqual(t, zero)
2181 t = fts(9e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002182 self.assertEqual(t, one)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002183 t = fts(0.99999949)
2184 self.assertEqual(t.second, 0)
2185 self.assertEqual(t.microsecond, 999999)
2186 t = fts(0.9999999)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002187 self.assertEqual(t.second, 1)
2188 self.assertEqual(t.microsecond, 0)
Victor Stinneradfefa52015-09-04 23:57:25 +02002189 t = fts(1/2**7)
2190 self.assertEqual(t.second, 0)
Victor Stinner7667f582015-09-09 01:02:23 +02002191 self.assertEqual(t.microsecond, 7812)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002192
Victor Stinnerb67f0962017-02-10 10:34:02 +01002193 def test_timestamp_limits(self):
2194 # minimum timestamp
2195 min_dt = self.theclass.min.replace(tzinfo=timezone.utc)
2196 min_ts = min_dt.timestamp()
Victor Stinner6f37e362017-02-10 11:45:14 +01002197 try:
2198 # date 0001-01-01 00:00:00+00:00: timestamp=-62135596800
2199 self.assertEqual(self.theclass.fromtimestamp(min_ts, tz=timezone.utc),
2200 min_dt)
Victor Stinner2a35c932017-02-10 12:37:21 +01002201 except (OverflowError, OSError) as exc:
2202 # the date 0001-01-01 doesn't fit into 32-bit time_t,
2203 # or platform doesn't support such very old date
Victor Stinner6f37e362017-02-10 11:45:14 +01002204 self.skipTest(str(exc))
Victor Stinnerb67f0962017-02-10 10:34:02 +01002205
2206 # maximum timestamp: set seconds to zero to avoid rounding issues
2207 max_dt = self.theclass.max.replace(tzinfo=timezone.utc,
2208 second=0, microsecond=0)
2209 max_ts = max_dt.timestamp()
2210 # date 9999-12-31 23:59:00+00:00: timestamp 253402300740
2211 self.assertEqual(self.theclass.fromtimestamp(max_ts, tz=timezone.utc),
2212 max_dt)
2213
2214 # number of seconds greater than 1 year: make sure that the new date
2215 # is not valid in datetime.datetime limits
2216 delta = 3600 * 24 * 400
2217
2218 # too small
2219 ts = min_ts - delta
2220 # converting a Python int to C time_t can raise a OverflowError,
2221 # especially on 32-bit platforms.
2222 with self.assertRaises((ValueError, OverflowError)):
2223 self.theclass.fromtimestamp(ts)
2224 with self.assertRaises((ValueError, OverflowError)):
2225 self.theclass.utcfromtimestamp(ts)
2226
2227 # too big
2228 ts = max_dt.timestamp() + delta
2229 with self.assertRaises((ValueError, OverflowError)):
2230 self.theclass.fromtimestamp(ts)
2231 with self.assertRaises((ValueError, OverflowError)):
2232 self.theclass.utcfromtimestamp(ts)
2233
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002234 def test_insane_fromtimestamp(self):
2235 # It's possible that some platform maps time_t to double,
2236 # and that this test will fail there. This test should
2237 # exempt such platforms (provided they return reasonable
2238 # results!).
2239 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002240 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002241 insane)
2242
2243 def test_insane_utcfromtimestamp(self):
2244 # It's possible that some platform maps time_t to double,
2245 # and that this test will fail there. This test should
2246 # exempt such platforms (provided they return reasonable
2247 # results!).
2248 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002249 self.assertRaises(OverflowError, self.theclass.utcfromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002250 insane)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002251
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002252 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2253 def test_negative_float_fromtimestamp(self):
2254 # The result is tz-dependent; at least test that this doesn't
2255 # fail (like it did before bug 1646728 was fixed).
2256 self.theclass.fromtimestamp(-1.05)
2257
2258 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2259 def test_negative_float_utcfromtimestamp(self):
2260 d = self.theclass.utcfromtimestamp(-1.05)
2261 self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
2262
2263 def test_utcnow(self):
2264 import time
2265
2266 # Call it a success if utcnow() and utcfromtimestamp() are within
2267 # a second of each other.
2268 tolerance = timedelta(seconds=1)
2269 for dummy in range(3):
2270 from_now = self.theclass.utcnow()
2271 from_timestamp = self.theclass.utcfromtimestamp(time.time())
2272 if abs(from_timestamp - from_now) <= tolerance:
2273 break
2274 # Else try again a few times.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002275 self.assertLessEqual(abs(from_timestamp - from_now), tolerance)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002276
2277 def test_strptime(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002278 string = '2004-12-01 13:02:47.197'
2279 format = '%Y-%m-%d %H:%M:%S.%f'
2280 expected = _strptime._strptime_datetime(self.theclass, string, format)
2281 got = self.theclass.strptime(string, format)
2282 self.assertEqual(expected, got)
2283 self.assertIs(type(expected), self.theclass)
2284 self.assertIs(type(got), self.theclass)
2285
2286 strptime = self.theclass.strptime
2287 self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
2288 self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
Mario Corchero32318932017-10-26 01:35:41 +01002289 self.assertEqual(
2290 strptime("-00:02:01.000003", "%z").utcoffset(),
2291 -timedelta(minutes=2, seconds=1, microseconds=3)
2292 )
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002293 # Only local timezone and UTC are supported
2294 for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
2295 (-_time.timezone, _time.tzname[0])):
2296 if tzseconds < 0:
2297 sign = '-'
2298 seconds = -tzseconds
2299 else:
2300 sign ='+'
2301 seconds = tzseconds
2302 hours, minutes = divmod(seconds//60, 60)
2303 dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
Martin Panterfca22322015-11-16 09:22:19 +00002304 dt = strptime(dtstr, "%z %Z")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002305 self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
2306 self.assertEqual(dt.tzname(), tzname)
2307 # Can produce inconsistent datetime
2308 dtstr, fmt = "+1234 UTC", "%z %Z"
2309 dt = strptime(dtstr, fmt)
2310 self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE)
2311 self.assertEqual(dt.tzname(), 'UTC')
2312 # yet will roundtrip
2313 self.assertEqual(dt.strftime(fmt), dtstr)
2314
2315 # Produce naive datetime if no %z is provided
2316 self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
2317
2318 with self.assertRaises(ValueError): strptime("-2400", "%z")
2319 with self.assertRaises(ValueError): strptime("-000", "%z")
2320
2321 def test_more_timetuple(self):
2322 # This tests fields beyond those tested by the TestDate.test_timetuple.
2323 t = self.theclass(2004, 12, 31, 6, 22, 33)
2324 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
2325 self.assertEqual(t.timetuple(),
2326 (t.year, t.month, t.day,
2327 t.hour, t.minute, t.second,
2328 t.weekday(),
2329 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
2330 -1))
2331 tt = t.timetuple()
2332 self.assertEqual(tt.tm_year, t.year)
2333 self.assertEqual(tt.tm_mon, t.month)
2334 self.assertEqual(tt.tm_mday, t.day)
2335 self.assertEqual(tt.tm_hour, t.hour)
2336 self.assertEqual(tt.tm_min, t.minute)
2337 self.assertEqual(tt.tm_sec, t.second)
2338 self.assertEqual(tt.tm_wday, t.weekday())
2339 self.assertEqual(tt.tm_yday, t.toordinal() -
2340 date(t.year, 1, 1).toordinal() + 1)
2341 self.assertEqual(tt.tm_isdst, -1)
2342
2343 def test_more_strftime(self):
2344 # This tests fields beyond those tested by the TestDate.test_strftime.
2345 t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
2346 self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
2347 "12 31 04 000047 33 22 06 366")
Alexander Belopolsky018d3532017-07-31 10:26:50 -04002348 tz = timezone(-timedelta(hours=2, seconds=33, microseconds=123))
2349 t = t.replace(tzinfo=tz)
2350 self.assertEqual(t.strftime("%z"), "-020033.000123")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002351
2352 def test_extract(self):
2353 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2354 self.assertEqual(dt.date(), date(2002, 3, 4))
2355 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2356
2357 def test_combine(self):
2358 d = date(2002, 3, 4)
2359 t = time(18, 45, 3, 1234)
2360 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2361 combine = self.theclass.combine
2362 dt = combine(d, t)
2363 self.assertEqual(dt, expected)
2364
2365 dt = combine(time=t, date=d)
2366 self.assertEqual(dt, expected)
2367
2368 self.assertEqual(d, dt.date())
2369 self.assertEqual(t, dt.time())
2370 self.assertEqual(dt, combine(dt.date(), dt.time()))
2371
2372 self.assertRaises(TypeError, combine) # need an arg
2373 self.assertRaises(TypeError, combine, d) # need two args
2374 self.assertRaises(TypeError, combine, t, d) # args reversed
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002375 self.assertRaises(TypeError, combine, d, t, 1) # wrong tzinfo type
2376 self.assertRaises(TypeError, combine, d, t, 1, 2) # too many args
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002377 self.assertRaises(TypeError, combine, "date", "time") # wrong types
2378 self.assertRaises(TypeError, combine, d, "time") # wrong type
2379 self.assertRaises(TypeError, combine, "date", t) # wrong type
2380
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002381 # tzinfo= argument
2382 dt = combine(d, t, timezone.utc)
2383 self.assertIs(dt.tzinfo, timezone.utc)
2384 dt = combine(d, t, tzinfo=timezone.utc)
2385 self.assertIs(dt.tzinfo, timezone.utc)
2386 t = time()
2387 dt = combine(dt, t)
2388 self.assertEqual(dt.date(), d)
2389 self.assertEqual(dt.time(), t)
2390
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002391 def test_replace(self):
2392 cls = self.theclass
2393 args = [1, 2, 3, 4, 5, 6, 7]
2394 base = cls(*args)
2395 self.assertEqual(base, base.replace())
2396
2397 i = 0
2398 for name, newval in (("year", 2),
2399 ("month", 3),
2400 ("day", 4),
2401 ("hour", 5),
2402 ("minute", 6),
2403 ("second", 7),
2404 ("microsecond", 8)):
2405 newargs = args[:]
2406 newargs[i] = newval
2407 expected = cls(*newargs)
2408 got = base.replace(**{name: newval})
2409 self.assertEqual(expected, got)
2410 i += 1
2411
2412 # Out of bounds.
2413 base = cls(2000, 2, 29)
2414 self.assertRaises(ValueError, base.replace, year=2001)
2415
2416 def test_astimezone(self):
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002417 return # The rest is no longer applicable
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002418 # Pretty boring! The TZ test is more interesting here. astimezone()
2419 # simply can't be applied to a naive object.
2420 dt = self.theclass.now()
2421 f = FixedOffset(44, "")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04002422 self.assertRaises(ValueError, dt.astimezone) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002423 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
2424 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
2425 self.assertRaises(ValueError, dt.astimezone, f) # naive
2426 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
2427
2428 class Bogus(tzinfo):
2429 def utcoffset(self, dt): return None
2430 def dst(self, dt): return timedelta(0)
2431 bog = Bogus()
2432 self.assertRaises(ValueError, dt.astimezone, bog) # naive
2433 self.assertRaises(ValueError,
2434 dt.replace(tzinfo=bog).astimezone, f)
2435
2436 class AlsoBogus(tzinfo):
2437 def utcoffset(self, dt): return timedelta(0)
2438 def dst(self, dt): return None
2439 alsobog = AlsoBogus()
2440 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
2441
2442 def test_subclass_datetime(self):
2443
2444 class C(self.theclass):
2445 theAnswer = 42
2446
2447 def __new__(cls, *args, **kws):
2448 temp = kws.copy()
2449 extra = temp.pop('extra')
2450 result = self.theclass.__new__(cls, *args, **temp)
2451 result.extra = extra
2452 return result
2453
2454 def newmeth(self, start):
2455 return start + self.year + self.month + self.second
2456
2457 args = 2003, 4, 14, 12, 13, 41
2458
2459 dt1 = self.theclass(*args)
2460 dt2 = C(*args, **{'extra': 7})
2461
2462 self.assertEqual(dt2.__class__, C)
2463 self.assertEqual(dt2.theAnswer, 42)
2464 self.assertEqual(dt2.extra, 7)
2465 self.assertEqual(dt1.toordinal(), dt2.toordinal())
2466 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
2467 dt1.second - 7)
2468
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002469 def test_subclass_alternate_constructors_datetime(self):
2470 # Test that alternate constructors call the constructor
2471 class DateTimeSubclass(self.theclass):
2472 def __new__(cls, *args, **kwargs):
2473 result = self.theclass.__new__(cls, *args, **kwargs)
2474 result.extra = 7
2475
2476 return result
2477
2478 args = (2003, 4, 14, 12, 30, 15, 123456)
2479 d_isoformat = '2003-04-14T12:30:15.123456' # Equivalent isoformat()
2480 utc_ts = 1050323415.123456 # UTC timestamp
2481
2482 base_d = DateTimeSubclass(*args)
2483 self.assertIsInstance(base_d, DateTimeSubclass)
2484 self.assertEqual(base_d.extra, 7)
2485
2486 # Timestamp depends on time zone, so we'll calculate the equivalent here
2487 ts = base_d.timestamp()
2488
2489 test_cases = [
2490 ('fromtimestamp', (ts,)),
2491 # See https://bugs.python.org/issue32417
2492 # ('fromtimestamp', (ts, timezone.utc)),
2493 ('utcfromtimestamp', (utc_ts,)),
2494 ('fromisoformat', (d_isoformat,)),
2495 ('strptime', (d_isoformat, '%Y-%m-%dT%H:%M:%S.%f')),
2496 ('combine', (date(*args[0:3]), time(*args[3:]))),
2497 ]
2498
2499 for constr_name, constr_args in test_cases:
2500 for base_obj in (DateTimeSubclass, base_d):
2501 # Test both the classmethod and method
2502 with self.subTest(base_obj_type=type(base_obj),
2503 constr_name=constr_name):
2504 constr = getattr(base_obj, constr_name)
2505
2506 dt = constr(*constr_args)
2507
2508 # Test that it creates the right subclass
2509 self.assertIsInstance(dt, DateTimeSubclass)
2510
2511 # Test that it's equal to the base object
2512 self.assertEqual(dt, base_d.replace(tzinfo=None))
2513
2514 # Test that it called the constructor
2515 self.assertEqual(dt.extra, 7)
2516
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002517 def test_fromisoformat_datetime(self):
2518 # Test that isoformat() is reversible
2519 base_dates = [
2520 (1, 1, 1),
2521 (1900, 1, 1),
2522 (2004, 11, 12),
2523 (2017, 5, 30)
2524 ]
2525
2526 base_times = [
2527 (0, 0, 0, 0),
2528 (0, 0, 0, 241000),
2529 (0, 0, 0, 234567),
2530 (12, 30, 45, 234567)
2531 ]
2532
2533 separators = [' ', 'T']
2534
2535 tzinfos = [None, timezone.utc,
2536 timezone(timedelta(hours=-5)),
2537 timezone(timedelta(hours=2))]
2538
2539 dts = [self.theclass(*date_tuple, *time_tuple, tzinfo=tzi)
2540 for date_tuple in base_dates
2541 for time_tuple in base_times
2542 for tzi in tzinfos]
2543
2544 for dt in dts:
2545 for sep in separators:
2546 dtstr = dt.isoformat(sep=sep)
2547
2548 with self.subTest(dtstr=dtstr):
2549 dt_rt = self.theclass.fromisoformat(dtstr)
2550 self.assertEqual(dt, dt_rt)
2551
2552 def test_fromisoformat_timezone(self):
2553 base_dt = self.theclass(2014, 12, 30, 12, 30, 45, 217456)
2554
2555 tzoffsets = [
2556 timedelta(hours=5), timedelta(hours=2),
2557 timedelta(hours=6, minutes=27),
2558 timedelta(hours=12, minutes=32, seconds=30),
2559 timedelta(hours=2, minutes=4, seconds=9, microseconds=123456)
2560 ]
2561
2562 tzoffsets += [-1 * td for td in tzoffsets]
2563
2564 tzinfos = [None, timezone.utc,
2565 timezone(timedelta(hours=0))]
2566
2567 tzinfos += [timezone(td) for td in tzoffsets]
2568
2569 for tzi in tzinfos:
2570 dt = base_dt.replace(tzinfo=tzi)
2571 dtstr = dt.isoformat()
2572
2573 with self.subTest(tstr=dtstr):
2574 dt_rt = self.theclass.fromisoformat(dtstr)
2575 assert dt == dt_rt, dt_rt
2576
2577 def test_fromisoformat_separators(self):
2578 separators = [
2579 ' ', 'T', '\u007f', # 1-bit widths
2580 '\u0080', 'ʁ', # 2-bit widths
2581 'ᛇ', '時', # 3-bit widths
2582 '🐍' # 4-bit widths
2583 ]
2584
2585 for sep in separators:
2586 dt = self.theclass(2018, 1, 31, 23, 59, 47, 124789)
2587 dtstr = dt.isoformat(sep=sep)
2588
2589 with self.subTest(dtstr=dtstr):
2590 dt_rt = self.theclass.fromisoformat(dtstr)
2591 self.assertEqual(dt, dt_rt)
2592
2593 def test_fromisoformat_ambiguous(self):
2594 # Test strings like 2018-01-31+12:15 (where +12:15 is not a time zone)
2595 separators = ['+', '-']
2596 for sep in separators:
2597 dt = self.theclass(2018, 1, 31, 12, 15)
2598 dtstr = dt.isoformat(sep=sep)
2599
2600 with self.subTest(dtstr=dtstr):
2601 dt_rt = self.theclass.fromisoformat(dtstr)
2602 self.assertEqual(dt, dt_rt)
2603
2604 def test_fromisoformat_timespecs(self):
2605 datetime_bases = [
2606 (2009, 12, 4, 8, 17, 45, 123456),
2607 (2009, 12, 4, 8, 17, 45, 0)]
2608
2609 tzinfos = [None, timezone.utc,
2610 timezone(timedelta(hours=-5)),
2611 timezone(timedelta(hours=2)),
2612 timezone(timedelta(hours=6, minutes=27))]
2613
2614 timespecs = ['hours', 'minutes', 'seconds',
2615 'milliseconds', 'microseconds']
2616
2617 for ip, ts in enumerate(timespecs):
2618 for tzi in tzinfos:
2619 for dt_tuple in datetime_bases:
2620 if ts == 'milliseconds':
2621 new_microseconds = 1000 * (dt_tuple[6] // 1000)
2622 dt_tuple = dt_tuple[0:6] + (new_microseconds,)
2623
2624 dt = self.theclass(*(dt_tuple[0:(4 + ip)]), tzinfo=tzi)
2625 dtstr = dt.isoformat(timespec=ts)
2626 with self.subTest(dtstr=dtstr):
2627 dt_rt = self.theclass.fromisoformat(dtstr)
2628 self.assertEqual(dt, dt_rt)
2629
2630 def test_fromisoformat_fails_datetime(self):
2631 # Test that fromisoformat() fails on invalid values
2632 bad_strs = [
2633 '', # Empty string
2634 '2009.04-19T03', # Wrong first separator
2635 '2009-04.19T03', # Wrong second separator
2636 '2009-04-19T0a', # Invalid hours
2637 '2009-04-19T03:1a:45', # Invalid minutes
2638 '2009-04-19T03:15:4a', # Invalid seconds
2639 '2009-04-19T03;15:45', # Bad first time separator
2640 '2009-04-19T03:15;45', # Bad second time separator
2641 '2009-04-19T03:15:4500:00', # Bad time zone separator
2642 '2009-04-19T03:15:45.2345', # Too many digits for milliseconds
2643 '2009-04-19T03:15:45.1234567', # Too many digits for microseconds
2644 '2009-04-19T03:15:45.123456+24:30', # Invalid time zone offset
2645 '2009-04-19T03:15:45.123456-24:30', # Invalid negative offset
2646 '2009-04-10ᛇᛇᛇᛇᛇ12:15', # Too many unicode separators
2647 '2009-04-19T1', # Incomplete hours
2648 '2009-04-19T12:3', # Incomplete minutes
2649 '2009-04-19T12:30:4', # Incomplete seconds
2650 '2009-04-19T12:', # Ends with time separator
2651 '2009-04-19T12:30:', # Ends with time separator
2652 '2009-04-19T12:30:45.', # Ends with time separator
2653 '2009-04-19T12:30:45.123456+', # Ends with timzone separator
2654 '2009-04-19T12:30:45.123456-', # Ends with timzone separator
2655 '2009-04-19T12:30:45.123456-05:00a', # Extra text
2656 '2009-04-19T12:30:45.123-05:00a', # Extra text
2657 '2009-04-19T12:30:45-05:00a', # Extra text
2658 ]
2659
2660 for bad_str in bad_strs:
2661 with self.subTest(bad_str=bad_str):
2662 with self.assertRaises(ValueError):
2663 self.theclass.fromisoformat(bad_str)
2664
2665 def test_fromisoformat_utc(self):
2666 dt_str = '2014-04-19T13:21:13+00:00'
2667 dt = self.theclass.fromisoformat(dt_str)
2668
2669 self.assertIs(dt.tzinfo, timezone.utc)
2670
2671 def test_fromisoformat_subclass(self):
2672 class DateTimeSubclass(self.theclass):
2673 pass
2674
2675 dt = DateTimeSubclass(2014, 12, 14, 9, 30, 45, 457390,
2676 tzinfo=timezone(timedelta(hours=10, minutes=45)))
2677
2678 dt_rt = DateTimeSubclass.fromisoformat(dt.isoformat())
2679
2680 self.assertEqual(dt, dt_rt)
2681 self.assertIsInstance(dt_rt, DateTimeSubclass)
2682
2683
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002684class TestSubclassDateTime(TestDateTime):
2685 theclass = SubclassDatetime
2686 # Override tests not designed for subclass
Zachary Ware9fe6d862013-12-08 00:20:35 -06002687 @unittest.skip('not appropriate for subclasses')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002688 def test_roundtrip(self):
2689 pass
2690
2691class SubclassTime(time):
2692 sub_var = 1
2693
2694class TestTime(HarmlessMixedComparison, unittest.TestCase):
2695
2696 theclass = time
2697
2698 def test_basic_attributes(self):
2699 t = self.theclass(12, 0)
2700 self.assertEqual(t.hour, 12)
2701 self.assertEqual(t.minute, 0)
2702 self.assertEqual(t.second, 0)
2703 self.assertEqual(t.microsecond, 0)
2704
2705 def test_basic_attributes_nonzero(self):
2706 # Make sure all attributes are non-zero so bugs in
2707 # bit-shifting access show up.
2708 t = self.theclass(12, 59, 59, 8000)
2709 self.assertEqual(t.hour, 12)
2710 self.assertEqual(t.minute, 59)
2711 self.assertEqual(t.second, 59)
2712 self.assertEqual(t.microsecond, 8000)
2713
2714 def test_roundtrip(self):
2715 t = self.theclass(1, 2, 3, 4)
2716
2717 # Verify t -> string -> time identity.
2718 s = repr(t)
2719 self.assertTrue(s.startswith('datetime.'))
2720 s = s[9:]
2721 t2 = eval(s)
2722 self.assertEqual(t, t2)
2723
2724 # Verify identity via reconstructing from pieces.
2725 t2 = self.theclass(t.hour, t.minute, t.second,
2726 t.microsecond)
2727 self.assertEqual(t, t2)
2728
2729 def test_comparing(self):
2730 args = [1, 2, 3, 4]
2731 t1 = self.theclass(*args)
2732 t2 = self.theclass(*args)
2733 self.assertEqual(t1, t2)
2734 self.assertTrue(t1 <= t2)
2735 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002736 self.assertFalse(t1 != t2)
2737 self.assertFalse(t1 < t2)
2738 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002739
2740 for i in range(len(args)):
2741 newargs = args[:]
2742 newargs[i] = args[i] + 1
2743 t2 = self.theclass(*newargs) # this is larger than t1
2744 self.assertTrue(t1 < t2)
2745 self.assertTrue(t2 > t1)
2746 self.assertTrue(t1 <= t2)
2747 self.assertTrue(t2 >= t1)
2748 self.assertTrue(t1 != t2)
2749 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002750 self.assertFalse(t1 == t2)
2751 self.assertFalse(t2 == t1)
2752 self.assertFalse(t1 > t2)
2753 self.assertFalse(t2 < t1)
2754 self.assertFalse(t1 >= t2)
2755 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002756
2757 for badarg in OTHERSTUFF:
2758 self.assertEqual(t1 == badarg, False)
2759 self.assertEqual(t1 != badarg, True)
2760 self.assertEqual(badarg == t1, False)
2761 self.assertEqual(badarg != t1, True)
2762
2763 self.assertRaises(TypeError, lambda: t1 <= badarg)
2764 self.assertRaises(TypeError, lambda: t1 < badarg)
2765 self.assertRaises(TypeError, lambda: t1 > badarg)
2766 self.assertRaises(TypeError, lambda: t1 >= badarg)
2767 self.assertRaises(TypeError, lambda: badarg <= t1)
2768 self.assertRaises(TypeError, lambda: badarg < t1)
2769 self.assertRaises(TypeError, lambda: badarg > t1)
2770 self.assertRaises(TypeError, lambda: badarg >= t1)
2771
2772 def test_bad_constructor_arguments(self):
2773 # bad hours
2774 self.theclass(0, 0) # no exception
2775 self.theclass(23, 0) # no exception
2776 self.assertRaises(ValueError, self.theclass, -1, 0)
2777 self.assertRaises(ValueError, self.theclass, 24, 0)
2778 # bad minutes
2779 self.theclass(23, 0) # no exception
2780 self.theclass(23, 59) # no exception
2781 self.assertRaises(ValueError, self.theclass, 23, -1)
2782 self.assertRaises(ValueError, self.theclass, 23, 60)
2783 # bad seconds
2784 self.theclass(23, 59, 0) # no exception
2785 self.theclass(23, 59, 59) # no exception
2786 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
2787 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
2788 # bad microseconds
2789 self.theclass(23, 59, 59, 0) # no exception
2790 self.theclass(23, 59, 59, 999999) # no exception
2791 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
2792 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
2793
2794 def test_hash_equality(self):
2795 d = self.theclass(23, 30, 17)
2796 e = self.theclass(23, 30, 17)
2797 self.assertEqual(d, e)
2798 self.assertEqual(hash(d), hash(e))
2799
2800 dic = {d: 1}
2801 dic[e] = 2
2802 self.assertEqual(len(dic), 1)
2803 self.assertEqual(dic[d], 2)
2804 self.assertEqual(dic[e], 2)
2805
2806 d = self.theclass(0, 5, 17)
2807 e = self.theclass(0, 5, 17)
2808 self.assertEqual(d, e)
2809 self.assertEqual(hash(d), hash(e))
2810
2811 dic = {d: 1}
2812 dic[e] = 2
2813 self.assertEqual(len(dic), 1)
2814 self.assertEqual(dic[d], 2)
2815 self.assertEqual(dic[e], 2)
2816
2817 def test_isoformat(self):
2818 t = self.theclass(4, 5, 1, 123)
2819 self.assertEqual(t.isoformat(), "04:05:01.000123")
2820 self.assertEqual(t.isoformat(), str(t))
2821
2822 t = self.theclass()
2823 self.assertEqual(t.isoformat(), "00:00:00")
2824 self.assertEqual(t.isoformat(), str(t))
2825
2826 t = self.theclass(microsecond=1)
2827 self.assertEqual(t.isoformat(), "00:00:00.000001")
2828 self.assertEqual(t.isoformat(), str(t))
2829
2830 t = self.theclass(microsecond=10)
2831 self.assertEqual(t.isoformat(), "00:00:00.000010")
2832 self.assertEqual(t.isoformat(), str(t))
2833
2834 t = self.theclass(microsecond=100)
2835 self.assertEqual(t.isoformat(), "00:00:00.000100")
2836 self.assertEqual(t.isoformat(), str(t))
2837
2838 t = self.theclass(microsecond=1000)
2839 self.assertEqual(t.isoformat(), "00:00:00.001000")
2840 self.assertEqual(t.isoformat(), str(t))
2841
2842 t = self.theclass(microsecond=10000)
2843 self.assertEqual(t.isoformat(), "00:00:00.010000")
2844 self.assertEqual(t.isoformat(), str(t))
2845
2846 t = self.theclass(microsecond=100000)
2847 self.assertEqual(t.isoformat(), "00:00:00.100000")
2848 self.assertEqual(t.isoformat(), str(t))
2849
Alexander Belopolskya2998a62016-03-06 14:58:43 -05002850 t = self.theclass(hour=12, minute=34, second=56, microsecond=123456)
2851 self.assertEqual(t.isoformat(timespec='hours'), "12")
2852 self.assertEqual(t.isoformat(timespec='minutes'), "12:34")
2853 self.assertEqual(t.isoformat(timespec='seconds'), "12:34:56")
2854 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.123")
2855 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.123456")
2856 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56.123456")
2857 self.assertRaises(ValueError, t.isoformat, timespec='monkey')
2858
2859 t = self.theclass(hour=12, minute=34, second=56, microsecond=999500)
2860 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.999")
2861
2862 t = self.theclass(hour=12, minute=34, second=56, microsecond=0)
2863 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.000")
2864 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.000000")
2865 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56")
2866
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002867 def test_isoformat_timezone(self):
2868 tzoffsets = [
2869 ('05:00', timedelta(hours=5)),
2870 ('02:00', timedelta(hours=2)),
2871 ('06:27', timedelta(hours=6, minutes=27)),
2872 ('12:32:30', timedelta(hours=12, minutes=32, seconds=30)),
2873 ('02:04:09.123456', timedelta(hours=2, minutes=4, seconds=9, microseconds=123456))
2874 ]
2875
2876 tzinfos = [
2877 ('', None),
2878 ('+00:00', timezone.utc),
2879 ('+00:00', timezone(timedelta(0))),
2880 ]
2881
2882 tzinfos += [
2883 (prefix + expected, timezone(sign * td))
2884 for expected, td in tzoffsets
2885 for prefix, sign in [('-', -1), ('+', 1)]
2886 ]
2887
2888 t_base = self.theclass(12, 37, 9)
2889 exp_base = '12:37:09'
2890
2891 for exp_tz, tzi in tzinfos:
2892 t = t_base.replace(tzinfo=tzi)
2893 exp = exp_base + exp_tz
2894 with self.subTest(tzi=tzi):
2895 assert t.isoformat() == exp
2896
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002897 def test_1653736(self):
2898 # verify it doesn't accept extra keyword arguments
2899 t = self.theclass(second=1)
2900 self.assertRaises(TypeError, t.isoformat, foo=3)
2901
2902 def test_strftime(self):
2903 t = self.theclass(1, 2, 3, 4)
2904 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
2905 # A naive object replaces %z and %Z with empty strings.
2906 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
2907
2908 def test_format(self):
2909 t = self.theclass(1, 2, 3, 4)
2910 self.assertEqual(t.__format__(''), str(t))
2911
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02002912 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002913 t.__format__(123)
2914
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002915 # check that a derived class's __str__() gets called
2916 class A(self.theclass):
2917 def __str__(self):
2918 return 'A'
2919 a = A(1, 2, 3, 4)
2920 self.assertEqual(a.__format__(''), 'A')
2921
2922 # check that a derived class's strftime gets called
2923 class B(self.theclass):
2924 def strftime(self, format_spec):
2925 return 'B'
2926 b = B(1, 2, 3, 4)
2927 self.assertEqual(b.__format__(''), str(t))
2928
2929 for fmt in ['%H %M %S',
2930 ]:
2931 self.assertEqual(t.__format__(fmt), t.strftime(fmt))
2932 self.assertEqual(a.__format__(fmt), t.strftime(fmt))
2933 self.assertEqual(b.__format__(fmt), 'B')
2934
2935 def test_str(self):
2936 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
2937 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
2938 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
2939 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
2940 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
2941
2942 def test_repr(self):
2943 name = 'datetime.' + self.theclass.__name__
2944 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
2945 "%s(1, 2, 3, 4)" % name)
2946 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
2947 "%s(10, 2, 3, 4000)" % name)
2948 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
2949 "%s(0, 2, 3, 400000)" % name)
2950 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
2951 "%s(12, 2, 3)" % name)
2952 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
2953 "%s(23, 15)" % name)
2954
2955 def test_resolution_info(self):
2956 self.assertIsInstance(self.theclass.min, self.theclass)
2957 self.assertIsInstance(self.theclass.max, self.theclass)
2958 self.assertIsInstance(self.theclass.resolution, timedelta)
2959 self.assertTrue(self.theclass.max > self.theclass.min)
2960
2961 def test_pickling(self):
2962 args = 20, 59, 16, 64**2
2963 orig = self.theclass(*args)
2964 for pickler, unpickler, proto in pickle_choices:
2965 green = pickler.dumps(orig, proto)
2966 derived = unpickler.loads(green)
2967 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02002968 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002969
2970 def test_pickling_subclass_time(self):
2971 args = 20, 59, 16, 64**2
2972 orig = SubclassTime(*args)
2973 for pickler, unpickler, proto in pickle_choices:
2974 green = pickler.dumps(orig, proto)
2975 derived = unpickler.loads(green)
2976 self.assertEqual(orig, derived)
2977
2978 def test_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002979 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002980 cls = self.theclass
2981 self.assertTrue(cls(1))
2982 self.assertTrue(cls(0, 1))
2983 self.assertTrue(cls(0, 0, 1))
2984 self.assertTrue(cls(0, 0, 0, 1))
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002985 self.assertTrue(cls(0))
2986 self.assertTrue(cls())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002987
2988 def test_replace(self):
2989 cls = self.theclass
2990 args = [1, 2, 3, 4]
2991 base = cls(*args)
2992 self.assertEqual(base, base.replace())
2993
2994 i = 0
2995 for name, newval in (("hour", 5),
2996 ("minute", 6),
2997 ("second", 7),
2998 ("microsecond", 8)):
2999 newargs = args[:]
3000 newargs[i] = newval
3001 expected = cls(*newargs)
3002 got = base.replace(**{name: newval})
3003 self.assertEqual(expected, got)
3004 i += 1
3005
3006 # Out of bounds.
3007 base = cls(1)
3008 self.assertRaises(ValueError, base.replace, hour=24)
3009 self.assertRaises(ValueError, base.replace, minute=-1)
3010 self.assertRaises(ValueError, base.replace, second=100)
3011 self.assertRaises(ValueError, base.replace, microsecond=1000000)
3012
Paul Ganssle191e9932017-11-09 16:34:29 -05003013 def test_subclass_replace(self):
3014 class TimeSubclass(self.theclass):
3015 pass
3016
3017 ctime = TimeSubclass(12, 30)
3018 self.assertIs(type(ctime.replace(hour=10)), TimeSubclass)
3019
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003020 def test_subclass_time(self):
3021
3022 class C(self.theclass):
3023 theAnswer = 42
3024
3025 def __new__(cls, *args, **kws):
3026 temp = kws.copy()
3027 extra = temp.pop('extra')
3028 result = self.theclass.__new__(cls, *args, **temp)
3029 result.extra = extra
3030 return result
3031
3032 def newmeth(self, start):
3033 return start + self.hour + self.second
3034
3035 args = 4, 5, 6
3036
3037 dt1 = self.theclass(*args)
3038 dt2 = C(*args, **{'extra': 7})
3039
3040 self.assertEqual(dt2.__class__, C)
3041 self.assertEqual(dt2.theAnswer, 42)
3042 self.assertEqual(dt2.extra, 7)
3043 self.assertEqual(dt1.isoformat(), dt2.isoformat())
3044 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
3045
3046 def test_backdoor_resistance(self):
3047 # see TestDate.test_backdoor_resistance().
3048 base = '2:59.0'
3049 for hour_byte in ' ', '9', chr(24), '\xff':
3050 self.assertRaises(TypeError, self.theclass,
3051 hour_byte + base[1:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04003052 # Good bytes, but bad tzinfo:
3053 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
3054 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003055
3056# A mixin for classes with a tzinfo= argument. Subclasses must define
Martin Panter46f50722016-05-26 05:35:26 +00003057# theclass as a class attribute, and theclass(1, 1, 1, tzinfo=whatever)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003058# must be legit (which is true for time and datetime).
3059class TZInfoBase:
3060
3061 def test_argument_passing(self):
3062 cls = self.theclass
3063 # A datetime passes itself on, a time passes None.
3064 class introspective(tzinfo):
3065 def tzname(self, dt): return dt and "real" or "none"
3066 def utcoffset(self, dt):
3067 return timedelta(minutes = dt and 42 or -42)
3068 dst = utcoffset
3069
3070 obj = cls(1, 2, 3, tzinfo=introspective())
3071
3072 expected = cls is time and "none" or "real"
3073 self.assertEqual(obj.tzname(), expected)
3074
3075 expected = timedelta(minutes=(cls is time and -42 or 42))
3076 self.assertEqual(obj.utcoffset(), expected)
3077 self.assertEqual(obj.dst(), expected)
3078
3079 def test_bad_tzinfo_classes(self):
3080 cls = self.theclass
3081 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
3082
3083 class NiceTry(object):
3084 def __init__(self): pass
3085 def utcoffset(self, dt): pass
3086 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
3087
3088 class BetterTry(tzinfo):
3089 def __init__(self): pass
3090 def utcoffset(self, dt): pass
3091 b = BetterTry()
3092 t = cls(1, 1, 1, tzinfo=b)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003093 self.assertIs(t.tzinfo, b)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003094
3095 def test_utc_offset_out_of_bounds(self):
3096 class Edgy(tzinfo):
3097 def __init__(self, offset):
3098 self.offset = timedelta(minutes=offset)
3099 def utcoffset(self, dt):
3100 return self.offset
3101
3102 cls = self.theclass
3103 for offset, legit in ((-1440, False),
3104 (-1439, True),
3105 (1439, True),
3106 (1440, False)):
3107 if cls is time:
3108 t = cls(1, 2, 3, tzinfo=Edgy(offset))
3109 elif cls is datetime:
3110 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
3111 else:
3112 assert 0, "impossible"
3113 if legit:
3114 aofs = abs(offset)
3115 h, m = divmod(aofs, 60)
3116 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
3117 if isinstance(t, datetime):
3118 t = t.timetz()
3119 self.assertEqual(str(t), "01:02:03" + tag)
3120 else:
3121 self.assertRaises(ValueError, str, t)
3122
3123 def test_tzinfo_classes(self):
3124 cls = self.theclass
3125 class C1(tzinfo):
3126 def utcoffset(self, dt): return None
3127 def dst(self, dt): return None
3128 def tzname(self, dt): return None
3129 for t in (cls(1, 1, 1),
3130 cls(1, 1, 1, tzinfo=None),
3131 cls(1, 1, 1, tzinfo=C1())):
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003132 self.assertIsNone(t.utcoffset())
3133 self.assertIsNone(t.dst())
3134 self.assertIsNone(t.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003135
3136 class C3(tzinfo):
3137 def utcoffset(self, dt): return timedelta(minutes=-1439)
3138 def dst(self, dt): return timedelta(minutes=1439)
3139 def tzname(self, dt): return "aname"
3140 t = cls(1, 1, 1, tzinfo=C3())
3141 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
3142 self.assertEqual(t.dst(), timedelta(minutes=1439))
3143 self.assertEqual(t.tzname(), "aname")
3144
3145 # Wrong types.
3146 class C4(tzinfo):
3147 def utcoffset(self, dt): return "aname"
3148 def dst(self, dt): return 7
3149 def tzname(self, dt): return 0
3150 t = cls(1, 1, 1, tzinfo=C4())
3151 self.assertRaises(TypeError, t.utcoffset)
3152 self.assertRaises(TypeError, t.dst)
3153 self.assertRaises(TypeError, t.tzname)
3154
3155 # Offset out of range.
3156 class C6(tzinfo):
3157 def utcoffset(self, dt): return timedelta(hours=-24)
3158 def dst(self, dt): return timedelta(hours=24)
3159 t = cls(1, 1, 1, tzinfo=C6())
3160 self.assertRaises(ValueError, t.utcoffset)
3161 self.assertRaises(ValueError, t.dst)
3162
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04003163 # Not a whole number of seconds.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003164 class C7(tzinfo):
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04003165 def utcoffset(self, dt): return timedelta(microseconds=61)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003166 def dst(self, dt): return timedelta(microseconds=-81)
3167 t = cls(1, 1, 1, tzinfo=C7())
Alexander Belopolsky018d3532017-07-31 10:26:50 -04003168 self.assertEqual(t.utcoffset(), timedelta(microseconds=61))
3169 self.assertEqual(t.dst(), timedelta(microseconds=-81))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003170
3171 def test_aware_compare(self):
3172 cls = self.theclass
3173
3174 # Ensure that utcoffset() gets ignored if the comparands have
3175 # the same tzinfo member.
3176 class OperandDependentOffset(tzinfo):
3177 def utcoffset(self, t):
3178 if t.minute < 10:
3179 # d0 and d1 equal after adjustment
3180 return timedelta(minutes=t.minute)
3181 else:
3182 # d2 off in the weeds
3183 return timedelta(minutes=59)
3184
3185 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
3186 d0 = base.replace(minute=3)
3187 d1 = base.replace(minute=9)
3188 d2 = base.replace(minute=11)
3189 for x in d0, d1, d2:
3190 for y in d0, d1, d2:
3191 for op in lt, le, gt, ge, eq, ne:
3192 got = op(x, y)
3193 expected = op(x.minute, y.minute)
3194 self.assertEqual(got, expected)
3195
3196 # However, if they're different members, uctoffset is not ignored.
3197 # Note that a time can't actually have an operand-depedent offset,
3198 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
3199 # so skip this test for time.
3200 if cls is not time:
3201 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
3202 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
3203 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
3204 for x in d0, d1, d2:
3205 for y in d0, d1, d2:
3206 got = (x > y) - (x < y)
3207 if (x is d0 or x is d1) and (y is d0 or y is d1):
3208 expected = 0
3209 elif x is y is d2:
3210 expected = 0
3211 elif x is d2:
3212 expected = -1
3213 else:
3214 assert y is d2
3215 expected = 1
3216 self.assertEqual(got, expected)
3217
3218
3219# Testing time objects with a non-None tzinfo.
3220class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
3221 theclass = time
3222
3223 def test_empty(self):
3224 t = self.theclass()
3225 self.assertEqual(t.hour, 0)
3226 self.assertEqual(t.minute, 0)
3227 self.assertEqual(t.second, 0)
3228 self.assertEqual(t.microsecond, 0)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003229 self.assertIsNone(t.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003230
3231 def test_zones(self):
3232 est = FixedOffset(-300, "EST", 1)
3233 utc = FixedOffset(0, "UTC", -2)
3234 met = FixedOffset(60, "MET", 3)
3235 t1 = time( 7, 47, tzinfo=est)
3236 t2 = time(12, 47, tzinfo=utc)
3237 t3 = time(13, 47, tzinfo=met)
3238 t4 = time(microsecond=40)
3239 t5 = time(microsecond=40, tzinfo=utc)
3240
3241 self.assertEqual(t1.tzinfo, est)
3242 self.assertEqual(t2.tzinfo, utc)
3243 self.assertEqual(t3.tzinfo, met)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003244 self.assertIsNone(t4.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003245 self.assertEqual(t5.tzinfo, utc)
3246
3247 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
3248 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
3249 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003250 self.assertIsNone(t4.utcoffset())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003251 self.assertRaises(TypeError, t1.utcoffset, "no args")
3252
3253 self.assertEqual(t1.tzname(), "EST")
3254 self.assertEqual(t2.tzname(), "UTC")
3255 self.assertEqual(t3.tzname(), "MET")
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003256 self.assertIsNone(t4.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003257 self.assertRaises(TypeError, t1.tzname, "no args")
3258
3259 self.assertEqual(t1.dst(), timedelta(minutes=1))
3260 self.assertEqual(t2.dst(), timedelta(minutes=-2))
3261 self.assertEqual(t3.dst(), timedelta(minutes=3))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003262 self.assertIsNone(t4.dst())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003263 self.assertRaises(TypeError, t1.dst, "no args")
3264
3265 self.assertEqual(hash(t1), hash(t2))
3266 self.assertEqual(hash(t1), hash(t3))
3267 self.assertEqual(hash(t2), hash(t3))
3268
3269 self.assertEqual(t1, t2)
3270 self.assertEqual(t1, t3)
3271 self.assertEqual(t2, t3)
Alexander Belopolsky08313822012-06-15 20:19:47 -04003272 self.assertNotEqual(t4, t5) # mixed tz-aware & naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003273 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
3274 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
3275
3276 self.assertEqual(str(t1), "07:47:00-05:00")
3277 self.assertEqual(str(t2), "12:47:00+00:00")
3278 self.assertEqual(str(t3), "13:47:00+01:00")
3279 self.assertEqual(str(t4), "00:00:00.000040")
3280 self.assertEqual(str(t5), "00:00:00.000040+00:00")
3281
3282 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
3283 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
3284 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
3285 self.assertEqual(t4.isoformat(), "00:00:00.000040")
3286 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
3287
3288 d = 'datetime.time'
3289 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
3290 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
3291 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
3292 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
3293 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
3294
3295 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
3296 "07:47:00 %Z=EST %z=-0500")
3297 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
3298 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
3299
3300 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
3301 t1 = time(23, 59, tzinfo=yuck)
3302 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
3303 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
3304
3305 # Check that an invalid tzname result raises an exception.
3306 class Badtzname(tzinfo):
Alexander Belopolskye239d232010-12-08 23:31:48 +00003307 tz = 42
3308 def tzname(self, dt): return self.tz
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003309 t = time(2, 3, 4, tzinfo=Badtzname())
3310 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
3311 self.assertRaises(TypeError, t.strftime, "%Z")
3312
Alexander Belopolskye239d232010-12-08 23:31:48 +00003313 # Issue #6697:
Utkarsh Upadhyay287c5592017-07-21 02:14:54 +02003314 if '_Fast' in self.__class__.__name__:
Alexander Belopolskye239d232010-12-08 23:31:48 +00003315 Badtzname.tz = '\ud800'
3316 self.assertRaises(ValueError, t.strftime, "%Z")
3317
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003318 def test_hash_edge_cases(self):
3319 # Offsets that overflow a basic time.
3320 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
3321 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
3322 self.assertEqual(hash(t1), hash(t2))
3323
3324 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
3325 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
3326 self.assertEqual(hash(t1), hash(t2))
3327
3328 def test_pickling(self):
3329 # Try one without a tzinfo.
3330 args = 20, 59, 16, 64**2
3331 orig = self.theclass(*args)
3332 for pickler, unpickler, proto in pickle_choices:
3333 green = pickler.dumps(orig, proto)
3334 derived = unpickler.loads(green)
3335 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003336 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003337
3338 # Try one with a tzinfo.
3339 tinfo = PicklableFixedOffset(-300, 'cookie')
3340 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
3341 for pickler, unpickler, proto in pickle_choices:
3342 green = pickler.dumps(orig, proto)
3343 derived = unpickler.loads(green)
3344 self.assertEqual(orig, derived)
3345 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3346 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3347 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003348 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003349
3350 def test_more_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003351 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003352 cls = self.theclass
3353
3354 t = cls(0, tzinfo=FixedOffset(-300, ""))
3355 self.assertTrue(t)
3356
3357 t = cls(5, tzinfo=FixedOffset(-300, ""))
3358 self.assertTrue(t)
3359
3360 t = cls(5, tzinfo=FixedOffset(300, ""))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003361 self.assertTrue(t)
3362
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003363 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
3364 self.assertTrue(t)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003365
3366 def test_replace(self):
3367 cls = self.theclass
3368 z100 = FixedOffset(100, "+100")
3369 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3370 args = [1, 2, 3, 4, z100]
3371 base = cls(*args)
3372 self.assertEqual(base, base.replace())
3373
3374 i = 0
3375 for name, newval in (("hour", 5),
3376 ("minute", 6),
3377 ("second", 7),
3378 ("microsecond", 8),
3379 ("tzinfo", zm200)):
3380 newargs = args[:]
3381 newargs[i] = newval
3382 expected = cls(*newargs)
3383 got = base.replace(**{name: newval})
3384 self.assertEqual(expected, got)
3385 i += 1
3386
3387 # Ensure we can get rid of a tzinfo.
3388 self.assertEqual(base.tzname(), "+100")
3389 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003390 self.assertIsNone(base2.tzinfo)
3391 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003392
3393 # Ensure we can add one.
3394 base3 = base2.replace(tzinfo=z100)
3395 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003396 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003397
3398 # Out of bounds.
3399 base = cls(1)
3400 self.assertRaises(ValueError, base.replace, hour=24)
3401 self.assertRaises(ValueError, base.replace, minute=-1)
3402 self.assertRaises(ValueError, base.replace, second=100)
3403 self.assertRaises(ValueError, base.replace, microsecond=1000000)
3404
3405 def test_mixed_compare(self):
3406 t1 = time(1, 2, 3)
3407 t2 = time(1, 2, 3)
3408 self.assertEqual(t1, t2)
3409 t2 = t2.replace(tzinfo=None)
3410 self.assertEqual(t1, t2)
3411 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3412 self.assertEqual(t1, t2)
3413 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04003414 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003415
3416 # In time w/ identical tzinfo objects, utcoffset is ignored.
3417 class Varies(tzinfo):
3418 def __init__(self):
3419 self.offset = timedelta(minutes=22)
3420 def utcoffset(self, t):
3421 self.offset += timedelta(minutes=1)
3422 return self.offset
3423
3424 v = Varies()
3425 t1 = t2.replace(tzinfo=v)
3426 t2 = t2.replace(tzinfo=v)
3427 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3428 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3429 self.assertEqual(t1, t2)
3430
3431 # But if they're not identical, it isn't ignored.
3432 t2 = t2.replace(tzinfo=Varies())
3433 self.assertTrue(t1 < t2) # t1's offset counter still going up
3434
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003435 def test_fromisoformat(self):
3436 time_examples = [
3437 (0, 0, 0, 0),
3438 (23, 59, 59, 999999),
3439 ]
3440
3441 hh = (9, 12, 20)
3442 mm = (5, 30)
3443 ss = (4, 45)
3444 usec = (0, 245000, 678901)
3445
3446 time_examples += list(itertools.product(hh, mm, ss, usec))
3447
3448 tzinfos = [None, timezone.utc,
3449 timezone(timedelta(hours=2)),
3450 timezone(timedelta(hours=6, minutes=27))]
3451
3452 for ttup in time_examples:
3453 for tzi in tzinfos:
3454 t = self.theclass(*ttup, tzinfo=tzi)
3455 tstr = t.isoformat()
3456
3457 with self.subTest(tstr=tstr):
3458 t_rt = self.theclass.fromisoformat(tstr)
3459 self.assertEqual(t, t_rt)
3460
3461 def test_fromisoformat_timezone(self):
3462 base_time = self.theclass(12, 30, 45, 217456)
3463
3464 tzoffsets = [
3465 timedelta(hours=5), timedelta(hours=2),
3466 timedelta(hours=6, minutes=27),
3467 timedelta(hours=12, minutes=32, seconds=30),
3468 timedelta(hours=2, minutes=4, seconds=9, microseconds=123456)
3469 ]
3470
3471 tzoffsets += [-1 * td for td in tzoffsets]
3472
3473 tzinfos = [None, timezone.utc,
3474 timezone(timedelta(hours=0))]
3475
3476 tzinfos += [timezone(td) for td in tzoffsets]
3477
3478 for tzi in tzinfos:
3479 t = base_time.replace(tzinfo=tzi)
3480 tstr = t.isoformat()
3481
3482 with self.subTest(tstr=tstr):
3483 t_rt = self.theclass.fromisoformat(tstr)
3484 assert t == t_rt, t_rt
3485
3486 def test_fromisoformat_timespecs(self):
3487 time_bases = [
3488 (8, 17, 45, 123456),
3489 (8, 17, 45, 0)
3490 ]
3491
3492 tzinfos = [None, timezone.utc,
3493 timezone(timedelta(hours=-5)),
3494 timezone(timedelta(hours=2)),
3495 timezone(timedelta(hours=6, minutes=27))]
3496
3497 timespecs = ['hours', 'minutes', 'seconds',
3498 'milliseconds', 'microseconds']
3499
3500 for ip, ts in enumerate(timespecs):
3501 for tzi in tzinfos:
3502 for t_tuple in time_bases:
3503 if ts == 'milliseconds':
3504 new_microseconds = 1000 * (t_tuple[-1] // 1000)
3505 t_tuple = t_tuple[0:-1] + (new_microseconds,)
3506
3507 t = self.theclass(*(t_tuple[0:(1 + ip)]), tzinfo=tzi)
3508 tstr = t.isoformat(timespec=ts)
3509 with self.subTest(tstr=tstr):
3510 t_rt = self.theclass.fromisoformat(tstr)
3511 self.assertEqual(t, t_rt)
3512
3513 def test_fromisoformat_fails(self):
3514 bad_strs = [
3515 '', # Empty string
3516 '12:', # Ends on a separator
3517 '12:30:', # Ends on a separator
3518 '12:30:15.', # Ends on a separator
3519 '1', # Incomplete hours
3520 '12:3', # Incomplete minutes
3521 '12:30:1', # Incomplete seconds
3522 '1a:30:45.334034', # Invalid character in hours
3523 '12:a0:45.334034', # Invalid character in minutes
3524 '12:30:a5.334034', # Invalid character in seconds
3525 '12:30:45.1234', # Too many digits for milliseconds
3526 '12:30:45.1234567', # Too many digits for microseconds
3527 '12:30:45.123456+24:30', # Invalid time zone offset
3528 '12:30:45.123456-24:30', # Invalid negative offset
3529 '12:30:45', # Uses full-width unicode colons
3530 '12:30:45․123456', # Uses \u2024 in place of decimal point
3531 '12:30:45a', # Extra at tend of basic time
3532 '12:30:45.123a', # Extra at end of millisecond time
3533 '12:30:45.123456a', # Extra at end of microsecond time
3534 '12:30:45.123456+12:00:30a', # Extra at end of full time
3535 ]
3536
3537 for bad_str in bad_strs:
3538 with self.subTest(bad_str=bad_str):
3539 with self.assertRaises(ValueError):
3540 self.theclass.fromisoformat(bad_str)
3541
3542 def test_fromisoformat_fails_typeerror(self):
3543 # Test the fromisoformat fails when passed the wrong type
3544 import io
3545
3546 bad_types = [b'12:30:45', None, io.StringIO('12:30:45')]
3547
3548 for bad_type in bad_types:
3549 with self.assertRaises(TypeError):
3550 self.theclass.fromisoformat(bad_type)
3551
3552 def test_fromisoformat_subclass(self):
3553 class TimeSubclass(self.theclass):
3554 pass
3555
3556 tsc = TimeSubclass(12, 14, 45, 203745, tzinfo=timezone.utc)
3557 tsc_rt = TimeSubclass.fromisoformat(tsc.isoformat())
3558
3559 self.assertEqual(tsc, tsc_rt)
3560 self.assertIsInstance(tsc_rt, TimeSubclass)
3561
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003562 def test_subclass_timetz(self):
3563
3564 class C(self.theclass):
3565 theAnswer = 42
3566
3567 def __new__(cls, *args, **kws):
3568 temp = kws.copy()
3569 extra = temp.pop('extra')
3570 result = self.theclass.__new__(cls, *args, **temp)
3571 result.extra = extra
3572 return result
3573
3574 def newmeth(self, start):
3575 return start + self.hour + self.second
3576
3577 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3578
3579 dt1 = self.theclass(*args)
3580 dt2 = C(*args, **{'extra': 7})
3581
3582 self.assertEqual(dt2.__class__, C)
3583 self.assertEqual(dt2.theAnswer, 42)
3584 self.assertEqual(dt2.extra, 7)
3585 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3586 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
3587
3588
3589# Testing datetime objects with a non-None tzinfo.
3590
3591class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
3592 theclass = datetime
3593
3594 def test_trivial(self):
3595 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
3596 self.assertEqual(dt.year, 1)
3597 self.assertEqual(dt.month, 2)
3598 self.assertEqual(dt.day, 3)
3599 self.assertEqual(dt.hour, 4)
3600 self.assertEqual(dt.minute, 5)
3601 self.assertEqual(dt.second, 6)
3602 self.assertEqual(dt.microsecond, 7)
3603 self.assertEqual(dt.tzinfo, None)
3604
3605 def test_even_more_compare(self):
3606 # The test_compare() and test_more_compare() inherited from TestDate
3607 # and TestDateTime covered non-tzinfo cases.
3608
3609 # Smallest possible after UTC adjustment.
3610 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
3611 # Largest possible after UTC adjustment.
3612 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3613 tzinfo=FixedOffset(-1439, ""))
3614
3615 # Make sure those compare correctly, and w/o overflow.
3616 self.assertTrue(t1 < t2)
3617 self.assertTrue(t1 != t2)
3618 self.assertTrue(t2 > t1)
3619
3620 self.assertEqual(t1, t1)
3621 self.assertEqual(t2, t2)
3622
3623 # Equal afer adjustment.
3624 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
3625 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
3626 self.assertEqual(t1, t2)
3627
3628 # Change t1 not to subtract a minute, and t1 should be larger.
3629 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
3630 self.assertTrue(t1 > t2)
3631
3632 # Change t1 to subtract 2 minutes, and t1 should be smaller.
3633 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
3634 self.assertTrue(t1 < t2)
3635
3636 # Back to the original t1, but make seconds resolve it.
3637 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3638 second=1)
3639 self.assertTrue(t1 > t2)
3640
3641 # Likewise, but make microseconds resolve it.
3642 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3643 microsecond=1)
3644 self.assertTrue(t1 > t2)
3645
Alexander Belopolsky08313822012-06-15 20:19:47 -04003646 # Make t2 naive and it should differ.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003647 t2 = self.theclass.min
Alexander Belopolsky08313822012-06-15 20:19:47 -04003648 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003649 self.assertEqual(t2, t2)
3650
3651 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
3652 class Naive(tzinfo):
3653 def utcoffset(self, dt): return None
3654 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
Alexander Belopolsky08313822012-06-15 20:19:47 -04003655 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003656 self.assertEqual(t2, t2)
3657
3658 # OTOH, it's OK to compare two of these mixing the two ways of being
3659 # naive.
3660 t1 = self.theclass(5, 6, 7)
3661 self.assertEqual(t1, t2)
3662
3663 # Try a bogus uctoffset.
3664 class Bogus(tzinfo):
3665 def utcoffset(self, dt):
3666 return timedelta(minutes=1440) # out of bounds
3667 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
3668 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
3669 self.assertRaises(ValueError, lambda: t1 == t2)
3670
3671 def test_pickling(self):
3672 # Try one without a tzinfo.
3673 args = 6, 7, 23, 20, 59, 1, 64**2
3674 orig = self.theclass(*args)
3675 for pickler, unpickler, proto in pickle_choices:
3676 green = pickler.dumps(orig, proto)
3677 derived = unpickler.loads(green)
3678 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003679 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003680
3681 # Try one with a tzinfo.
3682 tinfo = PicklableFixedOffset(-300, 'cookie')
3683 orig = self.theclass(*args, **{'tzinfo': tinfo})
3684 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
3685 for pickler, unpickler, proto in pickle_choices:
3686 green = pickler.dumps(orig, proto)
3687 derived = unpickler.loads(green)
3688 self.assertEqual(orig, derived)
3689 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3690 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3691 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003692 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003693
3694 def test_extreme_hashes(self):
3695 # If an attempt is made to hash these via subtracting the offset
3696 # then hashing a datetime object, OverflowError results. The
3697 # Python implementation used to blow up here.
3698 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
3699 hash(t)
3700 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3701 tzinfo=FixedOffset(-1439, ""))
3702 hash(t)
3703
3704 # OTOH, an OOB offset should blow up.
3705 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
3706 self.assertRaises(ValueError, hash, t)
3707
3708 def test_zones(self):
3709 est = FixedOffset(-300, "EST")
3710 utc = FixedOffset(0, "UTC")
3711 met = FixedOffset(60, "MET")
3712 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
3713 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
3714 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
3715 self.assertEqual(t1.tzinfo, est)
3716 self.assertEqual(t2.tzinfo, utc)
3717 self.assertEqual(t3.tzinfo, met)
3718 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
3719 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
3720 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
3721 self.assertEqual(t1.tzname(), "EST")
3722 self.assertEqual(t2.tzname(), "UTC")
3723 self.assertEqual(t3.tzname(), "MET")
3724 self.assertEqual(hash(t1), hash(t2))
3725 self.assertEqual(hash(t1), hash(t3))
3726 self.assertEqual(hash(t2), hash(t3))
3727 self.assertEqual(t1, t2)
3728 self.assertEqual(t1, t3)
3729 self.assertEqual(t2, t3)
3730 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
3731 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
3732 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
3733 d = 'datetime.datetime(2002, 3, 19, '
3734 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
3735 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
3736 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
3737
3738 def test_combine(self):
3739 met = FixedOffset(60, "MET")
3740 d = date(2002, 3, 4)
3741 tz = time(18, 45, 3, 1234, tzinfo=met)
3742 dt = datetime.combine(d, tz)
3743 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
3744 tzinfo=met))
3745
3746 def test_extract(self):
3747 met = FixedOffset(60, "MET")
3748 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
3749 self.assertEqual(dt.date(), date(2002, 3, 4))
3750 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
3751 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
3752
3753 def test_tz_aware_arithmetic(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003754 now = self.theclass.now()
3755 tz55 = FixedOffset(-330, "west 5:30")
3756 timeaware = now.time().replace(tzinfo=tz55)
3757 nowaware = self.theclass.combine(now.date(), timeaware)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003758 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003759 self.assertEqual(nowaware.timetz(), timeaware)
3760
3761 # Can't mix aware and non-aware.
3762 self.assertRaises(TypeError, lambda: now - nowaware)
3763 self.assertRaises(TypeError, lambda: nowaware - now)
3764
3765 # And adding datetime's doesn't make sense, aware or not.
3766 self.assertRaises(TypeError, lambda: now + nowaware)
3767 self.assertRaises(TypeError, lambda: nowaware + now)
3768 self.assertRaises(TypeError, lambda: nowaware + nowaware)
3769
3770 # Subtracting should yield 0.
3771 self.assertEqual(now - now, timedelta(0))
3772 self.assertEqual(nowaware - nowaware, timedelta(0))
3773
3774 # Adding a delta should preserve tzinfo.
3775 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
3776 nowawareplus = nowaware + delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003777 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003778 nowawareplus2 = delta + nowaware
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003779 self.assertIs(nowawareplus2.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003780 self.assertEqual(nowawareplus, nowawareplus2)
3781
3782 # that - delta should be what we started with, and that - what we
3783 # started with should be delta.
3784 diff = nowawareplus - delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003785 self.assertIs(diff.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003786 self.assertEqual(nowaware, diff)
3787 self.assertRaises(TypeError, lambda: delta - nowawareplus)
3788 self.assertEqual(nowawareplus - nowaware, delta)
3789
3790 # Make up a random timezone.
3791 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
3792 # Attach it to nowawareplus.
3793 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003794 self.assertIs(nowawareplus.tzinfo, tzr)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003795 # Make sure the difference takes the timezone adjustments into account.
3796 got = nowaware - nowawareplus
3797 # Expected: (nowaware base - nowaware offset) -
3798 # (nowawareplus base - nowawareplus offset) =
3799 # (nowaware base - nowawareplus base) +
3800 # (nowawareplus offset - nowaware offset) =
3801 # -delta + nowawareplus offset - nowaware offset
3802 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
3803 self.assertEqual(got, expected)
3804
3805 # Try max possible difference.
3806 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
3807 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3808 tzinfo=FixedOffset(-1439, "max"))
3809 maxdiff = max - min
3810 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
3811 timedelta(minutes=2*1439))
3812 # Different tzinfo, but the same offset
3813 tza = timezone(HOUR, 'A')
3814 tzb = timezone(HOUR, 'B')
3815 delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
3816 self.assertEqual(delta, self.theclass.min - self.theclass.max)
3817
3818 def test_tzinfo_now(self):
3819 meth = self.theclass.now
3820 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3821 base = meth()
3822 # Try with and without naming the keyword.
3823 off42 = FixedOffset(42, "42")
3824 another = meth(off42)
3825 again = meth(tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003826 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003827 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3828 # Bad argument with and w/o naming the keyword.
3829 self.assertRaises(TypeError, meth, 16)
3830 self.assertRaises(TypeError, meth, tzinfo=16)
3831 # Bad keyword name.
3832 self.assertRaises(TypeError, meth, tinfo=off42)
3833 # Too many args.
3834 self.assertRaises(TypeError, meth, off42, off42)
3835
3836 # We don't know which time zone we're in, and don't have a tzinfo
3837 # class to represent it, so seeing whether a tz argument actually
3838 # does a conversion is tricky.
3839 utc = FixedOffset(0, "utc", 0)
3840 for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
3841 timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
3842 for dummy in range(3):
3843 now = datetime.now(weirdtz)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003844 self.assertIs(now.tzinfo, weirdtz)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003845 utcnow = datetime.utcnow().replace(tzinfo=utc)
3846 now2 = utcnow.astimezone(weirdtz)
3847 if abs(now - now2) < timedelta(seconds=30):
3848 break
3849 # Else the code is broken, or more than 30 seconds passed between
3850 # calls; assuming the latter, just try again.
3851 else:
3852 # Three strikes and we're out.
3853 self.fail("utcnow(), now(tz), or astimezone() may be broken")
3854
3855 def test_tzinfo_fromtimestamp(self):
3856 import time
3857 meth = self.theclass.fromtimestamp
3858 ts = time.time()
3859 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3860 base = meth(ts)
3861 # Try with and without naming the keyword.
3862 off42 = FixedOffset(42, "42")
3863 another = meth(ts, off42)
3864 again = meth(ts, tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003865 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003866 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3867 # Bad argument with and w/o naming the keyword.
3868 self.assertRaises(TypeError, meth, ts, 16)
3869 self.assertRaises(TypeError, meth, ts, tzinfo=16)
3870 # Bad keyword name.
3871 self.assertRaises(TypeError, meth, ts, tinfo=off42)
3872 # Too many args.
3873 self.assertRaises(TypeError, meth, ts, off42, off42)
3874 # Too few args.
3875 self.assertRaises(TypeError, meth)
3876
3877 # Try to make sure tz= actually does some conversion.
3878 timestamp = 1000000000
3879 utcdatetime = datetime.utcfromtimestamp(timestamp)
3880 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
3881 # But on some flavor of Mac, it's nowhere near that. So we can't have
3882 # any idea here what time that actually is, we can only test that
3883 # relative changes match.
3884 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
3885 tz = FixedOffset(utcoffset, "tz", 0)
3886 expected = utcdatetime + utcoffset
3887 got = datetime.fromtimestamp(timestamp, tz)
3888 self.assertEqual(expected, got.replace(tzinfo=None))
3889
3890 def test_tzinfo_utcnow(self):
3891 meth = self.theclass.utcnow
3892 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3893 base = meth()
3894 # Try with and without naming the keyword; for whatever reason,
3895 # utcnow() doesn't accept a tzinfo argument.
3896 off42 = FixedOffset(42, "42")
3897 self.assertRaises(TypeError, meth, off42)
3898 self.assertRaises(TypeError, meth, tzinfo=off42)
3899
3900 def test_tzinfo_utcfromtimestamp(self):
3901 import time
3902 meth = self.theclass.utcfromtimestamp
3903 ts = time.time()
3904 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3905 base = meth(ts)
3906 # Try with and without naming the keyword; for whatever reason,
3907 # utcfromtimestamp() doesn't accept a tzinfo argument.
3908 off42 = FixedOffset(42, "42")
3909 self.assertRaises(TypeError, meth, ts, off42)
3910 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
3911
3912 def test_tzinfo_timetuple(self):
3913 # TestDateTime tested most of this. datetime adds a twist to the
3914 # DST flag.
3915 class DST(tzinfo):
3916 def __init__(self, dstvalue):
3917 if isinstance(dstvalue, int):
3918 dstvalue = timedelta(minutes=dstvalue)
3919 self.dstvalue = dstvalue
3920 def dst(self, dt):
3921 return self.dstvalue
3922
3923 cls = self.theclass
3924 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
3925 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
3926 t = d.timetuple()
3927 self.assertEqual(1, t.tm_year)
3928 self.assertEqual(1, t.tm_mon)
3929 self.assertEqual(1, t.tm_mday)
3930 self.assertEqual(10, t.tm_hour)
3931 self.assertEqual(20, t.tm_min)
3932 self.assertEqual(30, t.tm_sec)
3933 self.assertEqual(0, t.tm_wday)
3934 self.assertEqual(1, t.tm_yday)
3935 self.assertEqual(flag, t.tm_isdst)
3936
3937 # dst() returns wrong type.
3938 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
3939
3940 # dst() at the edge.
3941 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
3942 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
3943
3944 # dst() out of range.
3945 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
3946 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
3947
3948 def test_utctimetuple(self):
3949 class DST(tzinfo):
3950 def __init__(self, dstvalue=0):
3951 if isinstance(dstvalue, int):
3952 dstvalue = timedelta(minutes=dstvalue)
3953 self.dstvalue = dstvalue
3954 def dst(self, dt):
3955 return self.dstvalue
3956
3957 cls = self.theclass
3958 # This can't work: DST didn't implement utcoffset.
3959 self.assertRaises(NotImplementedError,
3960 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
3961
3962 class UOFS(DST):
3963 def __init__(self, uofs, dofs=None):
3964 DST.__init__(self, dofs)
3965 self.uofs = timedelta(minutes=uofs)
3966 def utcoffset(self, dt):
3967 return self.uofs
3968
3969 for dstvalue in -33, 33, 0, None:
3970 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
3971 t = d.utctimetuple()
3972 self.assertEqual(d.year, t.tm_year)
3973 self.assertEqual(d.month, t.tm_mon)
3974 self.assertEqual(d.day, t.tm_mday)
3975 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
3976 self.assertEqual(13, t.tm_min)
3977 self.assertEqual(d.second, t.tm_sec)
3978 self.assertEqual(d.weekday(), t.tm_wday)
3979 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
3980 t.tm_yday)
3981 # Ensure tm_isdst is 0 regardless of what dst() says: DST
3982 # is never in effect for a UTC time.
3983 self.assertEqual(0, t.tm_isdst)
3984
3985 # For naive datetime, utctimetuple == timetuple except for isdst
3986 d = cls(1, 2, 3, 10, 20, 30, 40)
3987 t = d.utctimetuple()
3988 self.assertEqual(t[:-1], d.timetuple()[:-1])
3989 self.assertEqual(0, t.tm_isdst)
3990 # Same if utcoffset is None
3991 class NOFS(DST):
3992 def utcoffset(self, dt):
3993 return None
3994 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
3995 t = d.utctimetuple()
3996 self.assertEqual(t[:-1], d.timetuple()[:-1])
3997 self.assertEqual(0, t.tm_isdst)
3998 # Check that bad tzinfo is detected
3999 class BOFS(DST):
4000 def utcoffset(self, dt):
4001 return "EST"
4002 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
4003 self.assertRaises(TypeError, d.utctimetuple)
4004
4005 # Check that utctimetuple() is the same as
4006 # astimezone(utc).timetuple()
4007 d = cls(2010, 11, 13, 14, 15, 16, 171819)
4008 for tz in [timezone.min, timezone.utc, timezone.max]:
4009 dtz = d.replace(tzinfo=tz)
4010 self.assertEqual(dtz.utctimetuple()[:-1],
4011 dtz.astimezone(timezone.utc).timetuple()[:-1])
4012 # At the edges, UTC adjustment can produce years out-of-range
4013 # for a datetime object. Ensure that an OverflowError is
4014 # raised.
4015 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
4016 # That goes back 1 minute less than a full day.
4017 self.assertRaises(OverflowError, tiny.utctimetuple)
4018
4019 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
4020 # That goes forward 1 minute less than a full day.
4021 self.assertRaises(OverflowError, huge.utctimetuple)
4022 # More overflow cases
4023 tiny = cls.min.replace(tzinfo=timezone(MINUTE))
4024 self.assertRaises(OverflowError, tiny.utctimetuple)
4025 huge = cls.max.replace(tzinfo=timezone(-MINUTE))
4026 self.assertRaises(OverflowError, huge.utctimetuple)
4027
4028 def test_tzinfo_isoformat(self):
4029 zero = FixedOffset(0, "+00:00")
4030 plus = FixedOffset(220, "+03:40")
4031 minus = FixedOffset(-231, "-03:51")
4032 unknown = FixedOffset(None, "")
4033
4034 cls = self.theclass
4035 datestr = '0001-02-03'
4036 for ofs in None, zero, plus, minus, unknown:
4037 for us in 0, 987001:
4038 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
4039 timestr = '04:05:59' + (us and '.987001' or '')
4040 ofsstr = ofs is not None and d.tzname() or ''
4041 tailstr = timestr + ofsstr
4042 iso = d.isoformat()
4043 self.assertEqual(iso, datestr + 'T' + tailstr)
4044 self.assertEqual(iso, d.isoformat('T'))
4045 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
4046 self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
4047 self.assertEqual(str(d), datestr + ' ' + tailstr)
4048
4049 def test_replace(self):
4050 cls = self.theclass
4051 z100 = FixedOffset(100, "+100")
4052 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
4053 args = [1, 2, 3, 4, 5, 6, 7, z100]
4054 base = cls(*args)
4055 self.assertEqual(base, base.replace())
4056
4057 i = 0
4058 for name, newval in (("year", 2),
4059 ("month", 3),
4060 ("day", 4),
4061 ("hour", 5),
4062 ("minute", 6),
4063 ("second", 7),
4064 ("microsecond", 8),
4065 ("tzinfo", zm200)):
4066 newargs = args[:]
4067 newargs[i] = newval
4068 expected = cls(*newargs)
4069 got = base.replace(**{name: newval})
4070 self.assertEqual(expected, got)
4071 i += 1
4072
4073 # Ensure we can get rid of a tzinfo.
4074 self.assertEqual(base.tzname(), "+100")
4075 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004076 self.assertIsNone(base2.tzinfo)
4077 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004078
4079 # Ensure we can add one.
4080 base3 = base2.replace(tzinfo=z100)
4081 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004082 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004083
4084 # Out of bounds.
4085 base = cls(2000, 2, 29)
4086 self.assertRaises(ValueError, base.replace, year=2001)
4087
4088 def test_more_astimezone(self):
4089 # The inherited test_astimezone covered some trivial and error cases.
4090 fnone = FixedOffset(None, "None")
4091 f44m = FixedOffset(44, "44")
4092 fm5h = FixedOffset(-timedelta(hours=5), "m300")
4093
4094 dt = self.theclass.now(tz=f44m)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004095 self.assertIs(dt.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004096 # Replacing with degenerate tzinfo raises an exception.
4097 self.assertRaises(ValueError, dt.astimezone, fnone)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004098 # Replacing with same tzinfo makes no change.
4099 x = dt.astimezone(dt.tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004100 self.assertIs(x.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004101 self.assertEqual(x.date(), dt.date())
4102 self.assertEqual(x.time(), dt.time())
4103
4104 # Replacing with different tzinfo does adjust.
4105 got = dt.astimezone(fm5h)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004106 self.assertIs(got.tzinfo, fm5h)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004107 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
4108 expected = dt - dt.utcoffset() # in effect, convert to UTC
4109 expected += fm5h.utcoffset(dt) # and from there to local time
4110 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
4111 self.assertEqual(got.date(), expected.date())
4112 self.assertEqual(got.time(), expected.time())
4113 self.assertEqual(got.timetz(), expected.timetz())
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004114 self.assertIs(got.tzinfo, expected.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004115 self.assertEqual(got, expected)
4116
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004117 @support.run_with_tz('UTC')
4118 def test_astimezone_default_utc(self):
4119 dt = self.theclass.now(timezone.utc)
4120 self.assertEqual(dt.astimezone(None), dt)
4121 self.assertEqual(dt.astimezone(), dt)
4122
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004123 # Note that offset in TZ variable has the opposite sign to that
4124 # produced by %z directive.
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004125 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4126 def test_astimezone_default_eastern(self):
4127 dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
4128 local = dt.astimezone()
4129 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004130 self.assertEqual(local.strftime("%z %Z"), "-0500 EST")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004131 dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc)
4132 local = dt.astimezone()
4133 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004134 self.assertEqual(local.strftime("%z %Z"), "-0400 EDT")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004135
Alexander Belopolsky1dcf4f92016-03-25 15:42:59 -04004136 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4137 def test_astimezone_default_near_fold(self):
4138 # Issue #26616.
4139 u = datetime(2015, 11, 1, 5, tzinfo=timezone.utc)
4140 t = u.astimezone()
4141 s = t.astimezone()
4142 self.assertEqual(t.tzinfo, s.tzinfo)
4143
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004144 def test_aware_subtract(self):
4145 cls = self.theclass
4146
4147 # Ensure that utcoffset() is ignored when the operands have the
4148 # same tzinfo member.
4149 class OperandDependentOffset(tzinfo):
4150 def utcoffset(self, t):
4151 if t.minute < 10:
4152 # d0 and d1 equal after adjustment
4153 return timedelta(minutes=t.minute)
4154 else:
4155 # d2 off in the weeds
4156 return timedelta(minutes=59)
4157
4158 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
4159 d0 = base.replace(minute=3)
4160 d1 = base.replace(minute=9)
4161 d2 = base.replace(minute=11)
4162 for x in d0, d1, d2:
4163 for y in d0, d1, d2:
4164 got = x - y
4165 expected = timedelta(minutes=x.minute - y.minute)
4166 self.assertEqual(got, expected)
4167
4168 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
4169 # ignored.
4170 base = cls(8, 9, 10, 11, 12, 13, 14)
4171 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
4172 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
4173 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
4174 for x in d0, d1, d2:
4175 for y in d0, d1, d2:
4176 got = x - y
4177 if (x is d0 or x is d1) and (y is d0 or y is d1):
4178 expected = timedelta(0)
4179 elif x is y is d2:
4180 expected = timedelta(0)
4181 elif x is d2:
4182 expected = timedelta(minutes=(11-59)-0)
4183 else:
4184 assert y is d2
4185 expected = timedelta(minutes=0-(11-59))
4186 self.assertEqual(got, expected)
4187
4188 def test_mixed_compare(self):
4189 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
4190 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
4191 self.assertEqual(t1, t2)
4192 t2 = t2.replace(tzinfo=None)
4193 self.assertEqual(t1, t2)
4194 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
4195 self.assertEqual(t1, t2)
4196 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04004197 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004198
4199 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
4200 class Varies(tzinfo):
4201 def __init__(self):
4202 self.offset = timedelta(minutes=22)
4203 def utcoffset(self, t):
4204 self.offset += timedelta(minutes=1)
4205 return self.offset
4206
4207 v = Varies()
4208 t1 = t2.replace(tzinfo=v)
4209 t2 = t2.replace(tzinfo=v)
4210 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
4211 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
4212 self.assertEqual(t1, t2)
4213
4214 # But if they're not identical, it isn't ignored.
4215 t2 = t2.replace(tzinfo=Varies())
4216 self.assertTrue(t1 < t2) # t1's offset counter still going up
4217
4218 def test_subclass_datetimetz(self):
4219
4220 class C(self.theclass):
4221 theAnswer = 42
4222
4223 def __new__(cls, *args, **kws):
4224 temp = kws.copy()
4225 extra = temp.pop('extra')
4226 result = self.theclass.__new__(cls, *args, **temp)
4227 result.extra = extra
4228 return result
4229
4230 def newmeth(self, start):
4231 return start + self.hour + self.year
4232
4233 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
4234
4235 dt1 = self.theclass(*args)
4236 dt2 = C(*args, **{'extra': 7})
4237
4238 self.assertEqual(dt2.__class__, C)
4239 self.assertEqual(dt2.theAnswer, 42)
4240 self.assertEqual(dt2.extra, 7)
4241 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
4242 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
4243
4244# Pain to set up DST-aware tzinfo classes.
4245
4246def first_sunday_on_or_after(dt):
4247 days_to_go = 6 - dt.weekday()
4248 if days_to_go:
4249 dt += timedelta(days_to_go)
4250 return dt
4251
4252ZERO = timedelta(0)
4253MINUTE = timedelta(minutes=1)
4254HOUR = timedelta(hours=1)
4255DAY = timedelta(days=1)
4256# In the US, DST starts at 2am (standard time) on the first Sunday in April.
4257DSTSTART = datetime(1, 4, 1, 2)
4258# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
4259# which is the first Sunday on or after Oct 25. Because we view 1:MM as
4260# being standard time on that day, there is no spelling in local time of
4261# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
4262DSTEND = datetime(1, 10, 25, 1)
4263
4264class USTimeZone(tzinfo):
4265
4266 def __init__(self, hours, reprname, stdname, dstname):
4267 self.stdoffset = timedelta(hours=hours)
4268 self.reprname = reprname
4269 self.stdname = stdname
4270 self.dstname = dstname
4271
4272 def __repr__(self):
4273 return self.reprname
4274
4275 def tzname(self, dt):
4276 if self.dst(dt):
4277 return self.dstname
4278 else:
4279 return self.stdname
4280
4281 def utcoffset(self, dt):
4282 return self.stdoffset + self.dst(dt)
4283
4284 def dst(self, dt):
4285 if dt is None or dt.tzinfo is None:
4286 # An exception instead may be sensible here, in one or more of
4287 # the cases.
4288 return ZERO
4289 assert dt.tzinfo is self
4290
4291 # Find first Sunday in April.
4292 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
4293 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
4294
4295 # Find last Sunday in October.
4296 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
4297 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
4298
4299 # Can't compare naive to aware objects, so strip the timezone from
4300 # dt first.
4301 if start <= dt.replace(tzinfo=None) < end:
4302 return HOUR
4303 else:
4304 return ZERO
4305
4306Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
4307Central = USTimeZone(-6, "Central", "CST", "CDT")
4308Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
4309Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
4310utc_real = FixedOffset(0, "UTC", 0)
4311# For better test coverage, we want another flavor of UTC that's west of
4312# the Eastern and Pacific timezones.
4313utc_fake = FixedOffset(-12*60, "UTCfake", 0)
4314
4315class TestTimezoneConversions(unittest.TestCase):
4316 # The DST switch times for 2002, in std time.
4317 dston = datetime(2002, 4, 7, 2)
4318 dstoff = datetime(2002, 10, 27, 1)
4319
4320 theclass = datetime
4321
4322 # Check a time that's inside DST.
4323 def checkinside(self, dt, tz, utc, dston, dstoff):
4324 self.assertEqual(dt.dst(), HOUR)
4325
4326 # Conversion to our own timezone is always an identity.
4327 self.assertEqual(dt.astimezone(tz), dt)
4328
4329 asutc = dt.astimezone(utc)
4330 there_and_back = asutc.astimezone(tz)
4331
4332 # Conversion to UTC and back isn't always an identity here,
4333 # because there are redundant spellings (in local time) of
4334 # UTC time when DST begins: the clock jumps from 1:59:59
4335 # to 3:00:00, and a local time of 2:MM:SS doesn't really
4336 # make sense then. The classes above treat 2:MM:SS as
4337 # daylight time then (it's "after 2am"), really an alias
4338 # for 1:MM:SS standard time. The latter form is what
4339 # conversion back from UTC produces.
4340 if dt.date() == dston.date() and dt.hour == 2:
4341 # We're in the redundant hour, and coming back from
4342 # UTC gives the 1:MM:SS standard-time spelling.
4343 self.assertEqual(there_and_back + HOUR, dt)
4344 # Although during was considered to be in daylight
4345 # time, there_and_back is not.
4346 self.assertEqual(there_and_back.dst(), ZERO)
4347 # They're the same times in UTC.
4348 self.assertEqual(there_and_back.astimezone(utc),
4349 dt.astimezone(utc))
4350 else:
4351 # We're not in the redundant hour.
4352 self.assertEqual(dt, there_and_back)
4353
4354 # Because we have a redundant spelling when DST begins, there is
Ezio Melotti3b3499b2011-03-16 11:35:38 +02004355 # (unfortunately) an hour when DST ends that can't be spelled at all in
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004356 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
4357 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
4358 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
4359 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
4360 # expressed in local time. Nevertheless, we want conversion back
4361 # from UTC to mimic the local clock's "repeat an hour" behavior.
4362 nexthour_utc = asutc + HOUR
4363 nexthour_tz = nexthour_utc.astimezone(tz)
4364 if dt.date() == dstoff.date() and dt.hour == 0:
4365 # We're in the hour before the last DST hour. The last DST hour
4366 # is ineffable. We want the conversion back to repeat 1:MM.
4367 self.assertEqual(nexthour_tz, dt.replace(hour=1))
4368 nexthour_utc += HOUR
4369 nexthour_tz = nexthour_utc.astimezone(tz)
4370 self.assertEqual(nexthour_tz, dt.replace(hour=1))
4371 else:
4372 self.assertEqual(nexthour_tz - dt, HOUR)
4373
4374 # Check a time that's outside DST.
4375 def checkoutside(self, dt, tz, utc):
4376 self.assertEqual(dt.dst(), ZERO)
4377
4378 # Conversion to our own timezone is always an identity.
4379 self.assertEqual(dt.astimezone(tz), dt)
4380
4381 # Converting to UTC and back is an identity too.
4382 asutc = dt.astimezone(utc)
4383 there_and_back = asutc.astimezone(tz)
4384 self.assertEqual(dt, there_and_back)
4385
4386 def convert_between_tz_and_utc(self, tz, utc):
4387 dston = self.dston.replace(tzinfo=tz)
4388 # Because 1:MM on the day DST ends is taken as being standard time,
4389 # there is no spelling in tz for the last hour of daylight time.
4390 # For purposes of the test, the last hour of DST is 0:MM, which is
4391 # taken as being daylight time (and 1:MM is taken as being standard
4392 # time).
4393 dstoff = self.dstoff.replace(tzinfo=tz)
4394 for delta in (timedelta(weeks=13),
4395 DAY,
4396 HOUR,
4397 timedelta(minutes=1),
4398 timedelta(microseconds=1)):
4399
4400 self.checkinside(dston, tz, utc, dston, dstoff)
4401 for during in dston + delta, dstoff - delta:
4402 self.checkinside(during, tz, utc, dston, dstoff)
4403
4404 self.checkoutside(dstoff, tz, utc)
4405 for outside in dston - delta, dstoff + delta:
4406 self.checkoutside(outside, tz, utc)
4407
4408 def test_easy(self):
4409 # Despite the name of this test, the endcases are excruciating.
4410 self.convert_between_tz_and_utc(Eastern, utc_real)
4411 self.convert_between_tz_and_utc(Pacific, utc_real)
4412 self.convert_between_tz_and_utc(Eastern, utc_fake)
4413 self.convert_between_tz_and_utc(Pacific, utc_fake)
4414 # The next is really dancing near the edge. It works because
4415 # Pacific and Eastern are far enough apart that their "problem
4416 # hours" don't overlap.
4417 self.convert_between_tz_and_utc(Eastern, Pacific)
4418 self.convert_between_tz_and_utc(Pacific, Eastern)
4419 # OTOH, these fail! Don't enable them. The difficulty is that
4420 # the edge case tests assume that every hour is representable in
4421 # the "utc" class. This is always true for a fixed-offset tzinfo
4422 # class (lke utc_real and utc_fake), but not for Eastern or Central.
4423 # For these adjacent DST-aware time zones, the range of time offsets
4424 # tested ends up creating hours in the one that aren't representable
4425 # in the other. For the same reason, we would see failures in the
4426 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
4427 # offset deltas in convert_between_tz_and_utc().
4428 #
4429 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
4430 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
4431
4432 def test_tricky(self):
4433 # 22:00 on day before daylight starts.
4434 fourback = self.dston - timedelta(hours=4)
4435 ninewest = FixedOffset(-9*60, "-0900", 0)
4436 fourback = fourback.replace(tzinfo=ninewest)
4437 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
4438 # 2", we should get the 3 spelling.
4439 # If we plug 22:00 the day before into Eastern, it "looks like std
4440 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
4441 # to 22:00 lands on 2:00, which makes no sense in local time (the
4442 # local clock jumps from 1 to 3). The point here is to make sure we
4443 # get the 3 spelling.
4444 expected = self.dston.replace(hour=3)
4445 got = fourback.astimezone(Eastern).replace(tzinfo=None)
4446 self.assertEqual(expected, got)
4447
4448 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
4449 # case we want the 1:00 spelling.
4450 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
4451 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
4452 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
4453 # spelling.
4454 expected = self.dston.replace(hour=1)
4455 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
4456 self.assertEqual(expected, got)
4457
4458 # Now on the day DST ends, we want "repeat an hour" behavior.
4459 # UTC 4:MM 5:MM 6:MM 7:MM checking these
4460 # EST 23:MM 0:MM 1:MM 2:MM
4461 # EDT 0:MM 1:MM 2:MM 3:MM
4462 # wall 0:MM 1:MM 1:MM 2:MM against these
4463 for utc in utc_real, utc_fake:
4464 for tz in Eastern, Pacific:
4465 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
4466 # Convert that to UTC.
4467 first_std_hour -= tz.utcoffset(None)
4468 # Adjust for possibly fake UTC.
4469 asutc = first_std_hour + utc.utcoffset(None)
4470 # First UTC hour to convert; this is 4:00 when utc=utc_real &
4471 # tz=Eastern.
4472 asutcbase = asutc.replace(tzinfo=utc)
4473 for tzhour in (0, 1, 1, 2):
4474 expectedbase = self.dstoff.replace(hour=tzhour)
4475 for minute in 0, 30, 59:
4476 expected = expectedbase.replace(minute=minute)
4477 asutc = asutcbase.replace(minute=minute)
4478 astz = asutc.astimezone(tz)
4479 self.assertEqual(astz.replace(tzinfo=None), expected)
4480 asutcbase += HOUR
4481
4482
4483 def test_bogus_dst(self):
4484 class ok(tzinfo):
4485 def utcoffset(self, dt): return HOUR
4486 def dst(self, dt): return HOUR
4487
4488 now = self.theclass.now().replace(tzinfo=utc_real)
4489 # Doesn't blow up.
4490 now.astimezone(ok())
4491
4492 # Does blow up.
4493 class notok(ok):
4494 def dst(self, dt): return None
4495 self.assertRaises(ValueError, now.astimezone, notok())
4496
4497 # Sometimes blow up. In the following, tzinfo.dst()
4498 # implementation may return None or not None depending on
4499 # whether DST is assumed to be in effect. In this situation,
4500 # a ValueError should be raised by astimezone().
4501 class tricky_notok(ok):
4502 def dst(self, dt):
4503 if dt.year == 2000:
4504 return None
4505 else:
4506 return 10*HOUR
4507 dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
4508 self.assertRaises(ValueError, dt.astimezone, tricky_notok())
4509
4510 def test_fromutc(self):
4511 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
4512 now = datetime.utcnow().replace(tzinfo=utc_real)
4513 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
4514 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
4515 enow = Eastern.fromutc(now) # doesn't blow up
4516 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
4517 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
4518 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
4519
4520 # Always converts UTC to standard time.
4521 class FauxUSTimeZone(USTimeZone):
4522 def fromutc(self, dt):
4523 return dt + self.stdoffset
4524 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
4525
4526 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
4527 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
4528 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
4529
4530 # Check around DST start.
4531 start = self.dston.replace(hour=4, tzinfo=Eastern)
4532 fstart = start.replace(tzinfo=FEastern)
4533 for wall in 23, 0, 1, 3, 4, 5:
4534 expected = start.replace(hour=wall)
4535 if wall == 23:
4536 expected -= timedelta(days=1)
4537 got = Eastern.fromutc(start)
4538 self.assertEqual(expected, got)
4539
4540 expected = fstart + FEastern.stdoffset
4541 got = FEastern.fromutc(fstart)
4542 self.assertEqual(expected, got)
4543
4544 # Ensure astimezone() calls fromutc() too.
4545 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
4546 self.assertEqual(expected, got)
4547
4548 start += HOUR
4549 fstart += HOUR
4550
4551 # Check around DST end.
4552 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
4553 fstart = start.replace(tzinfo=FEastern)
4554 for wall in 0, 1, 1, 2, 3, 4:
4555 expected = start.replace(hour=wall)
4556 got = Eastern.fromutc(start)
4557 self.assertEqual(expected, got)
4558
4559 expected = fstart + FEastern.stdoffset
4560 got = FEastern.fromutc(fstart)
4561 self.assertEqual(expected, got)
4562
4563 # Ensure astimezone() calls fromutc() too.
4564 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
4565 self.assertEqual(expected, got)
4566
4567 start += HOUR
4568 fstart += HOUR
4569
4570
4571#############################################################################
4572# oddballs
4573
4574class Oddballs(unittest.TestCase):
4575
4576 def test_bug_1028306(self):
4577 # Trying to compare a date to a datetime should act like a mixed-
4578 # type comparison, despite that datetime is a subclass of date.
4579 as_date = date.today()
4580 as_datetime = datetime.combine(as_date, time())
4581 self.assertTrue(as_date != as_datetime)
4582 self.assertTrue(as_datetime != as_date)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004583 self.assertFalse(as_date == as_datetime)
4584 self.assertFalse(as_datetime == as_date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004585 self.assertRaises(TypeError, lambda: as_date < as_datetime)
4586 self.assertRaises(TypeError, lambda: as_datetime < as_date)
4587 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
4588 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
4589 self.assertRaises(TypeError, lambda: as_date > as_datetime)
4590 self.assertRaises(TypeError, lambda: as_datetime > as_date)
4591 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
4592 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
4593
Raymond Hettinger15f44ab2016-08-30 10:47:49 -07004594 # Nevertheless, comparison should work with the base-class (date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004595 # projection if use of a date method is forced.
4596 self.assertEqual(as_date.__eq__(as_datetime), True)
4597 different_day = (as_date.day + 1) % 20 + 1
4598 as_different = as_datetime.replace(day= different_day)
4599 self.assertEqual(as_date.__eq__(as_different), False)
4600
4601 # And date should compare with other subclasses of date. If a
4602 # subclass wants to stop this, it's up to the subclass to do so.
4603 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
4604 self.assertEqual(as_date, date_sc)
4605 self.assertEqual(date_sc, as_date)
4606
4607 # Ditto for datetimes.
4608 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
4609 as_date.day, 0, 0, 0)
4610 self.assertEqual(as_datetime, datetime_sc)
4611 self.assertEqual(datetime_sc, as_datetime)
4612
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004613 def test_extra_attributes(self):
4614 for x in [date.today(),
4615 time(),
4616 datetime.utcnow(),
4617 timedelta(),
4618 tzinfo(),
4619 timezone(timedelta())]:
4620 with self.assertRaises(AttributeError):
4621 x.abc = 1
4622
4623 def test_check_arg_types(self):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004624 class Number:
4625 def __init__(self, value):
4626 self.value = value
4627 def __int__(self):
4628 return self.value
4629
4630 for xx in [decimal.Decimal(10),
4631 decimal.Decimal('10.9'),
4632 Number(10)]:
4633 self.assertEqual(datetime(10, 10, 10, 10, 10, 10, 10),
4634 datetime(xx, xx, xx, xx, xx, xx, xx))
4635
4636 with self.assertRaisesRegex(TypeError, '^an integer is required '
R David Murray44b548d2016-09-08 13:59:53 -04004637 r'\(got type str\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004638 datetime(10, 10, '10')
4639
4640 f10 = Number(10.9)
4641 with self.assertRaisesRegex(TypeError, '^__int__ returned non-int '
R David Murray44b548d2016-09-08 13:59:53 -04004642 r'\(type float\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004643 datetime(10, 10, f10)
4644
4645 class Float(float):
4646 pass
4647 s10 = Float(10.9)
4648 with self.assertRaisesRegex(TypeError, '^integer argument expected, '
4649 'got float$'):
4650 datetime(10, 10, s10)
4651
4652 with self.assertRaises(TypeError):
4653 datetime(10., 10, 10)
4654 with self.assertRaises(TypeError):
4655 datetime(10, 10., 10)
4656 with self.assertRaises(TypeError):
4657 datetime(10, 10, 10.)
4658 with self.assertRaises(TypeError):
4659 datetime(10, 10, 10, 10.)
4660 with self.assertRaises(TypeError):
4661 datetime(10, 10, 10, 10, 10.)
4662 with self.assertRaises(TypeError):
4663 datetime(10, 10, 10, 10, 10, 10.)
4664 with self.assertRaises(TypeError):
4665 datetime(10, 10, 10, 10, 10, 10, 10.)
4666
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004667#############################################################################
4668# Local Time Disambiguation
4669
4670# An experimental reimplementation of fromutc that respects the "fold" flag.
4671
4672class tzinfo2(tzinfo):
4673
4674 def fromutc(self, dt):
4675 "datetime in UTC -> datetime in local time."
4676
4677 if not isinstance(dt, datetime):
4678 raise TypeError("fromutc() requires a datetime argument")
4679 if dt.tzinfo is not self:
4680 raise ValueError("dt.tzinfo is not self")
4681 # Returned value satisfies
4682 # dt + ldt.utcoffset() = ldt
4683 off0 = dt.replace(fold=0).utcoffset()
4684 off1 = dt.replace(fold=1).utcoffset()
4685 if off0 is None or off1 is None or dt.dst() is None:
4686 raise ValueError
4687 if off0 == off1:
4688 ldt = dt + off0
4689 off1 = ldt.utcoffset()
4690 if off0 == off1:
4691 return ldt
4692 # Now, we discovered both possible offsets, so
4693 # we can just try four possible solutions:
4694 for off in [off0, off1]:
4695 ldt = dt + off
4696 if ldt.utcoffset() == off:
4697 return ldt
4698 ldt = ldt.replace(fold=1)
4699 if ldt.utcoffset() == off:
4700 return ldt
4701
4702 raise ValueError("No suitable local time found")
4703
4704# Reimplementing simplified US timezones to respect the "fold" flag:
4705
4706class USTimeZone2(tzinfo2):
4707
4708 def __init__(self, hours, reprname, stdname, dstname):
4709 self.stdoffset = timedelta(hours=hours)
4710 self.reprname = reprname
4711 self.stdname = stdname
4712 self.dstname = dstname
4713
4714 def __repr__(self):
4715 return self.reprname
4716
4717 def tzname(self, dt):
4718 if self.dst(dt):
4719 return self.dstname
4720 else:
4721 return self.stdname
4722
4723 def utcoffset(self, dt):
4724 return self.stdoffset + self.dst(dt)
4725
4726 def dst(self, dt):
4727 if dt is None or dt.tzinfo is None:
4728 # An exception instead may be sensible here, in one or more of
4729 # the cases.
4730 return ZERO
4731 assert dt.tzinfo is self
4732
4733 # Find first Sunday in April.
4734 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
4735 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
4736
4737 # Find last Sunday in October.
4738 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
4739 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
4740
4741 # Can't compare naive to aware objects, so strip the timezone from
4742 # dt first.
4743 dt = dt.replace(tzinfo=None)
4744 if start + HOUR <= dt < end:
4745 # DST is in effect.
4746 return HOUR
4747 elif end <= dt < end + HOUR:
4748 # Fold (an ambiguous hour): use dt.fold to disambiguate.
4749 return ZERO if dt.fold else HOUR
4750 elif start <= dt < start + HOUR:
4751 # Gap (a non-existent hour): reverse the fold rule.
4752 return HOUR if dt.fold else ZERO
4753 else:
4754 # DST is off.
4755 return ZERO
4756
4757Eastern2 = USTimeZone2(-5, "Eastern2", "EST", "EDT")
4758Central2 = USTimeZone2(-6, "Central2", "CST", "CDT")
4759Mountain2 = USTimeZone2(-7, "Mountain2", "MST", "MDT")
4760Pacific2 = USTimeZone2(-8, "Pacific2", "PST", "PDT")
4761
4762# Europe_Vilnius_1941 tzinfo implementation reproduces the following
4763# 1941 transition from Olson's tzdist:
4764#
4765# Zone NAME GMTOFF RULES FORMAT [UNTIL]
4766# ZoneEurope/Vilnius 1:00 - CET 1940 Aug 3
4767# 3:00 - MSK 1941 Jun 24
4768# 1:00 C-Eur CE%sT 1944 Aug
4769#
4770# $ zdump -v Europe/Vilnius | grep 1941
4771# Europe/Vilnius Mon Jun 23 20:59:59 1941 UTC = Mon Jun 23 23:59:59 1941 MSK isdst=0 gmtoff=10800
4772# Europe/Vilnius Mon Jun 23 21:00:00 1941 UTC = Mon Jun 23 23:00:00 1941 CEST isdst=1 gmtoff=7200
4773
4774class Europe_Vilnius_1941(tzinfo):
4775 def _utc_fold(self):
4776 return [datetime(1941, 6, 23, 21, tzinfo=self), # Mon Jun 23 21:00:00 1941 UTC
4777 datetime(1941, 6, 23, 22, tzinfo=self)] # Mon Jun 23 22:00:00 1941 UTC
4778
4779 def _loc_fold(self):
4780 return [datetime(1941, 6, 23, 23, tzinfo=self), # Mon Jun 23 23:00:00 1941 MSK / CEST
4781 datetime(1941, 6, 24, 0, tzinfo=self)] # Mon Jun 24 00:00:00 1941 CEST
4782
4783 def utcoffset(self, dt):
4784 fold_start, fold_stop = self._loc_fold()
4785 if dt < fold_start:
4786 return 3 * HOUR
4787 if dt < fold_stop:
4788 return (2 if dt.fold else 3) * HOUR
4789 # if dt >= fold_stop
4790 return 2 * HOUR
4791
4792 def dst(self, dt):
4793 fold_start, fold_stop = self._loc_fold()
4794 if dt < fold_start:
4795 return 0 * HOUR
4796 if dt < fold_stop:
4797 return (1 if dt.fold else 0) * HOUR
4798 # if dt >= fold_stop
4799 return 1 * HOUR
4800
4801 def tzname(self, dt):
4802 fold_start, fold_stop = self._loc_fold()
4803 if dt < fold_start:
4804 return 'MSK'
4805 if dt < fold_stop:
4806 return ('MSK', 'CEST')[dt.fold]
4807 # if dt >= fold_stop
4808 return 'CEST'
4809
4810 def fromutc(self, dt):
4811 assert dt.fold == 0
4812 assert dt.tzinfo is self
4813 if dt.year != 1941:
4814 raise NotImplementedError
4815 fold_start, fold_stop = self._utc_fold()
4816 if dt < fold_start:
4817 return dt + 3 * HOUR
4818 if dt < fold_stop:
4819 return (dt + 2 * HOUR).replace(fold=1)
4820 # if dt >= fold_stop
4821 return dt + 2 * HOUR
4822
4823
4824class TestLocalTimeDisambiguation(unittest.TestCase):
4825
4826 def test_vilnius_1941_fromutc(self):
4827 Vilnius = Europe_Vilnius_1941()
4828
4829 gdt = datetime(1941, 6, 23, 20, 59, 59, tzinfo=timezone.utc)
4830 ldt = gdt.astimezone(Vilnius)
4831 self.assertEqual(ldt.strftime("%c %Z%z"),
4832 'Mon Jun 23 23:59:59 1941 MSK+0300')
4833 self.assertEqual(ldt.fold, 0)
4834 self.assertFalse(ldt.dst())
4835
4836 gdt = datetime(1941, 6, 23, 21, tzinfo=timezone.utc)
4837 ldt = gdt.astimezone(Vilnius)
4838 self.assertEqual(ldt.strftime("%c %Z%z"),
4839 'Mon Jun 23 23:00:00 1941 CEST+0200')
4840 self.assertEqual(ldt.fold, 1)
4841 self.assertTrue(ldt.dst())
4842
4843 gdt = datetime(1941, 6, 23, 22, tzinfo=timezone.utc)
4844 ldt = gdt.astimezone(Vilnius)
4845 self.assertEqual(ldt.strftime("%c %Z%z"),
4846 'Tue Jun 24 00:00:00 1941 CEST+0200')
4847 self.assertEqual(ldt.fold, 0)
4848 self.assertTrue(ldt.dst())
4849
4850 def test_vilnius_1941_toutc(self):
4851 Vilnius = Europe_Vilnius_1941()
4852
4853 ldt = datetime(1941, 6, 23, 22, 59, 59, tzinfo=Vilnius)
4854 gdt = ldt.astimezone(timezone.utc)
4855 self.assertEqual(gdt.strftime("%c %Z"),
4856 'Mon Jun 23 19:59:59 1941 UTC')
4857
4858 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius)
4859 gdt = ldt.astimezone(timezone.utc)
4860 self.assertEqual(gdt.strftime("%c %Z"),
4861 'Mon Jun 23 20:59:59 1941 UTC')
4862
4863 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius, fold=1)
4864 gdt = ldt.astimezone(timezone.utc)
4865 self.assertEqual(gdt.strftime("%c %Z"),
4866 'Mon Jun 23 21:59:59 1941 UTC')
4867
4868 ldt = datetime(1941, 6, 24, 0, tzinfo=Vilnius)
4869 gdt = ldt.astimezone(timezone.utc)
4870 self.assertEqual(gdt.strftime("%c %Z"),
4871 'Mon Jun 23 22:00:00 1941 UTC')
4872
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004873 def test_constructors(self):
4874 t = time(0, fold=1)
4875 dt = datetime(1, 1, 1, fold=1)
4876 self.assertEqual(t.fold, 1)
4877 self.assertEqual(dt.fold, 1)
4878 with self.assertRaises(TypeError):
4879 time(0, 0, 0, 0, None, 0)
4880
4881 def test_member(self):
4882 dt = datetime(1, 1, 1, fold=1)
4883 t = dt.time()
4884 self.assertEqual(t.fold, 1)
4885 t = dt.timetz()
4886 self.assertEqual(t.fold, 1)
4887
4888 def test_replace(self):
4889 t = time(0)
4890 dt = datetime(1, 1, 1)
4891 self.assertEqual(t.replace(fold=1).fold, 1)
4892 self.assertEqual(dt.replace(fold=1).fold, 1)
4893 self.assertEqual(t.replace(fold=0).fold, 0)
4894 self.assertEqual(dt.replace(fold=0).fold, 0)
4895 # Check that replacement of other fields does not change "fold".
4896 t = t.replace(fold=1, tzinfo=Eastern)
4897 dt = dt.replace(fold=1, tzinfo=Eastern)
4898 self.assertEqual(t.replace(tzinfo=None).fold, 1)
4899 self.assertEqual(dt.replace(tzinfo=None).fold, 1)
Serhiy Storchaka314d6fc2017-03-31 22:48:16 +03004900 # Out of bounds.
4901 with self.assertRaises(ValueError):
4902 t.replace(fold=2)
4903 with self.assertRaises(ValueError):
4904 dt.replace(fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004905 # Check that fold is a keyword-only argument
4906 with self.assertRaises(TypeError):
4907 t.replace(1, 1, 1, None, 1)
4908 with self.assertRaises(TypeError):
4909 dt.replace(1, 1, 1, 1, 1, 1, 1, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04004910
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004911 def test_comparison(self):
4912 t = time(0)
4913 dt = datetime(1, 1, 1)
4914 self.assertEqual(t, t.replace(fold=1))
4915 self.assertEqual(dt, dt.replace(fold=1))
4916
4917 def test_hash(self):
4918 t = time(0)
4919 dt = datetime(1, 1, 1)
4920 self.assertEqual(hash(t), hash(t.replace(fold=1)))
4921 self.assertEqual(hash(dt), hash(dt.replace(fold=1)))
4922
4923 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4924 def test_fromtimestamp(self):
4925 s = 1414906200
4926 dt0 = datetime.fromtimestamp(s)
4927 dt1 = datetime.fromtimestamp(s + 3600)
4928 self.assertEqual(dt0.fold, 0)
4929 self.assertEqual(dt1.fold, 1)
4930
4931 @support.run_with_tz('Australia/Lord_Howe')
4932 def test_fromtimestamp_lord_howe(self):
4933 tm = _time.localtime(1.4e9)
4934 if _time.strftime('%Z%z', tm) != 'LHST+1030':
4935 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
4936 # $ TZ=Australia/Lord_Howe date -r 1428158700
4937 # Sun Apr 5 01:45:00 LHDT 2015
4938 # $ TZ=Australia/Lord_Howe date -r 1428160500
4939 # Sun Apr 5 01:45:00 LHST 2015
4940 s = 1428158700
4941 t0 = datetime.fromtimestamp(s)
4942 t1 = datetime.fromtimestamp(s + 1800)
4943 self.assertEqual(t0, t1)
4944 self.assertEqual(t0.fold, 0)
4945 self.assertEqual(t1.fold, 1)
4946
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004947 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4948 def test_timestamp(self):
4949 dt0 = datetime(2014, 11, 2, 1, 30)
4950 dt1 = dt0.replace(fold=1)
4951 self.assertEqual(dt0.timestamp() + 3600,
4952 dt1.timestamp())
4953
4954 @support.run_with_tz('Australia/Lord_Howe')
4955 def test_timestamp_lord_howe(self):
4956 tm = _time.localtime(1.4e9)
4957 if _time.strftime('%Z%z', tm) != 'LHST+1030':
4958 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
4959 t = datetime(2015, 4, 5, 1, 45)
4960 s0 = t.replace(fold=0).timestamp()
4961 s1 = t.replace(fold=1).timestamp()
4962 self.assertEqual(s0 + 1800, s1)
4963
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004964 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4965 def test_astimezone(self):
4966 dt0 = datetime(2014, 11, 2, 1, 30)
4967 dt1 = dt0.replace(fold=1)
4968 # Convert both naive instances to aware.
4969 adt0 = dt0.astimezone()
4970 adt1 = dt1.astimezone()
4971 # Check that the first instance in DST zone and the second in STD
4972 self.assertEqual(adt0.tzname(), 'EDT')
4973 self.assertEqual(adt1.tzname(), 'EST')
4974 self.assertEqual(adt0 + HOUR, adt1)
4975 # Aware instances with fixed offset tzinfo's always have fold=0
4976 self.assertEqual(adt0.fold, 0)
4977 self.assertEqual(adt1.fold, 0)
4978
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004979 def test_pickle_fold(self):
4980 t = time(fold=1)
4981 dt = datetime(1, 1, 1, fold=1)
4982 for pickler, unpickler, proto in pickle_choices:
4983 for x in [t, dt]:
4984 s = pickler.dumps(x, proto)
4985 y = unpickler.loads(s)
4986 self.assertEqual(x, y)
4987 self.assertEqual((0 if proto < 4 else x.fold), y.fold)
4988
4989 def test_repr(self):
4990 t = time(fold=1)
4991 dt = datetime(1, 1, 1, fold=1)
4992 self.assertEqual(repr(t), 'datetime.time(0, 0, fold=1)')
4993 self.assertEqual(repr(dt),
4994 'datetime.datetime(1, 1, 1, 0, 0, fold=1)')
4995
4996 def test_dst(self):
4997 # Let's first establish that things work in regular times.
4998 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
4999 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
5000 self.assertEqual(dt_summer.dst(), HOUR)
5001 self.assertEqual(dt_winter.dst(), ZERO)
5002 # The disambiguation flag is ignored
5003 self.assertEqual(dt_summer.replace(fold=1).dst(), HOUR)
5004 self.assertEqual(dt_winter.replace(fold=1).dst(), ZERO)
5005
5006 # Pick local time in the fold.
5007 for minute in [0, 30, 59]:
5008 dt = datetime(2002, 10, 27, 1, minute, tzinfo=Eastern2)
5009 # With fold=0 (the default) it is in DST.
5010 self.assertEqual(dt.dst(), HOUR)
5011 # With fold=1 it is in STD.
5012 self.assertEqual(dt.replace(fold=1).dst(), ZERO)
5013
5014 # Pick local time in the gap.
5015 for minute in [0, 30, 59]:
5016 dt = datetime(2002, 4, 7, 2, minute, tzinfo=Eastern2)
5017 # With fold=0 (the default) it is in STD.
5018 self.assertEqual(dt.dst(), ZERO)
5019 # With fold=1 it is in DST.
5020 self.assertEqual(dt.replace(fold=1).dst(), HOUR)
5021
5022
5023 def test_utcoffset(self):
5024 # Let's first establish that things work in regular times.
5025 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
5026 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
5027 self.assertEqual(dt_summer.utcoffset(), -4 * HOUR)
5028 self.assertEqual(dt_winter.utcoffset(), -5 * HOUR)
5029 # The disambiguation flag is ignored
5030 self.assertEqual(dt_summer.replace(fold=1).utcoffset(), -4 * HOUR)
5031 self.assertEqual(dt_winter.replace(fold=1).utcoffset(), -5 * HOUR)
5032
5033 def test_fromutc(self):
5034 # Let's first establish that things work in regular times.
5035 u_summer = datetime(2002, 10, 27, 6, tzinfo=Eastern2) - timedelta.resolution
5036 u_winter = datetime(2002, 10, 27, 7, tzinfo=Eastern2)
5037 t_summer = Eastern2.fromutc(u_summer)
5038 t_winter = Eastern2.fromutc(u_winter)
5039 self.assertEqual(t_summer, u_summer - 4 * HOUR)
5040 self.assertEqual(t_winter, u_winter - 5 * HOUR)
5041 self.assertEqual(t_summer.fold, 0)
5042 self.assertEqual(t_winter.fold, 0)
5043
5044 # What happens in the fall-back fold?
5045 u = datetime(2002, 10, 27, 5, 30, tzinfo=Eastern2)
5046 t0 = Eastern2.fromutc(u)
5047 u += HOUR
5048 t1 = Eastern2.fromutc(u)
5049 self.assertEqual(t0, t1)
5050 self.assertEqual(t0.fold, 0)
5051 self.assertEqual(t1.fold, 1)
5052 # The tricky part is when u is in the local fold:
5053 u = datetime(2002, 10, 27, 1, 30, tzinfo=Eastern2)
5054 t = Eastern2.fromutc(u)
5055 self.assertEqual((t.day, t.hour), (26, 21))
5056 # .. or gets into the local fold after a standard time adjustment
5057 u = datetime(2002, 10, 27, 6, 30, tzinfo=Eastern2)
5058 t = Eastern2.fromutc(u)
5059 self.assertEqual((t.day, t.hour), (27, 1))
5060
5061 # What happens in the spring-forward gap?
5062 u = datetime(2002, 4, 7, 2, 0, tzinfo=Eastern2)
5063 t = Eastern2.fromutc(u)
5064 self.assertEqual((t.day, t.hour), (6, 21))
5065
5066 def test_mixed_compare_regular(self):
5067 t = datetime(2000, 1, 1, tzinfo=Eastern2)
5068 self.assertEqual(t, t.astimezone(timezone.utc))
5069 t = datetime(2000, 6, 1, tzinfo=Eastern2)
5070 self.assertEqual(t, t.astimezone(timezone.utc))
5071
5072 def test_mixed_compare_fold(self):
5073 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
5074 t_fold_utc = t_fold.astimezone(timezone.utc)
5075 self.assertNotEqual(t_fold, t_fold_utc)
5076
5077 def test_mixed_compare_gap(self):
5078 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
5079 t_gap_utc = t_gap.astimezone(timezone.utc)
5080 self.assertNotEqual(t_gap, t_gap_utc)
5081
5082 def test_hash_aware(self):
5083 t = datetime(2000, 1, 1, tzinfo=Eastern2)
5084 self.assertEqual(hash(t), hash(t.replace(fold=1)))
5085 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
5086 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
5087 self.assertEqual(hash(t_fold), hash(t_fold.replace(fold=1)))
5088 self.assertEqual(hash(t_gap), hash(t_gap.replace(fold=1)))
5089
5090SEC = timedelta(0, 1)
5091
5092def pairs(iterable):
5093 a, b = itertools.tee(iterable)
5094 next(b, None)
5095 return zip(a, b)
5096
5097class ZoneInfo(tzinfo):
5098 zoneroot = '/usr/share/zoneinfo'
5099 def __init__(self, ut, ti):
5100 """
5101
5102 :param ut: array
5103 Array of transition point timestamps
5104 :param ti: list
5105 A list of (offset, isdst, abbr) tuples
5106 :return: None
5107 """
5108 self.ut = ut
5109 self.ti = ti
5110 self.lt = self.invert(ut, ti)
5111
5112 @staticmethod
5113 def invert(ut, ti):
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04005114 lt = (array('q', ut), array('q', ut))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005115 if ut:
5116 offset = ti[0][0] // SEC
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04005117 lt[0][0] += offset
5118 lt[1][0] += offset
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005119 for i in range(1, len(ut)):
5120 lt[0][i] += ti[i-1][0] // SEC
5121 lt[1][i] += ti[i][0] // SEC
5122 return lt
5123
5124 @classmethod
5125 def fromfile(cls, fileobj):
5126 if fileobj.read(4).decode() != "TZif":
5127 raise ValueError("not a zoneinfo file")
5128 fileobj.seek(32)
5129 counts = array('i')
5130 counts.fromfile(fileobj, 3)
5131 if sys.byteorder != 'big':
5132 counts.byteswap()
5133
5134 ut = array('i')
5135 ut.fromfile(fileobj, counts[0])
5136 if sys.byteorder != 'big':
5137 ut.byteswap()
5138
5139 type_indices = array('B')
5140 type_indices.fromfile(fileobj, counts[0])
5141
5142 ttis = []
5143 for i in range(counts[1]):
5144 ttis.append(struct.unpack(">lbb", fileobj.read(6)))
5145
5146 abbrs = fileobj.read(counts[2])
5147
5148 # Convert ttis
5149 for i, (gmtoff, isdst, abbrind) in enumerate(ttis):
5150 abbr = abbrs[abbrind:abbrs.find(0, abbrind)].decode()
5151 ttis[i] = (timedelta(0, gmtoff), isdst, abbr)
5152
5153 ti = [None] * len(ut)
5154 for i, idx in enumerate(type_indices):
5155 ti[i] = ttis[idx]
5156
5157 self = cls(ut, ti)
5158
5159 return self
5160
5161 @classmethod
5162 def fromname(cls, name):
5163 path = os.path.join(cls.zoneroot, name)
5164 with open(path, 'rb') as f:
5165 return cls.fromfile(f)
5166
5167 EPOCHORDINAL = date(1970, 1, 1).toordinal()
5168
5169 def fromutc(self, dt):
5170 """datetime in UTC -> datetime in local time."""
5171
5172 if not isinstance(dt, datetime):
5173 raise TypeError("fromutc() requires a datetime argument")
5174 if dt.tzinfo is not self:
5175 raise ValueError("dt.tzinfo is not self")
5176
5177 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
5178 + dt.hour * 3600
5179 + dt.minute * 60
5180 + dt.second)
5181
5182 if timestamp < self.ut[1]:
5183 tti = self.ti[0]
5184 fold = 0
5185 else:
5186 idx = bisect.bisect_right(self.ut, timestamp)
5187 assert self.ut[idx-1] <= timestamp
5188 assert idx == len(self.ut) or timestamp < self.ut[idx]
5189 tti_prev, tti = self.ti[idx-2:idx]
5190 # Detect fold
5191 shift = tti_prev[0] - tti[0]
5192 fold = (shift > timedelta(0, timestamp - self.ut[idx-1]))
5193 dt += tti[0]
5194 if fold:
5195 return dt.replace(fold=1)
5196 else:
5197 return dt
5198
5199 def _find_ti(self, dt, i):
5200 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
5201 + dt.hour * 3600
5202 + dt.minute * 60
5203 + dt.second)
5204 lt = self.lt[dt.fold]
5205 idx = bisect.bisect_right(lt, timestamp)
5206
5207 return self.ti[max(0, idx - 1)][i]
5208
5209 def utcoffset(self, dt):
5210 return self._find_ti(dt, 0)
5211
5212 def dst(self, dt):
5213 isdst = self._find_ti(dt, 1)
5214 # XXX: We cannot accurately determine the "save" value,
5215 # so let's return 1h whenever DST is in effect. Since
5216 # we don't use dst() in fromutc(), it is unlikely that
5217 # it will be needed for anything more than bool(dst()).
5218 return ZERO if isdst else HOUR
5219
5220 def tzname(self, dt):
5221 return self._find_ti(dt, 2)
5222
5223 @classmethod
5224 def zonenames(cls, zonedir=None):
5225 if zonedir is None:
5226 zonedir = cls.zoneroot
Alexander Belopolsky1b8f26c2016-08-11 11:01:45 -04005227 zone_tab = os.path.join(zonedir, 'zone.tab')
5228 try:
5229 f = open(zone_tab)
5230 except OSError:
5231 return
5232 with f:
5233 for line in f:
5234 line = line.strip()
5235 if line and not line.startswith('#'):
5236 yield line.split()[2]
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005237
5238 @classmethod
5239 def stats(cls, start_year=1):
5240 count = gap_count = fold_count = zeros_count = 0
5241 min_gap = min_fold = timedelta.max
5242 max_gap = max_fold = ZERO
5243 min_gap_datetime = max_gap_datetime = datetime.min
5244 min_gap_zone = max_gap_zone = None
5245 min_fold_datetime = max_fold_datetime = datetime.min
5246 min_fold_zone = max_fold_zone = None
5247 stats_since = datetime(start_year, 1, 1) # Starting from 1970 eliminates a lot of noise
5248 for zonename in cls.zonenames():
5249 count += 1
5250 tz = cls.fromname(zonename)
5251 for dt, shift in tz.transitions():
5252 if dt < stats_since:
5253 continue
5254 if shift > ZERO:
5255 gap_count += 1
5256 if (shift, dt) > (max_gap, max_gap_datetime):
5257 max_gap = shift
5258 max_gap_zone = zonename
5259 max_gap_datetime = dt
5260 if (shift, datetime.max - dt) < (min_gap, datetime.max - min_gap_datetime):
5261 min_gap = shift
5262 min_gap_zone = zonename
5263 min_gap_datetime = dt
5264 elif shift < ZERO:
5265 fold_count += 1
5266 shift = -shift
5267 if (shift, dt) > (max_fold, max_fold_datetime):
5268 max_fold = shift
5269 max_fold_zone = zonename
5270 max_fold_datetime = dt
5271 if (shift, datetime.max - dt) < (min_fold, datetime.max - min_fold_datetime):
5272 min_fold = shift
5273 min_fold_zone = zonename
5274 min_fold_datetime = dt
5275 else:
5276 zeros_count += 1
5277 trans_counts = (gap_count, fold_count, zeros_count)
5278 print("Number of zones: %5d" % count)
5279 print("Number of transitions: %5d = %d (gaps) + %d (folds) + %d (zeros)" %
5280 ((sum(trans_counts),) + trans_counts))
5281 print("Min gap: %16s at %s in %s" % (min_gap, min_gap_datetime, min_gap_zone))
5282 print("Max gap: %16s at %s in %s" % (max_gap, max_gap_datetime, max_gap_zone))
5283 print("Min fold: %16s at %s in %s" % (min_fold, min_fold_datetime, min_fold_zone))
5284 print("Max fold: %16s at %s in %s" % (max_fold, max_fold_datetime, max_fold_zone))
5285
5286
5287 def transitions(self):
5288 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
5289 shift = ti[0] - prev_ti[0]
5290 yield datetime.utcfromtimestamp(t), shift
5291
5292 def nondst_folds(self):
5293 """Find all folds with the same value of isdst on both sides of the transition."""
5294 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
5295 shift = ti[0] - prev_ti[0]
5296 if shift < ZERO and ti[1] == prev_ti[1]:
5297 yield datetime.utcfromtimestamp(t), -shift, prev_ti[2], ti[2]
5298
5299 @classmethod
5300 def print_all_nondst_folds(cls, same_abbr=False, start_year=1):
5301 count = 0
5302 for zonename in cls.zonenames():
5303 tz = cls.fromname(zonename)
5304 for dt, shift, prev_abbr, abbr in tz.nondst_folds():
5305 if dt.year < start_year or same_abbr and prev_abbr != abbr:
5306 continue
5307 count += 1
5308 print("%3d) %-30s %s %10s %5s -> %s" %
5309 (count, zonename, dt, shift, prev_abbr, abbr))
5310
5311 def folds(self):
5312 for t, shift in self.transitions():
5313 if shift < ZERO:
5314 yield t, -shift
5315
5316 def gaps(self):
5317 for t, shift in self.transitions():
5318 if shift > ZERO:
5319 yield t, shift
5320
5321 def zeros(self):
5322 for t, shift in self.transitions():
5323 if not shift:
5324 yield t
5325
5326
5327class ZoneInfoTest(unittest.TestCase):
5328 zonename = 'America/New_York'
5329
5330 def setUp(self):
5331 if sys.platform == "win32":
5332 self.skipTest("Skipping zoneinfo tests on Windows")
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04005333 try:
5334 self.tz = ZoneInfo.fromname(self.zonename)
5335 except FileNotFoundError as err:
5336 self.skipTest("Skipping %s: %s" % (self.zonename, err))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005337
5338 def assertEquivDatetimes(self, a, b):
5339 self.assertEqual((a.replace(tzinfo=None), a.fold, id(a.tzinfo)),
5340 (b.replace(tzinfo=None), b.fold, id(b.tzinfo)))
5341
5342 def test_folds(self):
5343 tz = self.tz
5344 for dt, shift in tz.folds():
5345 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
5346 udt = dt + x
5347 ldt = tz.fromutc(udt.replace(tzinfo=tz))
5348 self.assertEqual(ldt.fold, 1)
5349 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
5350 self.assertEquivDatetimes(adt, ldt)
5351 utcoffset = ldt.utcoffset()
5352 self.assertEqual(ldt.replace(tzinfo=None), udt + utcoffset)
5353 # Round trip
5354 self.assertEquivDatetimes(ldt.astimezone(timezone.utc),
5355 udt.replace(tzinfo=timezone.utc))
5356
5357
5358 for x in [-timedelta.resolution, shift]:
5359 udt = dt + x
5360 udt = udt.replace(tzinfo=tz)
5361 ldt = tz.fromutc(udt)
5362 self.assertEqual(ldt.fold, 0)
5363
5364 def test_gaps(self):
5365 tz = self.tz
5366 for dt, shift in tz.gaps():
5367 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
5368 udt = dt + x
5369 udt = udt.replace(tzinfo=tz)
5370 ldt = tz.fromutc(udt)
5371 self.assertEqual(ldt.fold, 0)
5372 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
5373 self.assertEquivDatetimes(adt, ldt)
5374 utcoffset = ldt.utcoffset()
5375 self.assertEqual(ldt.replace(tzinfo=None), udt.replace(tzinfo=None) + utcoffset)
5376 # Create a local time inside the gap
5377 ldt = tz.fromutc(dt.replace(tzinfo=tz)) - shift + x
5378 self.assertLess(ldt.replace(fold=1).utcoffset(),
5379 ldt.replace(fold=0).utcoffset(),
5380 "At %s." % ldt)
5381
5382 for x in [-timedelta.resolution, shift]:
5383 udt = dt + x
5384 ldt = tz.fromutc(udt.replace(tzinfo=tz))
5385 self.assertEqual(ldt.fold, 0)
5386
5387 def test_system_transitions(self):
5388 if ('Riyadh8' in self.zonename or
5389 # From tzdata NEWS file:
5390 # The files solar87, solar88, and solar89 are no longer distributed.
5391 # They were a negative experiment - that is, a demonstration that
5392 # tz data can represent solar time only with some difficulty and error.
5393 # Their presence in the distribution caused confusion, as Riyadh
5394 # civil time was generally not solar time in those years.
5395 self.zonename.startswith('right/')):
5396 self.skipTest("Skipping %s" % self.zonename)
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04005397 tz = self.tz
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005398 TZ = os.environ.get('TZ')
5399 os.environ['TZ'] = self.zonename
5400 try:
5401 _time.tzset()
5402 for udt, shift in tz.transitions():
Alexander Belopolsky10c2dd22016-08-12 19:08:15 -04005403 if udt.year >= 2037:
5404 # System support for times around the end of 32-bit time_t
5405 # and later is flaky on many systems.
5406 break
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005407 s0 = (udt - datetime(1970, 1, 1)) // SEC
5408 ss = shift // SEC # shift seconds
5409 for x in [-40 * 3600, -20*3600, -1, 0,
5410 ss - 1, ss + 20 * 3600, ss + 40 * 3600]:
5411 s = s0 + x
5412 sdt = datetime.fromtimestamp(s)
5413 tzdt = datetime.fromtimestamp(s, tz).replace(tzinfo=None)
5414 self.assertEquivDatetimes(sdt, tzdt)
5415 s1 = sdt.timestamp()
5416 self.assertEqual(s, s1)
5417 if ss > 0: # gap
5418 # Create local time inside the gap
5419 dt = datetime.fromtimestamp(s0) - shift / 2
5420 ts0 = dt.timestamp()
5421 ts1 = dt.replace(fold=1).timestamp()
5422 self.assertEqual(ts0, s0 + ss / 2)
5423 self.assertEqual(ts1, s0 - ss / 2)
5424 finally:
5425 if TZ is None:
5426 del os.environ['TZ']
5427 else:
5428 os.environ['TZ'] = TZ
5429 _time.tzset()
5430
5431
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04005432class ZoneInfoCompleteTest(unittest.TestSuite):
5433 def __init__(self):
5434 tests = []
5435 if is_resource_enabled('tzdata'):
5436 for name in ZoneInfo.zonenames():
5437 Test = type('ZoneInfoTest[%s]' % name, (ZoneInfoTest,), {})
5438 Test.zonename = name
5439 for method in dir(Test):
5440 if method.startswith('test_'):
5441 tests.append(Test(method))
5442 super().__init__(tests)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005443
5444# Iran had a sub-minute UTC offset before 1946.
5445class IranTest(ZoneInfoTest):
Alexander Belopolsky07e2a0a2016-07-30 11:41:02 -04005446 zonename = 'Asia/Tehran'
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005447
Paul Ganssle04af5b12018-01-24 17:29:30 -05005448
5449class CapiTest(unittest.TestCase):
5450 def setUp(self):
5451 # Since the C API is not present in the _Pure tests, skip all tests
5452 if self.__class__.__name__.endswith('Pure'):
5453 self.skipTest('Not relevant in pure Python')
5454
5455 # This *must* be called, and it must be called first, so until either
5456 # restriction is loosened, we'll call it as part of test setup
5457 _testcapi.test_datetime_capi()
5458
5459 def test_utc_capi(self):
5460 for use_macro in (True, False):
5461 capi_utc = _testcapi.get_timezone_utc_capi(use_macro)
5462
5463 with self.subTest(use_macro=use_macro):
5464 self.assertIs(capi_utc, timezone.utc)
5465
5466 def test_timezones_capi(self):
5467 est_capi, est_macro, est_macro_nn = _testcapi.make_timezones_capi()
5468
5469 exp_named = timezone(timedelta(hours=-5), "EST")
5470 exp_unnamed = timezone(timedelta(hours=-5))
5471
5472 cases = [
5473 ('est_capi', est_capi, exp_named),
5474 ('est_macro', est_macro, exp_named),
5475 ('est_macro_nn', est_macro_nn, exp_unnamed)
5476 ]
5477
5478 for name, tz_act, tz_exp in cases:
5479 with self.subTest(name=name):
5480 self.assertEqual(tz_act, tz_exp)
5481
5482 dt1 = datetime(2000, 2, 4, tzinfo=tz_act)
5483 dt2 = datetime(2000, 2, 4, tzinfo=tz_exp)
5484
5485 self.assertEqual(dt1, dt2)
5486 self.assertEqual(dt1.tzname(), dt2.tzname())
5487
5488 dt_utc = datetime(2000, 2, 4, 5, tzinfo=timezone.utc)
5489
5490 self.assertEqual(dt1.astimezone(timezone.utc), dt_utc)
5491
5492 def test_check_date(self):
5493 class DateSubclass(date):
5494 pass
5495
5496 d = date(2011, 1, 1)
5497 ds = DateSubclass(2011, 1, 1)
5498 dt = datetime(2011, 1, 1)
5499
5500 is_date = _testcapi.datetime_check_date
5501
5502 # Check the ones that should be valid
5503 self.assertTrue(is_date(d))
5504 self.assertTrue(is_date(dt))
5505 self.assertTrue(is_date(ds))
5506 self.assertTrue(is_date(d, True))
5507
5508 # Check that the subclasses do not match exactly
5509 self.assertFalse(is_date(dt, True))
5510 self.assertFalse(is_date(ds, True))
5511
5512 # Check that various other things are not dates at all
5513 args = [tuple(), list(), 1, '2011-01-01',
5514 timedelta(1), timezone.utc, time(12, 00)]
5515 for arg in args:
5516 for exact in (True, False):
5517 with self.subTest(arg=arg, exact=exact):
5518 self.assertFalse(is_date(arg, exact))
5519
5520 def test_check_time(self):
5521 class TimeSubclass(time):
5522 pass
5523
5524 t = time(12, 30)
5525 ts = TimeSubclass(12, 30)
5526
5527 is_time = _testcapi.datetime_check_time
5528
5529 # Check the ones that should be valid
5530 self.assertTrue(is_time(t))
5531 self.assertTrue(is_time(ts))
5532 self.assertTrue(is_time(t, True))
5533
5534 # Check that the subclass does not match exactly
5535 self.assertFalse(is_time(ts, True))
5536
5537 # Check that various other things are not times
5538 args = [tuple(), list(), 1, '2011-01-01',
5539 timedelta(1), timezone.utc, date(2011, 1, 1)]
5540
5541 for arg in args:
5542 for exact in (True, False):
5543 with self.subTest(arg=arg, exact=exact):
5544 self.assertFalse(is_time(arg, exact))
5545
5546 def test_check_datetime(self):
5547 class DateTimeSubclass(datetime):
5548 pass
5549
5550 dt = datetime(2011, 1, 1, 12, 30)
5551 dts = DateTimeSubclass(2011, 1, 1, 12, 30)
5552
5553 is_datetime = _testcapi.datetime_check_datetime
5554
5555 # Check the ones that should be valid
5556 self.assertTrue(is_datetime(dt))
5557 self.assertTrue(is_datetime(dts))
5558 self.assertTrue(is_datetime(dt, True))
5559
5560 # Check that the subclass does not match exactly
5561 self.assertFalse(is_datetime(dts, True))
5562
5563 # Check that various other things are not datetimes
5564 args = [tuple(), list(), 1, '2011-01-01',
5565 timedelta(1), timezone.utc, date(2011, 1, 1)]
5566
5567 for arg in args:
5568 for exact in (True, False):
5569 with self.subTest(arg=arg, exact=exact):
5570 self.assertFalse(is_datetime(arg, exact))
5571
5572 def test_check_delta(self):
5573 class TimeDeltaSubclass(timedelta):
5574 pass
5575
5576 td = timedelta(1)
5577 tds = TimeDeltaSubclass(1)
5578
5579 is_timedelta = _testcapi.datetime_check_delta
5580
5581 # Check the ones that should be valid
5582 self.assertTrue(is_timedelta(td))
5583 self.assertTrue(is_timedelta(tds))
5584 self.assertTrue(is_timedelta(td, True))
5585
5586 # Check that the subclass does not match exactly
5587 self.assertFalse(is_timedelta(tds, True))
5588
5589 # Check that various other things are not timedeltas
5590 args = [tuple(), list(), 1, '2011-01-01',
5591 timezone.utc, date(2011, 1, 1), datetime(2011, 1, 1)]
5592
5593 for arg in args:
5594 for exact in (True, False):
5595 with self.subTest(arg=arg, exact=exact):
5596 self.assertFalse(is_timedelta(arg, exact))
5597
5598 def test_check_tzinfo(self):
5599 class TZInfoSubclass(tzinfo):
5600 pass
5601
5602 tzi = tzinfo()
5603 tzis = TZInfoSubclass()
5604 tz = timezone(timedelta(hours=-5))
5605
5606 is_tzinfo = _testcapi.datetime_check_tzinfo
5607
5608 # Check the ones that should be valid
5609 self.assertTrue(is_tzinfo(tzi))
5610 self.assertTrue(is_tzinfo(tz))
5611 self.assertTrue(is_tzinfo(tzis))
5612 self.assertTrue(is_tzinfo(tzi, True))
5613
5614 # Check that the subclasses do not match exactly
5615 self.assertFalse(is_tzinfo(tz, True))
5616 self.assertFalse(is_tzinfo(tzis, True))
5617
5618 # Check that various other things are not tzinfos
5619 args = [tuple(), list(), 1, '2011-01-01',
5620 date(2011, 1, 1), datetime(2011, 1, 1)]
5621
5622 for arg in args:
5623 for exact in (True, False):
5624 with self.subTest(arg=arg, exact=exact):
5625 self.assertFalse(is_tzinfo(arg, exact))
5626
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04005627def load_tests(loader, standard_tests, pattern):
5628 standard_tests.addTest(ZoneInfoCompleteTest())
5629 return standard_tests
5630
5631
Alexander Belopolskycf86e362010-07-23 19:25:47 +00005632if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -05005633 unittest.main()