blob: 775d3151ae29023ab250677af5b2118659bdb477 [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"""
Paul Ganssle1b97b9b2020-05-16 10:02:59 -04005import io
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04006import itertools
7import bisect
Serhiy Storchakae28209f2015-11-16 11:12:58 +02008import copy
Antoine Pitrou392f4132014-10-03 11:25:30 +02009import decimal
Alexander Belopolskycf86e362010-07-23 19:25:47 +000010import sys
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -040011import os
Alexander Belopolskycf86e362010-07-23 19:25:47 +000012import pickle
Raymond Hettinger5a2146a2014-07-25 14:59:48 -070013import random
Paul Ganssle3df85402018-10-22 12:32:52 -040014import re
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -040015import struct
Alexander Belopolskycf86e362010-07-23 19:25:47 +000016import unittest
17
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -040018from array import array
19
Alexander Belopolskycf86e362010-07-23 19:25:47 +000020from operator import lt, le, gt, ge, eq, ne, truediv, floordiv, mod
21
22from test import support
Serhiy Storchaka17e52642019-08-04 12:38:46 +030023from test.support import is_resource_enabled, ALWAYS_EQ, LARGEST, SMALLEST
Alexander Belopolskycf86e362010-07-23 19:25:47 +000024
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
Serhiy Storchaka8452ca12018-12-07 13:42:10 +020040pickle_loads = {pickle.loads, pickle._loads}
Alexander Belopolskycf86e362010-07-23 19:25:47 +000041
42pickle_choices = [(pickle, pickle, proto)
43 for proto in range(pickle.HIGHEST_PROTOCOL + 1)]
44assert len(pickle_choices) == pickle.HIGHEST_PROTOCOL + 1
45
46# An arbitrary collection of objects of non-datetime types, for testing
47# mixed-type comparisons.
48OTHERSTUFF = (10, 34.5, "abc", {}, [], ())
49
50
51# XXX Copied from test_float.
52INF = float("inf")
53NAN = float("nan")
54
Xtreake6b46aa2019-07-13 18:52:21 +053055
Alexander Belopolskycf86e362010-07-23 19:25:47 +000056#############################################################################
57# module tests
58
59class TestModule(unittest.TestCase):
60
61 def test_constants(self):
62 datetime = datetime_module
63 self.assertEqual(datetime.MINYEAR, 1)
64 self.assertEqual(datetime.MAXYEAR, 9999)
65
t k96b1c592019-09-19 09:34:41 -040066 def test_all(self):
67 """Test that __all__ only points to valid attributes."""
68 all_attrs = dir(datetime_module)
69 for attr in datetime_module.__all__:
70 self.assertIn(attr, all_attrs)
71
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -040072 def test_name_cleanup(self):
Utkarsh Upadhyay287c5592017-07-21 02:14:54 +020073 if '_Pure' in self.__class__.__name__:
74 self.skipTest('Only run for Fast C implementation')
75
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -040076 datetime = datetime_module
77 names = set(name for name in dir(datetime)
78 if not name.startswith('__') and not name.endswith('__'))
79 allowed = set(['MAXYEAR', 'MINYEAR', 'date', 'datetime',
80 'datetime_CAPI', 'time', 'timedelta', 'timezone',
Ammar Askar96d1e692018-07-25 09:54:58 -070081 'tzinfo', 'sys'])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -040082 self.assertEqual(names - allowed, set([]))
83
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -050084 def test_divide_and_round(self):
Utkarsh Upadhyay287c5592017-07-21 02:14:54 +020085 if '_Fast' in self.__class__.__name__:
86 self.skipTest('Only run for Pure Python implementation')
87
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -050088 dar = datetime_module._divide_and_round
89
90 self.assertEqual(dar(-10, -3), 3)
91 self.assertEqual(dar(5, -2), -2)
92
93 # four cases: (2 signs of a) x (2 signs of b)
94 self.assertEqual(dar(7, 3), 2)
95 self.assertEqual(dar(-7, 3), -2)
96 self.assertEqual(dar(7, -3), -2)
97 self.assertEqual(dar(-7, -3), 2)
98
99 # ties to even - eight cases:
100 # (2 signs of a) x (2 signs of b) x (even / odd quotient)
101 self.assertEqual(dar(10, 4), 2)
102 self.assertEqual(dar(-10, 4), -2)
103 self.assertEqual(dar(10, -4), -2)
104 self.assertEqual(dar(-10, -4), 2)
105
106 self.assertEqual(dar(6, 4), 2)
107 self.assertEqual(dar(-6, 4), -2)
108 self.assertEqual(dar(6, -4), -2)
109 self.assertEqual(dar(-6, -4), 2)
110
111
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000112#############################################################################
113# tzinfo tests
114
115class FixedOffset(tzinfo):
116
117 def __init__(self, offset, name, dstoffset=42):
118 if isinstance(offset, int):
119 offset = timedelta(minutes=offset)
120 if isinstance(dstoffset, int):
121 dstoffset = timedelta(minutes=dstoffset)
122 self.__offset = offset
123 self.__name = name
124 self.__dstoffset = dstoffset
125 def __repr__(self):
126 return self.__name.lower()
127 def utcoffset(self, dt):
128 return self.__offset
129 def tzname(self, dt):
130 return self.__name
131 def dst(self, dt):
132 return self.__dstoffset
133
134class PicklableFixedOffset(FixedOffset):
135
136 def __init__(self, offset=None, name=None, dstoffset=None):
137 FixedOffset.__init__(self, offset, name, dstoffset)
138
Berker Peksage3385b42016-03-19 13:16:32 +0200139 def __getstate__(self):
140 return self.__dict__
141
Raymond Hettinger5a2146a2014-07-25 14:59:48 -0700142class _TZInfo(tzinfo):
143 def utcoffset(self, datetime_module):
144 return random.random()
145
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000146class TestTZInfo(unittest.TestCase):
147
Raymond Hettinger5a2146a2014-07-25 14:59:48 -0700148 def test_refcnt_crash_bug_22044(self):
149 tz1 = _TZInfo()
150 dt1 = datetime(2014, 7, 21, 11, 32, 3, 0, tz1)
151 with self.assertRaises(TypeError):
152 dt1.utcoffset()
153
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000154 def test_non_abstractness(self):
155 # In order to allow subclasses to get pickled, the C implementation
156 # wasn't able to get away with having __init__ raise
157 # NotImplementedError.
158 useless = tzinfo()
159 dt = datetime.max
160 self.assertRaises(NotImplementedError, useless.tzname, dt)
161 self.assertRaises(NotImplementedError, useless.utcoffset, dt)
162 self.assertRaises(NotImplementedError, useless.dst, dt)
163
164 def test_subclass_must_override(self):
165 class NotEnough(tzinfo):
166 def __init__(self, offset, name):
167 self.__offset = offset
168 self.__name = name
169 self.assertTrue(issubclass(NotEnough, tzinfo))
170 ne = NotEnough(3, "NotByALongShot")
171 self.assertIsInstance(ne, tzinfo)
172
173 dt = datetime.now()
174 self.assertRaises(NotImplementedError, ne.tzname, dt)
175 self.assertRaises(NotImplementedError, ne.utcoffset, dt)
176 self.assertRaises(NotImplementedError, ne.dst, dt)
177
178 def test_normal(self):
179 fo = FixedOffset(3, "Three")
180 self.assertIsInstance(fo, tzinfo)
181 for dt in datetime.now(), None:
182 self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3))
183 self.assertEqual(fo.tzname(dt), "Three")
184 self.assertEqual(fo.dst(dt), timedelta(minutes=42))
185
186 def test_pickling_base(self):
187 # There's no point to pickling tzinfo objects on their own (they
188 # carry no data), but they need to be picklable anyway else
189 # concrete subclasses can't be pickled.
190 orig = tzinfo.__new__(tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200191 self.assertIs(type(orig), tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000192 for pickler, unpickler, proto in pickle_choices:
193 green = pickler.dumps(orig, proto)
194 derived = unpickler.loads(green)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200195 self.assertIs(type(derived), tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000196
197 def test_pickling_subclass(self):
198 # Make sure we can pickle/unpickle an instance of a subclass.
199 offset = timedelta(minutes=-300)
200 for otype, args in [
201 (PicklableFixedOffset, (offset, 'cookie')),
202 (timezone, (offset,)),
203 (timezone, (offset, "EST"))]:
204 orig = otype(*args)
205 oname = orig.tzname(None)
206 self.assertIsInstance(orig, tzinfo)
207 self.assertIs(type(orig), otype)
208 self.assertEqual(orig.utcoffset(None), offset)
209 self.assertEqual(orig.tzname(None), oname)
210 for pickler, unpickler, proto in pickle_choices:
211 green = pickler.dumps(orig, proto)
212 derived = unpickler.loads(green)
213 self.assertIsInstance(derived, tzinfo)
214 self.assertIs(type(derived), otype)
215 self.assertEqual(derived.utcoffset(None), offset)
216 self.assertEqual(derived.tzname(None), oname)
217
Alexander Belopolskyc79447b2015-09-27 21:41:55 -0400218 def test_issue23600(self):
219 DSTDIFF = DSTOFFSET = timedelta(hours=1)
220
221 class UKSummerTime(tzinfo):
222 """Simple time zone which pretends to always be in summer time, since
223 that's what shows the failure.
224 """
225
226 def utcoffset(self, dt):
227 return DSTOFFSET
228
229 def dst(self, dt):
230 return DSTDIFF
231
232 def tzname(self, dt):
233 return 'UKSummerTime'
234
235 tz = UKSummerTime()
236 u = datetime(2014, 4, 26, 12, 1, tzinfo=tz)
237 t = tz.fromutc(u)
238 self.assertEqual(t - t.utcoffset(), u)
239
240
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000241class TestTimeZone(unittest.TestCase):
242
243 def setUp(self):
244 self.ACDT = timezone(timedelta(hours=9.5), 'ACDT')
245 self.EST = timezone(-timedelta(hours=5), 'EST')
246 self.DT = datetime(2010, 1, 1)
247
248 def test_str(self):
249 for tz in [self.ACDT, self.EST, timezone.utc,
250 timezone.min, timezone.max]:
251 self.assertEqual(str(tz), tz.tzname(None))
252
253 def test_repr(self):
254 datetime = datetime_module
255 for tz in [self.ACDT, self.EST, timezone.utc,
256 timezone.min, timezone.max]:
257 # test round-trip
258 tzrep = repr(tz)
259 self.assertEqual(tz, eval(tzrep))
260
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000261 def test_class_members(self):
262 limit = timedelta(hours=23, minutes=59)
263 self.assertEqual(timezone.utc.utcoffset(None), ZERO)
264 self.assertEqual(timezone.min.utcoffset(None), -limit)
265 self.assertEqual(timezone.max.utcoffset(None), limit)
266
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000267 def test_constructor(self):
Alexander Belopolsky1bcbaab2010-10-14 17:03:51 +0000268 self.assertIs(timezone.utc, timezone(timedelta(0)))
269 self.assertIsNot(timezone.utc, timezone(timedelta(0), 'UTC'))
270 self.assertEqual(timezone.utc, timezone(timedelta(0), 'UTC'))
Alexander Belopolsky018d3532017-07-31 10:26:50 -0400271 for subminute in [timedelta(microseconds=1), timedelta(seconds=1)]:
272 tz = timezone(subminute)
273 self.assertNotEqual(tz.utcoffset(None) % timedelta(minutes=1), 0)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000274 # invalid offsets
Alexander Belopolsky018d3532017-07-31 10:26:50 -0400275 for invalid in [timedelta(1, 1), timedelta(1)]:
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000276 self.assertRaises(ValueError, timezone, invalid)
277 self.assertRaises(ValueError, timezone, -invalid)
278
279 with self.assertRaises(TypeError): timezone(None)
280 with self.assertRaises(TypeError): timezone(42)
281 with self.assertRaises(TypeError): timezone(ZERO, None)
282 with self.assertRaises(TypeError): timezone(ZERO, 42)
283 with self.assertRaises(TypeError): timezone(ZERO, 'ABC', 'extra')
284
285 def test_inheritance(self):
286 self.assertIsInstance(timezone.utc, tzinfo)
287 self.assertIsInstance(self.EST, tzinfo)
288
289 def test_utcoffset(self):
290 dummy = self.DT
291 for h in [0, 1.5, 12]:
292 offset = h * HOUR
293 self.assertEqual(offset, timezone(offset).utcoffset(dummy))
294 self.assertEqual(-offset, timezone(-offset).utcoffset(dummy))
295
296 with self.assertRaises(TypeError): self.EST.utcoffset('')
297 with self.assertRaises(TypeError): self.EST.utcoffset(5)
298
299
300 def test_dst(self):
Raymond Hettinger7beae8a2011-01-06 05:34:17 +0000301 self.assertIsNone(timezone.utc.dst(self.DT))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000302
303 with self.assertRaises(TypeError): self.EST.dst('')
304 with self.assertRaises(TypeError): self.EST.dst(5)
305
306 def test_tzname(self):
Alexander Belopolsky7827a5b2015-09-06 13:07:21 -0400307 self.assertEqual('UTC', timezone.utc.tzname(None))
308 self.assertEqual('UTC', timezone(ZERO).tzname(None))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000309 self.assertEqual('UTC-05:00', timezone(-5 * HOUR).tzname(None))
310 self.assertEqual('UTC+09:30', timezone(9.5 * HOUR).tzname(None))
311 self.assertEqual('UTC-00:01', timezone(timedelta(minutes=-1)).tzname(None))
312 self.assertEqual('XYZ', timezone(-5 * HOUR, 'XYZ').tzname(None))
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +0300313 # bpo-34482: Check that surrogates are handled properly.
314 self.assertEqual('\ud800', timezone(ZERO, '\ud800').tzname(None))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000315
Alexander Belopolsky018d3532017-07-31 10:26:50 -0400316 # Sub-minute offsets:
317 self.assertEqual('UTC+01:06:40', timezone(timedelta(0, 4000)).tzname(None))
318 self.assertEqual('UTC-01:06:40',
319 timezone(-timedelta(0, 4000)).tzname(None))
320 self.assertEqual('UTC+01:06:40.000001',
321 timezone(timedelta(0, 4000, 1)).tzname(None))
322 self.assertEqual('UTC-01:06:40.000001',
323 timezone(-timedelta(0, 4000, 1)).tzname(None))
324
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000325 with self.assertRaises(TypeError): self.EST.tzname('')
326 with self.assertRaises(TypeError): self.EST.tzname(5)
327
328 def test_fromutc(self):
329 with self.assertRaises(ValueError):
330 timezone.utc.fromutc(self.DT)
331 with self.assertRaises(TypeError):
332 timezone.utc.fromutc('not datetime')
333 for tz in [self.EST, self.ACDT, Eastern]:
334 utctime = self.DT.replace(tzinfo=tz)
335 local = tz.fromutc(utctime)
336 self.assertEqual(local - utctime, tz.utcoffset(local))
337 self.assertEqual(local,
338 self.DT.replace(tzinfo=timezone.utc))
339
340 def test_comparison(self):
341 self.assertNotEqual(timezone(ZERO), timezone(HOUR))
342 self.assertEqual(timezone(HOUR), timezone(HOUR))
343 self.assertEqual(timezone(-5 * HOUR), timezone(-5 * HOUR, 'EST'))
344 with self.assertRaises(TypeError): timezone(ZERO) < timezone(ZERO)
345 self.assertIn(timezone(ZERO), {timezone(ZERO)})
Georg Brandl0085a242012-09-22 09:23:12 +0200346 self.assertTrue(timezone(ZERO) != None)
347 self.assertFalse(timezone(ZERO) == None)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000348
Serhiy Storchaka17e52642019-08-04 12:38:46 +0300349 tz = timezone(ZERO)
350 self.assertTrue(tz == ALWAYS_EQ)
351 self.assertFalse(tz != ALWAYS_EQ)
352 self.assertTrue(tz < LARGEST)
353 self.assertFalse(tz > LARGEST)
354 self.assertTrue(tz <= LARGEST)
355 self.assertFalse(tz >= LARGEST)
356 self.assertFalse(tz < SMALLEST)
357 self.assertTrue(tz > SMALLEST)
358 self.assertFalse(tz <= SMALLEST)
359 self.assertTrue(tz >= SMALLEST)
360
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000361 def test_aware_datetime(self):
362 # test that timezone instances can be used by datetime
363 t = datetime(1, 1, 1)
364 for tz in [timezone.min, timezone.max, timezone.utc]:
365 self.assertEqual(tz.tzname(t),
366 t.replace(tzinfo=tz).tzname())
367 self.assertEqual(tz.utcoffset(t),
368 t.replace(tzinfo=tz).utcoffset())
369 self.assertEqual(tz.dst(t),
370 t.replace(tzinfo=tz).dst())
371
Serhiy Storchakae28209f2015-11-16 11:12:58 +0200372 def test_pickle(self):
373 for tz in self.ACDT, self.EST, timezone.min, timezone.max:
374 for pickler, unpickler, proto in pickle_choices:
375 tz_copy = unpickler.loads(pickler.dumps(tz, proto))
376 self.assertEqual(tz_copy, tz)
377 tz = timezone.utc
378 for pickler, unpickler, proto in pickle_choices:
379 tz_copy = unpickler.loads(pickler.dumps(tz, proto))
380 self.assertIs(tz_copy, tz)
381
382 def test_copy(self):
383 for tz in self.ACDT, self.EST, timezone.min, timezone.max:
384 tz_copy = copy.copy(tz)
385 self.assertEqual(tz_copy, tz)
386 tz = timezone.utc
387 tz_copy = copy.copy(tz)
388 self.assertIs(tz_copy, tz)
389
390 def test_deepcopy(self):
391 for tz in self.ACDT, self.EST, timezone.min, timezone.max:
392 tz_copy = copy.deepcopy(tz)
393 self.assertEqual(tz_copy, tz)
394 tz = timezone.utc
395 tz_copy = copy.deepcopy(tz)
396 self.assertIs(tz_copy, tz)
397
Ngalim Siregar92c7e302019-08-09 21:22:16 +0700398 def test_offset_boundaries(self):
399 # Test timedeltas close to the boundaries
400 time_deltas = [
401 timedelta(hours=23, minutes=59),
402 timedelta(hours=23, minutes=59, seconds=59),
403 timedelta(hours=23, minutes=59, seconds=59, microseconds=999999),
404 ]
405 time_deltas.extend([-delta for delta in time_deltas])
406
407 for delta in time_deltas:
408 with self.subTest(test_type='good', delta=delta):
409 timezone(delta)
410
411 # Test timedeltas on and outside the boundaries
412 bad_time_deltas = [
413 timedelta(hours=24),
414 timedelta(hours=24, microseconds=1),
415 ]
416 bad_time_deltas.extend([-delta for delta in bad_time_deltas])
417
418 for delta in bad_time_deltas:
419 with self.subTest(test_type='bad', delta=delta):
420 with self.assertRaises(ValueError):
421 timezone(delta)
422
Pablo Galindo4be11c02019-08-22 20:24:25 +0100423 def test_comparison_with_tzinfo(self):
424 # Constructing tzinfo objects directly should not be done by users
425 # and serves only to check the bug described in bpo-37915
426 self.assertNotEqual(timezone.utc, tzinfo())
427 self.assertNotEqual(timezone(timedelta(hours=1)), tzinfo())
Serhiy Storchakae28209f2015-11-16 11:12:58 +0200428
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000429#############################################################################
Ezio Melotti85a86292013-08-17 16:57:41 +0300430# Base class for testing a particular aspect of timedelta, time, date and
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000431# datetime comparisons.
432
433class HarmlessMixedComparison:
434 # Test that __eq__ and __ne__ don't complain for mixed-type comparisons.
435
436 # Subclasses must define 'theclass', and theclass(1, 1, 1) must be a
437 # legit constructor.
438
439 def test_harmless_mixed_comparison(self):
440 me = self.theclass(1, 1, 1)
441
442 self.assertFalse(me == ())
443 self.assertTrue(me != ())
444 self.assertFalse(() == me)
445 self.assertTrue(() != me)
446
447 self.assertIn(me, [1, 20, [], me])
448 self.assertIn([], [me, 1, 20, []])
449
Xtreake6b46aa2019-07-13 18:52:21 +0530450 # Comparison to objects of unsupported types should return
451 # NotImplemented which falls back to the right hand side's __eq__
Serhiy Storchaka17e52642019-08-04 12:38:46 +0300452 # method. In this case, ALWAYS_EQ.__eq__ always returns True.
453 # ALWAYS_EQ.__ne__ always returns False.
454 self.assertTrue(me == ALWAYS_EQ)
455 self.assertFalse(me != ALWAYS_EQ)
456
457 # If the other class explicitly defines ordering
458 # relative to our class, it is allowed to do so
459 self.assertTrue(me < LARGEST)
460 self.assertFalse(me > LARGEST)
461 self.assertTrue(me <= LARGEST)
462 self.assertFalse(me >= LARGEST)
463 self.assertFalse(me < SMALLEST)
464 self.assertTrue(me > SMALLEST)
465 self.assertFalse(me <= SMALLEST)
466 self.assertTrue(me >= SMALLEST)
Xtreake6b46aa2019-07-13 18:52:21 +0530467
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000468 def test_harmful_mixed_comparison(self):
469 me = self.theclass(1, 1, 1)
470
471 self.assertRaises(TypeError, lambda: me < ())
472 self.assertRaises(TypeError, lambda: me <= ())
473 self.assertRaises(TypeError, lambda: me > ())
474 self.assertRaises(TypeError, lambda: me >= ())
475
476 self.assertRaises(TypeError, lambda: () < me)
477 self.assertRaises(TypeError, lambda: () <= me)
478 self.assertRaises(TypeError, lambda: () > me)
479 self.assertRaises(TypeError, lambda: () >= me)
480
481#############################################################################
482# timedelta tests
483
484class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
485
486 theclass = timedelta
487
488 def test_constructor(self):
489 eq = self.assertEqual
490 td = timedelta
491
492 # Check keyword args to constructor
493 eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0,
494 milliseconds=0, microseconds=0))
495 eq(td(1), td(days=1))
496 eq(td(0, 1), td(seconds=1))
497 eq(td(0, 0, 1), td(microseconds=1))
498 eq(td(weeks=1), td(days=7))
499 eq(td(days=1), td(hours=24))
500 eq(td(hours=1), td(minutes=60))
501 eq(td(minutes=1), td(seconds=60))
502 eq(td(seconds=1), td(milliseconds=1000))
503 eq(td(milliseconds=1), td(microseconds=1000))
504
505 # Check float args to constructor
506 eq(td(weeks=1.0/7), td(days=1))
507 eq(td(days=1.0/24), td(hours=1))
508 eq(td(hours=1.0/60), td(minutes=1))
509 eq(td(minutes=1.0/60), td(seconds=1))
510 eq(td(seconds=0.001), td(milliseconds=1))
511 eq(td(milliseconds=0.001), td(microseconds=1))
512
513 def test_computations(self):
514 eq = self.assertEqual
515 td = timedelta
516
517 a = td(7) # One week
518 b = td(0, 60) # One minute
519 c = td(0, 0, 1000) # One millisecond
520 eq(a+b+c, td(7, 60, 1000))
521 eq(a-b, td(6, 24*3600 - 60))
522 eq(b.__rsub__(a), td(6, 24*3600 - 60))
523 eq(-a, td(-7))
524 eq(+a, td(7))
525 eq(-b, td(-1, 24*3600 - 60))
526 eq(-c, td(-1, 24*3600 - 1, 999000))
527 eq(abs(a), a)
528 eq(abs(-a), a)
529 eq(td(6, 24*3600), a)
530 eq(td(0, 0, 60*1000000), b)
531 eq(a*10, td(70))
532 eq(a*10, 10*a)
533 eq(a*10, 10*a)
534 eq(b*10, td(0, 600))
535 eq(10*b, td(0, 600))
536 eq(b*10, td(0, 600))
537 eq(c*10, td(0, 0, 10000))
538 eq(10*c, td(0, 0, 10000))
539 eq(c*10, td(0, 0, 10000))
540 eq(a*-1, -a)
541 eq(b*-2, -b-b)
542 eq(c*-2, -c+-c)
543 eq(b*(60*24), (b*60)*24)
544 eq(b*(60*24), (60*b)*24)
545 eq(c*1000, td(0, 1))
546 eq(1000*c, td(0, 1))
547 eq(a//7, td(1))
548 eq(b//10, td(0, 6))
549 eq(c//1000, td(0, 0, 1))
550 eq(a//10, td(0, 7*24*360))
551 eq(a//3600000, td(0, 0, 7*24*1000))
552 eq(a/0.5, td(14))
553 eq(b/0.5, td(0, 120))
554 eq(a/7, td(1))
555 eq(b/10, td(0, 6))
556 eq(c/1000, td(0, 0, 1))
557 eq(a/10, td(0, 7*24*360))
558 eq(a/3600000, td(0, 0, 7*24*1000))
559
560 # Multiplication by float
561 us = td(microseconds=1)
562 eq((3*us) * 0.5, 2*us)
563 eq((5*us) * 0.5, 2*us)
564 eq(0.5 * (3*us), 2*us)
565 eq(0.5 * (5*us), 2*us)
566 eq((-3*us) * 0.5, -2*us)
567 eq((-5*us) * 0.5, -2*us)
568
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -0500569 # Issue #23521
570 eq(td(seconds=1) * 0.123456, td(microseconds=123456))
571 eq(td(seconds=1) * 0.6112295, td(microseconds=611229))
572
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000573 # Division by int and float
574 eq((3*us) / 2, 2*us)
575 eq((5*us) / 2, 2*us)
576 eq((-3*us) / 2.0, -2*us)
577 eq((-5*us) / 2.0, -2*us)
578 eq((3*us) / -2, -2*us)
579 eq((5*us) / -2, -2*us)
580 eq((3*us) / -2.0, -2*us)
581 eq((5*us) / -2.0, -2*us)
582 for i in range(-10, 10):
583 eq((i*us/3)//us, round(i/3))
584 for i in range(-10, 10):
585 eq((i*us/-3)//us, round(i/-3))
586
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -0500587 # Issue #23521
588 eq(td(seconds=1) / (1 / 0.6112295), td(microseconds=611229))
589
Alexander Belopolskyb6f5ec72011-04-05 20:07:38 -0400590 # Issue #11576
591 eq(td(999999999, 86399, 999999) - td(999999999, 86399, 999998),
592 td(0, 0, 1))
593 eq(td(999999999, 1, 1) - td(999999999, 1, 0),
594 td(0, 0, 1))
595
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000596 def test_disallowed_computations(self):
597 a = timedelta(42)
598
599 # Add/sub ints or floats should be illegal
600 for i in 1, 1.0:
601 self.assertRaises(TypeError, lambda: a+i)
602 self.assertRaises(TypeError, lambda: a-i)
603 self.assertRaises(TypeError, lambda: i+a)
604 self.assertRaises(TypeError, lambda: i-a)
605
606 # Division of int by timedelta doesn't make sense.
607 # Division by zero doesn't make sense.
608 zero = 0
609 self.assertRaises(TypeError, lambda: zero // a)
610 self.assertRaises(ZeroDivisionError, lambda: a // zero)
611 self.assertRaises(ZeroDivisionError, lambda: a / zero)
612 self.assertRaises(ZeroDivisionError, lambda: a / 0.0)
613 self.assertRaises(TypeError, lambda: a / '')
614
Eric Smith3ab08ca2010-12-04 15:17:38 +0000615 @support.requires_IEEE_754
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000616 def test_disallowed_special(self):
617 a = timedelta(42)
618 self.assertRaises(ValueError, a.__mul__, NAN)
619 self.assertRaises(ValueError, a.__truediv__, NAN)
620
621 def test_basic_attributes(self):
622 days, seconds, us = 1, 7, 31
623 td = timedelta(days, seconds, us)
624 self.assertEqual(td.days, days)
625 self.assertEqual(td.seconds, seconds)
626 self.assertEqual(td.microseconds, us)
627
628 def test_total_seconds(self):
629 td = timedelta(days=365)
630 self.assertEqual(td.total_seconds(), 31536000.0)
631 for total_seconds in [123456.789012, -123456.789012, 0.123456, 0, 1e6]:
632 td = timedelta(seconds=total_seconds)
633 self.assertEqual(td.total_seconds(), total_seconds)
634 # Issue8644: Test that td.total_seconds() has the same
635 # accuracy as td / timedelta(seconds=1).
636 for ms in [-1, -2, -123]:
637 td = timedelta(microseconds=ms)
638 self.assertEqual(td.total_seconds(), td / timedelta(seconds=1))
639
640 def test_carries(self):
641 t1 = timedelta(days=100,
642 weeks=-7,
643 hours=-24*(100-49),
644 minutes=-3,
645 seconds=12,
646 microseconds=(3*60 - 12) * 1e6 + 1)
647 t2 = timedelta(microseconds=1)
648 self.assertEqual(t1, t2)
649
650 def test_hash_equality(self):
651 t1 = timedelta(days=100,
652 weeks=-7,
653 hours=-24*(100-49),
654 minutes=-3,
655 seconds=12,
656 microseconds=(3*60 - 12) * 1000000)
657 t2 = timedelta()
658 self.assertEqual(hash(t1), hash(t2))
659
660 t1 += timedelta(weeks=7)
661 t2 += timedelta(days=7*7)
662 self.assertEqual(t1, t2)
663 self.assertEqual(hash(t1), hash(t2))
664
665 d = {t1: 1}
666 d[t2] = 2
667 self.assertEqual(len(d), 1)
668 self.assertEqual(d[t1], 2)
669
670 def test_pickling(self):
671 args = 12, 34, 56
672 orig = timedelta(*args)
673 for pickler, unpickler, proto in pickle_choices:
674 green = pickler.dumps(orig, proto)
675 derived = unpickler.loads(green)
676 self.assertEqual(orig, derived)
677
678 def test_compare(self):
679 t1 = timedelta(2, 3, 4)
680 t2 = timedelta(2, 3, 4)
681 self.assertEqual(t1, t2)
682 self.assertTrue(t1 <= t2)
683 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200684 self.assertFalse(t1 != t2)
685 self.assertFalse(t1 < t2)
686 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000687
688 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
689 t2 = timedelta(*args) # this is larger than t1
690 self.assertTrue(t1 < t2)
691 self.assertTrue(t2 > t1)
692 self.assertTrue(t1 <= t2)
693 self.assertTrue(t2 >= t1)
694 self.assertTrue(t1 != t2)
695 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200696 self.assertFalse(t1 == t2)
697 self.assertFalse(t2 == t1)
698 self.assertFalse(t1 > t2)
699 self.assertFalse(t2 < t1)
700 self.assertFalse(t1 >= t2)
701 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000702
703 for badarg in OTHERSTUFF:
704 self.assertEqual(t1 == badarg, False)
705 self.assertEqual(t1 != badarg, True)
706 self.assertEqual(badarg == t1, False)
707 self.assertEqual(badarg != t1, True)
708
709 self.assertRaises(TypeError, lambda: t1 <= badarg)
710 self.assertRaises(TypeError, lambda: t1 < badarg)
711 self.assertRaises(TypeError, lambda: t1 > badarg)
712 self.assertRaises(TypeError, lambda: t1 >= badarg)
713 self.assertRaises(TypeError, lambda: badarg <= t1)
714 self.assertRaises(TypeError, lambda: badarg < t1)
715 self.assertRaises(TypeError, lambda: badarg > t1)
716 self.assertRaises(TypeError, lambda: badarg >= t1)
717
718 def test_str(self):
719 td = timedelta
720 eq = self.assertEqual
721
722 eq(str(td(1)), "1 day, 0:00:00")
723 eq(str(td(-1)), "-1 day, 0:00:00")
724 eq(str(td(2)), "2 days, 0:00:00")
725 eq(str(td(-2)), "-2 days, 0:00:00")
726
727 eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
728 eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
729 eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
730 "-210 days, 23:12:34")
731
732 eq(str(td(milliseconds=1)), "0:00:00.001000")
733 eq(str(td(microseconds=3)), "0:00:00.000003")
734
735 eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
736 microseconds=999999)),
737 "999999999 days, 23:59:59.999999")
738
739 def test_repr(self):
740 name = 'datetime.' + self.theclass.__name__
741 self.assertEqual(repr(self.theclass(1)),
Utkarsh Upadhyaycc5a65c2017-07-25 23:51:33 +0200742 "%s(days=1)" % name)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000743 self.assertEqual(repr(self.theclass(10, 2)),
Utkarsh Upadhyaycc5a65c2017-07-25 23:51:33 +0200744 "%s(days=10, seconds=2)" % name)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000745 self.assertEqual(repr(self.theclass(-10, 2, 400000)),
Utkarsh Upadhyaycc5a65c2017-07-25 23:51:33 +0200746 "%s(days=-10, seconds=2, microseconds=400000)" % name)
747 self.assertEqual(repr(self.theclass(seconds=60)),
748 "%s(seconds=60)" % name)
749 self.assertEqual(repr(self.theclass()),
750 "%s(0)" % name)
751 self.assertEqual(repr(self.theclass(microseconds=100)),
752 "%s(microseconds=100)" % name)
753 self.assertEqual(repr(self.theclass(days=1, microseconds=100)),
754 "%s(days=1, microseconds=100)" % name)
755 self.assertEqual(repr(self.theclass(seconds=1, microseconds=100)),
756 "%s(seconds=1, microseconds=100)" % name)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000757
758 def test_roundtrip(self):
759 for td in (timedelta(days=999999999, hours=23, minutes=59,
760 seconds=59, microseconds=999999),
761 timedelta(days=-999999999),
762 timedelta(days=-999999999, seconds=1),
763 timedelta(days=1, seconds=2, microseconds=3)):
764
765 # Verify td -> string -> td identity.
766 s = repr(td)
767 self.assertTrue(s.startswith('datetime.'))
768 s = s[9:]
769 td2 = eval(s)
770 self.assertEqual(td, td2)
771
772 # Verify identity via reconstructing from pieces.
773 td2 = timedelta(td.days, td.seconds, td.microseconds)
774 self.assertEqual(td, td2)
775
776 def test_resolution_info(self):
777 self.assertIsInstance(timedelta.min, timedelta)
778 self.assertIsInstance(timedelta.max, timedelta)
779 self.assertIsInstance(timedelta.resolution, timedelta)
780 self.assertTrue(timedelta.max > timedelta.min)
781 self.assertEqual(timedelta.min, timedelta(-999999999))
782 self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
783 self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
784
785 def test_overflow(self):
786 tiny = timedelta.resolution
787
788 td = timedelta.min + tiny
789 td -= tiny # no problem
790 self.assertRaises(OverflowError, td.__sub__, tiny)
791 self.assertRaises(OverflowError, td.__add__, -tiny)
792
793 td = timedelta.max - tiny
794 td += tiny # no problem
795 self.assertRaises(OverflowError, td.__add__, tiny)
796 self.assertRaises(OverflowError, td.__sub__, -tiny)
797
798 self.assertRaises(OverflowError, lambda: -timedelta.max)
799
800 day = timedelta(1)
801 self.assertRaises(OverflowError, day.__mul__, 10**9)
802 self.assertRaises(OverflowError, day.__mul__, 1e9)
803 self.assertRaises(OverflowError, day.__truediv__, 1e-20)
804 self.assertRaises(OverflowError, day.__truediv__, 1e-10)
805 self.assertRaises(OverflowError, day.__truediv__, 9e-10)
806
Eric Smith3ab08ca2010-12-04 15:17:38 +0000807 @support.requires_IEEE_754
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000808 def _test_overflow_special(self):
809 day = timedelta(1)
810 self.assertRaises(OverflowError, day.__mul__, INF)
811 self.assertRaises(OverflowError, day.__mul__, -INF)
812
813 def test_microsecond_rounding(self):
814 td = timedelta
815 eq = self.assertEqual
816
817 # Single-field rounding.
818 eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
819 eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
Victor Stinner69cc4872015-09-08 23:58:54 +0200820 eq(td(milliseconds=0.5/1000), td(microseconds=0))
821 eq(td(milliseconds=-0.5/1000), td(microseconds=-0))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000822 eq(td(milliseconds=0.6/1000), td(microseconds=1))
823 eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
Victor Stinner69cc4872015-09-08 23:58:54 +0200824 eq(td(milliseconds=1.5/1000), td(microseconds=2))
825 eq(td(milliseconds=-1.5/1000), td(microseconds=-2))
826 eq(td(seconds=0.5/10**6), td(microseconds=0))
827 eq(td(seconds=-0.5/10**6), td(microseconds=-0))
828 eq(td(seconds=1/2**7), td(microseconds=7812))
829 eq(td(seconds=-1/2**7), td(microseconds=-7812))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000830
831 # Rounding due to contributions from more than one field.
832 us_per_hour = 3600e6
833 us_per_day = us_per_hour * 24
834 eq(td(days=.4/us_per_day), td(0))
835 eq(td(hours=.2/us_per_hour), td(0))
Victor Stinnercd5d7652015-09-09 01:09:21 +0200836 eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000837
838 eq(td(days=-.4/us_per_day), td(0))
839 eq(td(hours=-.2/us_per_hour), td(0))
840 eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
841
Victor Stinner69cc4872015-09-08 23:58:54 +0200842 # Test for a patch in Issue 8860
843 eq(td(microseconds=0.5), 0.5*td(microseconds=1.0))
844 eq(td(microseconds=0.5)//td.resolution, 0.5*td.resolution//td.resolution)
845
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000846 def test_massive_normalization(self):
847 td = timedelta(microseconds=-1)
848 self.assertEqual((td.days, td.seconds, td.microseconds),
849 (-1, 24*3600-1, 999999))
850
851 def test_bool(self):
852 self.assertTrue(timedelta(1))
853 self.assertTrue(timedelta(0, 1))
854 self.assertTrue(timedelta(0, 0, 1))
855 self.assertTrue(timedelta(microseconds=1))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200856 self.assertFalse(timedelta(0))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000857
858 def test_subclass_timedelta(self):
859
860 class T(timedelta):
861 @staticmethod
862 def from_td(td):
863 return T(td.days, td.seconds, td.microseconds)
864
865 def as_hours(self):
866 sum = (self.days * 24 +
867 self.seconds / 3600.0 +
868 self.microseconds / 3600e6)
869 return round(sum)
870
871 t1 = T(days=1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200872 self.assertIs(type(t1), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000873 self.assertEqual(t1.as_hours(), 24)
874
875 t2 = T(days=-1, seconds=-3600)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200876 self.assertIs(type(t2), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000877 self.assertEqual(t2.as_hours(), -25)
878
879 t3 = t1 + t2
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200880 self.assertIs(type(t3), timedelta)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000881 t4 = T.from_td(t3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200882 self.assertIs(type(t4), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000883 self.assertEqual(t3.days, t4.days)
884 self.assertEqual(t3.seconds, t4.seconds)
885 self.assertEqual(t3.microseconds, t4.microseconds)
886 self.assertEqual(str(t3), str(t4))
887 self.assertEqual(t4.as_hours(), -1)
888
Paul Ganssle89427cd2019-02-04 14:42:04 -0500889 def test_subclass_date(self):
890 class DateSubclass(date):
891 pass
892
893 d1 = DateSubclass(2018, 1, 5)
894 td = timedelta(days=1)
895
896 tests = [
897 ('add', lambda d, t: d + t, DateSubclass(2018, 1, 6)),
898 ('radd', lambda d, t: t + d, DateSubclass(2018, 1, 6)),
899 ('sub', lambda d, t: d - t, DateSubclass(2018, 1, 4)),
900 ]
901
902 for name, func, expected in tests:
903 with self.subTest(name):
904 act = func(d1, td)
905 self.assertEqual(act, expected)
906 self.assertIsInstance(act, DateSubclass)
907
908 def test_subclass_datetime(self):
909 class DateTimeSubclass(datetime):
910 pass
911
912 d1 = DateTimeSubclass(2018, 1, 5, 12, 30)
913 td = timedelta(days=1, minutes=30)
914
915 tests = [
916 ('add', lambda d, t: d + t, DateTimeSubclass(2018, 1, 6, 13)),
917 ('radd', lambda d, t: t + d, DateTimeSubclass(2018, 1, 6, 13)),
918 ('sub', lambda d, t: d - t, DateTimeSubclass(2018, 1, 4, 12)),
919 ]
920
921 for name, func, expected in tests:
922 with self.subTest(name):
923 act = func(d1, td)
924 self.assertEqual(act, expected)
925 self.assertIsInstance(act, DateTimeSubclass)
926
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000927 def test_division(self):
928 t = timedelta(hours=1, minutes=24, seconds=19)
929 second = timedelta(seconds=1)
930 self.assertEqual(t / second, 5059.0)
931 self.assertEqual(t // second, 5059)
932
933 t = timedelta(minutes=2, seconds=30)
934 minute = timedelta(minutes=1)
935 self.assertEqual(t / minute, 2.5)
936 self.assertEqual(t // minute, 2)
937
938 zerotd = timedelta(0)
939 self.assertRaises(ZeroDivisionError, truediv, t, zerotd)
940 self.assertRaises(ZeroDivisionError, floordiv, t, zerotd)
941
942 # self.assertRaises(TypeError, truediv, t, 2)
943 # note: floor division of a timedelta by an integer *is*
944 # currently permitted.
945
946 def test_remainder(self):
947 t = timedelta(minutes=2, seconds=30)
948 minute = timedelta(minutes=1)
949 r = t % minute
950 self.assertEqual(r, timedelta(seconds=30))
951
952 t = timedelta(minutes=-2, seconds=30)
953 r = t % minute
954 self.assertEqual(r, timedelta(seconds=30))
955
956 zerotd = timedelta(0)
957 self.assertRaises(ZeroDivisionError, mod, t, zerotd)
958
959 self.assertRaises(TypeError, mod, t, 10)
960
961 def test_divmod(self):
962 t = timedelta(minutes=2, seconds=30)
963 minute = timedelta(minutes=1)
964 q, r = divmod(t, minute)
965 self.assertEqual(q, 2)
966 self.assertEqual(r, timedelta(seconds=30))
967
968 t = timedelta(minutes=-2, seconds=30)
969 q, r = divmod(t, minute)
970 self.assertEqual(q, -2)
971 self.assertEqual(r, timedelta(seconds=30))
972
973 zerotd = timedelta(0)
974 self.assertRaises(ZeroDivisionError, divmod, t, zerotd)
975
976 self.assertRaises(TypeError, divmod, t, 10)
977
Oren Milman865e4b42017-09-19 15:58:11 +0300978 def test_issue31293(self):
979 # The interpreter shouldn't crash in case a timedelta is divided or
980 # multiplied by a float with a bad as_integer_ratio() method.
981 def get_bad_float(bad_ratio):
982 class BadFloat(float):
983 def as_integer_ratio(self):
984 return bad_ratio
985 return BadFloat()
986
987 with self.assertRaises(TypeError):
988 timedelta() / get_bad_float(1 << 1000)
989 with self.assertRaises(TypeError):
990 timedelta() * get_bad_float(1 << 1000)
991
992 for bad_ratio in [(), (42, ), (1, 2, 3)]:
993 with self.assertRaises(ValueError):
994 timedelta() / get_bad_float(bad_ratio)
995 with self.assertRaises(ValueError):
996 timedelta() * get_bad_float(bad_ratio)
997
Serhiy Storchaka4ffd4652017-10-23 17:12:28 +0300998 def test_issue31752(self):
999 # The interpreter shouldn't crash because divmod() returns negative
1000 # remainder.
1001 class BadInt(int):
1002 def __mul__(self, other):
1003 return Prod()
Serhiy Storchaka3ec0f492018-11-20 20:41:09 +02001004 def __rmul__(self, other):
1005 return Prod()
1006 def __floordiv__(self, other):
1007 return Prod()
1008 def __rfloordiv__(self, other):
1009 return Prod()
Serhiy Storchaka4ffd4652017-10-23 17:12:28 +03001010
1011 class Prod:
Serhiy Storchaka3ec0f492018-11-20 20:41:09 +02001012 def __add__(self, other):
1013 return Sum()
Serhiy Storchaka4ffd4652017-10-23 17:12:28 +03001014 def __radd__(self, other):
1015 return Sum()
1016
1017 class Sum(int):
1018 def __divmod__(self, other):
Serhiy Storchaka3ec0f492018-11-20 20:41:09 +02001019 return divmodresult
Serhiy Storchaka4ffd4652017-10-23 17:12:28 +03001020
Serhiy Storchaka3ec0f492018-11-20 20:41:09 +02001021 for divmodresult in [None, (), (0, 1, 2), (0, -1)]:
1022 with self.subTest(divmodresult=divmodresult):
1023 # The following examples should not crash.
1024 try:
1025 timedelta(microseconds=BadInt(1))
1026 except TypeError:
1027 pass
1028 try:
1029 timedelta(hours=BadInt(1))
1030 except TypeError:
1031 pass
1032 try:
1033 timedelta(weeks=BadInt(1))
1034 except (TypeError, ValueError):
1035 pass
1036 try:
1037 timedelta(1) * BadInt(1)
1038 except (TypeError, ValueError):
1039 pass
1040 try:
1041 BadInt(1) * timedelta(1)
1042 except TypeError:
1043 pass
1044 try:
1045 timedelta(1) // BadInt(1)
1046 except TypeError:
1047 pass
Serhiy Storchaka4ffd4652017-10-23 17:12:28 +03001048
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001049
1050#############################################################################
1051# date tests
1052
1053class TestDateOnly(unittest.TestCase):
1054 # Tests here won't pass if also run on datetime objects, so don't
1055 # subclass this to test datetimes too.
1056
1057 def test_delta_non_days_ignored(self):
1058 dt = date(2000, 1, 2)
1059 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
1060 microseconds=5)
1061 days = timedelta(delta.days)
1062 self.assertEqual(days, timedelta(1))
1063
1064 dt2 = dt + delta
1065 self.assertEqual(dt2, dt + days)
1066
1067 dt2 = delta + dt
1068 self.assertEqual(dt2, dt + days)
1069
1070 dt2 = dt - delta
1071 self.assertEqual(dt2, dt - days)
1072
1073 delta = -delta
1074 days = timedelta(delta.days)
1075 self.assertEqual(days, timedelta(-2))
1076
1077 dt2 = dt + delta
1078 self.assertEqual(dt2, dt + days)
1079
1080 dt2 = delta + dt
1081 self.assertEqual(dt2, dt + days)
1082
1083 dt2 = dt - delta
1084 self.assertEqual(dt2, dt - days)
1085
1086class SubclassDate(date):
1087 sub_var = 1
1088
1089class TestDate(HarmlessMixedComparison, unittest.TestCase):
1090 # Tests here should pass for both dates and datetimes, except for a
1091 # few tests that TestDateTime overrides.
1092
1093 theclass = date
1094
1095 def test_basic_attributes(self):
1096 dt = self.theclass(2002, 3, 1)
1097 self.assertEqual(dt.year, 2002)
1098 self.assertEqual(dt.month, 3)
1099 self.assertEqual(dt.day, 1)
1100
1101 def test_roundtrip(self):
1102 for dt in (self.theclass(1, 2, 3),
1103 self.theclass.today()):
1104 # Verify dt -> string -> date identity.
1105 s = repr(dt)
1106 self.assertTrue(s.startswith('datetime.'))
1107 s = s[9:]
1108 dt2 = eval(s)
1109 self.assertEqual(dt, dt2)
1110
1111 # Verify identity via reconstructing from pieces.
1112 dt2 = self.theclass(dt.year, dt.month, dt.day)
1113 self.assertEqual(dt, dt2)
1114
1115 def test_ordinal_conversions(self):
1116 # Check some fixed values.
1117 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
1118 (1, 12, 31, 365),
1119 (2, 1, 1, 366),
1120 # first example from "Calendrical Calculations"
1121 (1945, 11, 12, 710347)]:
1122 d = self.theclass(y, m, d)
1123 self.assertEqual(n, d.toordinal())
1124 fromord = self.theclass.fromordinal(n)
1125 self.assertEqual(d, fromord)
1126 if hasattr(fromord, "hour"):
1127 # if we're checking something fancier than a date, verify
1128 # the extra fields have been zeroed out
1129 self.assertEqual(fromord.hour, 0)
1130 self.assertEqual(fromord.minute, 0)
1131 self.assertEqual(fromord.second, 0)
1132 self.assertEqual(fromord.microsecond, 0)
1133
1134 # Check first and last days of year spottily across the whole
1135 # range of years supported.
1136 for year in range(MINYEAR, MAXYEAR+1, 7):
1137 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
1138 d = self.theclass(year, 1, 1)
1139 n = d.toordinal()
1140 d2 = self.theclass.fromordinal(n)
1141 self.assertEqual(d, d2)
1142 # Verify that moving back a day gets to the end of year-1.
1143 if year > 1:
1144 d = self.theclass.fromordinal(n-1)
1145 d2 = self.theclass(year-1, 12, 31)
1146 self.assertEqual(d, d2)
1147 self.assertEqual(d2.toordinal(), n-1)
1148
1149 # Test every day in a leap-year and a non-leap year.
1150 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
1151 for year, isleap in (2000, True), (2002, False):
1152 n = self.theclass(year, 1, 1).toordinal()
1153 for month, maxday in zip(range(1, 13), dim):
1154 if month == 2 and isleap:
1155 maxday += 1
1156 for day in range(1, maxday+1):
1157 d = self.theclass(year, month, day)
1158 self.assertEqual(d.toordinal(), n)
1159 self.assertEqual(d, self.theclass.fromordinal(n))
1160 n += 1
1161
1162 def test_extreme_ordinals(self):
1163 a = self.theclass.min
1164 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
1165 aord = a.toordinal()
1166 b = a.fromordinal(aord)
1167 self.assertEqual(a, b)
1168
1169 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
1170
1171 b = a + timedelta(days=1)
1172 self.assertEqual(b.toordinal(), aord + 1)
1173 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
1174
1175 a = self.theclass.max
1176 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
1177 aord = a.toordinal()
1178 b = a.fromordinal(aord)
1179 self.assertEqual(a, b)
1180
1181 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
1182
1183 b = a - timedelta(days=1)
1184 self.assertEqual(b.toordinal(), aord - 1)
1185 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
1186
1187 def test_bad_constructor_arguments(self):
1188 # bad years
1189 self.theclass(MINYEAR, 1, 1) # no exception
1190 self.theclass(MAXYEAR, 1, 1) # no exception
1191 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1192 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1193 # bad months
1194 self.theclass(2000, 1, 1) # no exception
1195 self.theclass(2000, 12, 1) # no exception
1196 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1197 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1198 # bad days
1199 self.theclass(2000, 2, 29) # no exception
1200 self.theclass(2004, 2, 29) # no exception
1201 self.theclass(2400, 2, 29) # no exception
1202 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1203 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1204 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1205 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1206 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1207 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1208
1209 def test_hash_equality(self):
1210 d = self.theclass(2000, 12, 31)
1211 # same thing
1212 e = self.theclass(2000, 12, 31)
1213 self.assertEqual(d, e)
1214 self.assertEqual(hash(d), hash(e))
1215
1216 dic = {d: 1}
1217 dic[e] = 2
1218 self.assertEqual(len(dic), 1)
1219 self.assertEqual(dic[d], 2)
1220 self.assertEqual(dic[e], 2)
1221
1222 d = self.theclass(2001, 1, 1)
1223 # same thing
1224 e = self.theclass(2001, 1, 1)
1225 self.assertEqual(d, e)
1226 self.assertEqual(hash(d), hash(e))
1227
1228 dic = {d: 1}
1229 dic[e] = 2
1230 self.assertEqual(len(dic), 1)
1231 self.assertEqual(dic[d], 2)
1232 self.assertEqual(dic[e], 2)
1233
1234 def test_computations(self):
1235 a = self.theclass(2002, 1, 31)
1236 b = self.theclass(1956, 1, 31)
1237 c = self.theclass(2001,2,1)
1238
1239 diff = a-b
1240 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1241 self.assertEqual(diff.seconds, 0)
1242 self.assertEqual(diff.microseconds, 0)
1243
1244 day = timedelta(1)
1245 week = timedelta(7)
1246 a = self.theclass(2002, 3, 2)
1247 self.assertEqual(a + day, self.theclass(2002, 3, 3))
1248 self.assertEqual(day + a, self.theclass(2002, 3, 3))
1249 self.assertEqual(a - day, self.theclass(2002, 3, 1))
1250 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
1251 self.assertEqual(a + week, self.theclass(2002, 3, 9))
1252 self.assertEqual(a - week, self.theclass(2002, 2, 23))
1253 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
1254 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
1255 self.assertEqual((a + week) - a, week)
1256 self.assertEqual((a + day) - a, day)
1257 self.assertEqual((a - week) - a, -week)
1258 self.assertEqual((a - day) - a, -day)
1259 self.assertEqual(a - (a + week), -week)
1260 self.assertEqual(a - (a + day), -day)
1261 self.assertEqual(a - (a - week), week)
1262 self.assertEqual(a - (a - day), day)
1263 self.assertEqual(c - (c - day), day)
1264
1265 # Add/sub ints or floats should be illegal
1266 for i in 1, 1.0:
1267 self.assertRaises(TypeError, lambda: a+i)
1268 self.assertRaises(TypeError, lambda: a-i)
1269 self.assertRaises(TypeError, lambda: i+a)
1270 self.assertRaises(TypeError, lambda: i-a)
1271
1272 # delta - date is senseless.
1273 self.assertRaises(TypeError, lambda: day - a)
1274 # mixing date and (delta or date) via * or // is senseless
1275 self.assertRaises(TypeError, lambda: day * a)
1276 self.assertRaises(TypeError, lambda: a * day)
1277 self.assertRaises(TypeError, lambda: day // a)
1278 self.assertRaises(TypeError, lambda: a // day)
1279 self.assertRaises(TypeError, lambda: a * a)
1280 self.assertRaises(TypeError, lambda: a // a)
1281 # date + date is senseless
1282 self.assertRaises(TypeError, lambda: a + a)
1283
1284 def test_overflow(self):
1285 tiny = self.theclass.resolution
1286
1287 for delta in [tiny, timedelta(1), timedelta(2)]:
1288 dt = self.theclass.min + delta
1289 dt -= delta # no problem
1290 self.assertRaises(OverflowError, dt.__sub__, delta)
1291 self.assertRaises(OverflowError, dt.__add__, -delta)
1292
1293 dt = self.theclass.max - delta
1294 dt += delta # no problem
1295 self.assertRaises(OverflowError, dt.__add__, delta)
1296 self.assertRaises(OverflowError, dt.__sub__, -delta)
1297
1298 def test_fromtimestamp(self):
1299 import time
1300
1301 # Try an arbitrary fixed value.
1302 year, month, day = 1999, 9, 19
1303 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
1304 d = self.theclass.fromtimestamp(ts)
1305 self.assertEqual(d.year, year)
1306 self.assertEqual(d.month, month)
1307 self.assertEqual(d.day, day)
1308
1309 def test_insane_fromtimestamp(self):
1310 # It's possible that some platform maps time_t to double,
1311 # and that this test will fail there. This test should
1312 # exempt such platforms (provided they return reasonable
1313 # results!).
1314 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001315 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001316 insane)
1317
1318 def test_today(self):
1319 import time
1320
1321 # We claim that today() is like fromtimestamp(time.time()), so
1322 # prove it.
1323 for dummy in range(3):
1324 today = self.theclass.today()
1325 ts = time.time()
1326 todayagain = self.theclass.fromtimestamp(ts)
1327 if today == todayagain:
1328 break
1329 # There are several legit reasons that could fail:
1330 # 1. It recently became midnight, between the today() and the
1331 # time() calls.
1332 # 2. The platform time() has such fine resolution that we'll
1333 # never get the same value twice.
1334 # 3. The platform time() has poor resolution, and we just
1335 # happened to call today() right before a resolution quantum
1336 # boundary.
1337 # 4. The system clock got fiddled between calls.
1338 # In any case, wait a little while and try again.
1339 time.sleep(0.1)
1340
1341 # It worked or it didn't. If it didn't, assume it's reason #2, and
1342 # let the test pass if they're within half a second of each other.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001343 if today != todayagain:
1344 self.assertAlmostEqual(todayagain, today,
1345 delta=timedelta(seconds=0.5))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001346
1347 def test_weekday(self):
1348 for i in range(7):
1349 # March 4, 2002 is a Monday
1350 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
1351 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
1352 # January 2, 1956 is a Monday
1353 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
1354 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
1355
1356 def test_isocalendar(self):
1357 # Check examples from
1358 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
Paul Ganssle1b97b9b2020-05-16 10:02:59 -04001359 week_mondays = [
1360 ((2003, 12, 22), (2003, 52, 1)),
1361 ((2003, 12, 29), (2004, 1, 1)),
1362 ((2004, 1, 5), (2004, 2, 1)),
1363 ((2009, 12, 21), (2009, 52, 1)),
1364 ((2009, 12, 28), (2009, 53, 1)),
1365 ((2010, 1, 4), (2010, 1, 1)),
1366 ]
1367
1368 test_cases = []
1369 for cal_date, iso_date in week_mondays:
1370 base_date = self.theclass(*cal_date)
1371 # Adds one test case for every day of the specified weeks
1372 for i in range(7):
1373 new_date = base_date + timedelta(i)
1374 new_iso = iso_date[0:2] + (iso_date[2] + i,)
1375 test_cases.append((new_date, new_iso))
1376
1377 for d, exp_iso in test_cases:
1378 with self.subTest(d=d, comparison="tuple"):
1379 self.assertEqual(d.isocalendar(), exp_iso)
1380
1381 # Check that the tuple contents are accessible by field name
1382 with self.subTest(d=d, comparison="fields"):
1383 t = d.isocalendar()
1384 self.assertEqual((t.year, t.week, t.weekday), exp_iso)
1385
1386 def test_isocalendar_pickling(self):
1387 """Test that the result of datetime.isocalendar() can be pickled.
1388
1389 The result of a round trip should be a plain tuple.
1390 """
1391 d = self.theclass(2019, 1, 1)
1392 p = pickle.dumps(d.isocalendar())
1393 res = pickle.loads(p)
1394 self.assertEqual(type(res), tuple)
1395 self.assertEqual(res, (2019, 1, 2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001396
1397 def test_iso_long_years(self):
1398 # Calculate long ISO years and compare to table from
1399 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1400 ISO_LONG_YEARS_TABLE = """
1401 4 32 60 88
1402 9 37 65 93
1403 15 43 71 99
1404 20 48 76
1405 26 54 82
1406
1407 105 133 161 189
1408 111 139 167 195
1409 116 144 172
1410 122 150 178
1411 128 156 184
1412
1413 201 229 257 285
1414 207 235 263 291
1415 212 240 268 296
1416 218 246 274
1417 224 252 280
1418
1419 303 331 359 387
1420 308 336 364 392
1421 314 342 370 398
1422 320 348 376
1423 325 353 381
1424 """
1425 iso_long_years = sorted(map(int, ISO_LONG_YEARS_TABLE.split()))
1426 L = []
1427 for i in range(400):
1428 d = self.theclass(2000+i, 12, 31)
1429 d1 = self.theclass(1600+i, 12, 31)
1430 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
1431 if d.isocalendar()[1] == 53:
1432 L.append(i)
1433 self.assertEqual(L, iso_long_years)
1434
1435 def test_isoformat(self):
1436 t = self.theclass(2, 3, 2)
1437 self.assertEqual(t.isoformat(), "0002-03-02")
1438
1439 def test_ctime(self):
1440 t = self.theclass(2002, 3, 2)
1441 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
1442
1443 def test_strftime(self):
1444 t = self.theclass(2005, 3, 2)
1445 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
1446 self.assertEqual(t.strftime(""), "") # SF bug #761337
1447 self.assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784
1448
1449 self.assertRaises(TypeError, t.strftime) # needs an arg
1450 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
1451 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
1452
1453 # test that unicode input is allowed (issue 2782)
1454 self.assertEqual(t.strftime("%m"), "03")
1455
1456 # A naive object replaces %z and %Z w/ empty strings.
1457 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1458
1459 #make sure that invalid format specifiers are handled correctly
1460 #self.assertRaises(ValueError, t.strftime, "%e")
1461 #self.assertRaises(ValueError, t.strftime, "%")
1462 #self.assertRaises(ValueError, t.strftime, "%#")
1463
1464 #oh well, some systems just ignore those invalid ones.
Martin Panter46f50722016-05-26 05:35:26 +00001465 #at least, exercise them to make sure that no crashes
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001466 #are generated
1467 for f in ["%e", "%", "%#"]:
1468 try:
1469 t.strftime(f)
1470 except ValueError:
1471 pass
1472
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03001473 # bpo-34482: Check that surrogates don't cause a crash.
1474 try:
1475 t.strftime('%y\ud800%m')
1476 except UnicodeEncodeError:
1477 pass
1478
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001479 #check that this standard extension works
1480 t.strftime("%f")
1481
MichaelSaah454b3d42019-01-14 05:23:39 -05001482 def test_strftime_trailing_percent(self):
Benjamin Petersonf2173ae2019-09-11 11:50:38 +01001483 # bpo-35066: Make sure trailing '%' doesn't cause datetime's strftime to
1484 # complain. Different libcs have different handling of trailing
1485 # percents, so we simply check datetime's strftime acts the same as
1486 # time.strftime.
MichaelSaah454b3d42019-01-14 05:23:39 -05001487 t = self.theclass(2005, 3, 2)
1488 try:
1489 _time.strftime('%')
1490 except ValueError:
1491 self.skipTest('time module does not support trailing %')
Benjamin Petersonf2173ae2019-09-11 11:50:38 +01001492 self.assertEqual(t.strftime('%'), _time.strftime('%', t.timetuple()))
1493 self.assertEqual(
1494 t.strftime("m:%m d:%d y:%y %"),
1495 _time.strftime("m:03 d:02 y:05 %", t.timetuple()),
1496 )
MichaelSaah454b3d42019-01-14 05:23:39 -05001497
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001498 def test_format(self):
1499 dt = self.theclass(2007, 9, 10)
1500 self.assertEqual(dt.__format__(''), str(dt))
1501
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02001502 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001503 dt.__format__(123)
1504
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001505 # check that a derived class's __str__() gets called
1506 class A(self.theclass):
1507 def __str__(self):
1508 return 'A'
1509 a = A(2007, 9, 10)
1510 self.assertEqual(a.__format__(''), 'A')
1511
1512 # check that a derived class's strftime gets called
1513 class B(self.theclass):
1514 def strftime(self, format_spec):
1515 return 'B'
1516 b = B(2007, 9, 10)
1517 self.assertEqual(b.__format__(''), str(dt))
1518
1519 for fmt in ["m:%m d:%d y:%y",
1520 "m:%m d:%d y:%y H:%H M:%M S:%S",
1521 "%z %Z",
1522 ]:
1523 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1524 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1525 self.assertEqual(b.__format__(fmt), 'B')
1526
1527 def test_resolution_info(self):
1528 # XXX: Should min and max respect subclassing?
1529 if issubclass(self.theclass, datetime):
1530 expected_class = datetime
1531 else:
1532 expected_class = date
1533 self.assertIsInstance(self.theclass.min, expected_class)
1534 self.assertIsInstance(self.theclass.max, expected_class)
1535 self.assertIsInstance(self.theclass.resolution, timedelta)
1536 self.assertTrue(self.theclass.max > self.theclass.min)
1537
1538 def test_extreme_timedelta(self):
1539 big = self.theclass.max - self.theclass.min
1540 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
1541 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
1542 # n == 315537897599999999 ~= 2**58.13
1543 justasbig = timedelta(0, 0, n)
1544 self.assertEqual(big, justasbig)
1545 self.assertEqual(self.theclass.min + big, self.theclass.max)
1546 self.assertEqual(self.theclass.max - big, self.theclass.min)
1547
1548 def test_timetuple(self):
1549 for i in range(7):
1550 # January 2, 1956 is a Monday (0)
1551 d = self.theclass(1956, 1, 2+i)
1552 t = d.timetuple()
1553 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
1554 # February 1, 1956 is a Wednesday (2)
1555 d = self.theclass(1956, 2, 1+i)
1556 t = d.timetuple()
1557 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
1558 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
1559 # of the year.
1560 d = self.theclass(1956, 3, 1+i)
1561 t = d.timetuple()
1562 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
1563 self.assertEqual(t.tm_year, 1956)
1564 self.assertEqual(t.tm_mon, 3)
1565 self.assertEqual(t.tm_mday, 1+i)
1566 self.assertEqual(t.tm_hour, 0)
1567 self.assertEqual(t.tm_min, 0)
1568 self.assertEqual(t.tm_sec, 0)
1569 self.assertEqual(t.tm_wday, (3+i)%7)
1570 self.assertEqual(t.tm_yday, 61+i)
1571 self.assertEqual(t.tm_isdst, -1)
1572
1573 def test_pickling(self):
1574 args = 6, 7, 23
1575 orig = self.theclass(*args)
1576 for pickler, unpickler, proto in pickle_choices:
1577 green = pickler.dumps(orig, proto)
1578 derived = unpickler.loads(green)
1579 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02001580 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001581
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02001582 def test_compat_unpickle(self):
1583 tests = [
1584 b"cdatetime\ndate\n(S'\\x07\\xdf\\x0b\\x1b'\ntR.",
1585 b'cdatetime\ndate\n(U\x04\x07\xdf\x0b\x1btR.',
1586 b'\x80\x02cdatetime\ndate\nU\x04\x07\xdf\x0b\x1b\x85R.',
1587 ]
1588 args = 2015, 11, 27
1589 expected = self.theclass(*args)
1590 for data in tests:
1591 for loads in pickle_loads:
1592 derived = loads(data, encoding='latin1')
1593 self.assertEqual(derived, expected)
1594
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001595 def test_compare(self):
1596 t1 = self.theclass(2, 3, 4)
1597 t2 = self.theclass(2, 3, 4)
1598 self.assertEqual(t1, t2)
1599 self.assertTrue(t1 <= t2)
1600 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001601 self.assertFalse(t1 != t2)
1602 self.assertFalse(t1 < t2)
1603 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001604
1605 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
1606 t2 = self.theclass(*args) # this is larger than t1
1607 self.assertTrue(t1 < t2)
1608 self.assertTrue(t2 > t1)
1609 self.assertTrue(t1 <= t2)
1610 self.assertTrue(t2 >= t1)
1611 self.assertTrue(t1 != t2)
1612 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001613 self.assertFalse(t1 == t2)
1614 self.assertFalse(t2 == t1)
1615 self.assertFalse(t1 > t2)
1616 self.assertFalse(t2 < t1)
1617 self.assertFalse(t1 >= t2)
1618 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001619
1620 for badarg in OTHERSTUFF:
1621 self.assertEqual(t1 == badarg, False)
1622 self.assertEqual(t1 != badarg, True)
1623 self.assertEqual(badarg == t1, False)
1624 self.assertEqual(badarg != t1, True)
1625
1626 self.assertRaises(TypeError, lambda: t1 < badarg)
1627 self.assertRaises(TypeError, lambda: t1 > badarg)
1628 self.assertRaises(TypeError, lambda: t1 >= badarg)
1629 self.assertRaises(TypeError, lambda: badarg <= t1)
1630 self.assertRaises(TypeError, lambda: badarg < t1)
1631 self.assertRaises(TypeError, lambda: badarg > t1)
1632 self.assertRaises(TypeError, lambda: badarg >= t1)
1633
1634 def test_mixed_compare(self):
1635 our = self.theclass(2000, 4, 5)
1636
1637 # Our class can be compared for equality to other classes
1638 self.assertEqual(our == 1, False)
1639 self.assertEqual(1 == our, False)
1640 self.assertEqual(our != 1, True)
1641 self.assertEqual(1 != our, True)
1642
1643 # But the ordering is undefined
1644 self.assertRaises(TypeError, lambda: our < 1)
1645 self.assertRaises(TypeError, lambda: 1 < our)
1646
1647 # Repeat those tests with a different class
1648
1649 class SomeClass:
1650 pass
1651
1652 their = SomeClass()
1653 self.assertEqual(our == their, False)
1654 self.assertEqual(their == our, False)
1655 self.assertEqual(our != their, True)
1656 self.assertEqual(their != our, True)
1657 self.assertRaises(TypeError, lambda: our < their)
1658 self.assertRaises(TypeError, lambda: their < our)
1659
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001660 def test_bool(self):
1661 # All dates are considered true.
1662 self.assertTrue(self.theclass.min)
1663 self.assertTrue(self.theclass.max)
1664
Alexander Belopolsky89da3492011-05-02 13:14:24 -04001665 def test_strftime_y2k(self):
1666 for y in (1, 49, 70, 99, 100, 999, 1000, 1970):
Florent Xicluna49ce0682011-11-01 12:56:14 +01001667 d = self.theclass(y, 1, 1)
1668 # Issue 13305: For years < 1000, the value is not always
1669 # padded to 4 digits across platforms. The C standard
1670 # assumes year >= 1900, so it does not specify the number
1671 # of digits.
1672 if d.strftime("%Y") != '%04d' % y:
1673 # Year 42 returns '42', not padded
1674 self.assertEqual(d.strftime("%Y"), '%d' % y)
Victor Stinner15a83e82016-03-12 08:16:48 +01001675 # '0042' is obtained anyway
1676 self.assertEqual(d.strftime("%4Y"), '%04d' % y)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001677
1678 def test_replace(self):
1679 cls = self.theclass
1680 args = [1, 2, 3]
1681 base = cls(*args)
1682 self.assertEqual(base, base.replace())
1683
1684 i = 0
1685 for name, newval in (("year", 2),
1686 ("month", 3),
1687 ("day", 4)):
1688 newargs = args[:]
1689 newargs[i] = newval
1690 expected = cls(*newargs)
1691 got = base.replace(**{name: newval})
1692 self.assertEqual(expected, got)
1693 i += 1
1694
1695 # Out of bounds.
1696 base = cls(2000, 2, 29)
1697 self.assertRaises(ValueError, base.replace, year=2001)
1698
Paul Ganssle191e9932017-11-09 16:34:29 -05001699 def test_subclass_replace(self):
1700 class DateSubclass(self.theclass):
1701 pass
1702
1703 dt = DateSubclass(2012, 1, 1)
1704 self.assertIs(type(dt.replace(year=2013)), DateSubclass)
1705
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001706 def test_subclass_date(self):
1707
1708 class C(self.theclass):
1709 theAnswer = 42
1710
1711 def __new__(cls, *args, **kws):
1712 temp = kws.copy()
1713 extra = temp.pop('extra')
1714 result = self.theclass.__new__(cls, *args, **temp)
1715 result.extra = extra
1716 return result
1717
1718 def newmeth(self, start):
1719 return start + self.year + self.month
1720
1721 args = 2003, 4, 14
1722
1723 dt1 = self.theclass(*args)
1724 dt2 = C(*args, **{'extra': 7})
1725
1726 self.assertEqual(dt2.__class__, C)
1727 self.assertEqual(dt2.theAnswer, 42)
1728 self.assertEqual(dt2.extra, 7)
1729 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1730 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1731
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05001732 def test_subclass_alternate_constructors(self):
1733 # Test that alternate constructors call the constructor
1734 class DateSubclass(self.theclass):
1735 def __new__(cls, *args, **kwargs):
1736 result = self.theclass.__new__(cls, *args, **kwargs)
1737 result.extra = 7
1738
1739 return result
1740
1741 args = (2003, 4, 14)
1742 d_ord = 731319 # Equivalent ordinal date
1743 d_isoformat = '2003-04-14' # Equivalent isoformat()
1744
1745 base_d = DateSubclass(*args)
1746 self.assertIsInstance(base_d, DateSubclass)
1747 self.assertEqual(base_d.extra, 7)
1748
1749 # Timestamp depends on time zone, so we'll calculate the equivalent here
1750 ts = datetime.combine(base_d, time(0)).timestamp()
1751
1752 test_cases = [
1753 ('fromordinal', (d_ord,)),
1754 ('fromtimestamp', (ts,)),
1755 ('fromisoformat', (d_isoformat,)),
1756 ]
1757
1758 for constr_name, constr_args in test_cases:
1759 for base_obj in (DateSubclass, base_d):
1760 # Test both the classmethod and method
1761 with self.subTest(base_obj_type=type(base_obj),
1762 constr_name=constr_name):
1763 constr = getattr(base_obj, constr_name)
1764
1765 dt = constr(*constr_args)
1766
1767 # Test that it creates the right subclass
1768 self.assertIsInstance(dt, DateSubclass)
1769
1770 # Test that it's equal to the base object
1771 self.assertEqual(dt, base_d)
1772
1773 # Test that it called the constructor
1774 self.assertEqual(dt.extra, 7)
1775
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001776 def test_pickling_subclass_date(self):
1777
1778 args = 6, 7, 23
1779 orig = SubclassDate(*args)
1780 for pickler, unpickler, proto in pickle_choices:
1781 green = pickler.dumps(orig, proto)
1782 derived = unpickler.loads(green)
1783 self.assertEqual(orig, derived)
scaramallionc304c9a2020-10-19 01:49:48 +11001784 self.assertTrue(isinstance(derived, SubclassDate))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001785
1786 def test_backdoor_resistance(self):
1787 # For fast unpickling, the constructor accepts a pickle byte string.
1788 # This is a low-overhead backdoor. A user can (by intent or
1789 # mistake) pass a string directly, which (if it's the right length)
1790 # will get treated like a pickle, and bypass the normal sanity
1791 # checks in the constructor. This can create insane objects.
1792 # The constructor doesn't want to burn the time to validate all
1793 # fields, but does check the month field. This stops, e.g.,
1794 # datetime.datetime('1995-03-25') from yielding an insane object.
1795 base = b'1995-03-25'
1796 if not issubclass(self.theclass, datetime):
1797 base = base[:4]
1798 for month_byte in b'9', b'\0', b'\r', b'\xff':
1799 self.assertRaises(TypeError, self.theclass,
1800 base[:2] + month_byte + base[3:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001801 if issubclass(self.theclass, datetime):
1802 # Good bytes, but bad tzinfo:
1803 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
1804 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001805
1806 for ord_byte in range(1, 13):
1807 # This shouldn't blow up because of the month byte alone. If
1808 # the implementation changes to do more-careful checking, it may
1809 # blow up because other fields are insane.
1810 self.theclass(base[:2] + bytes([ord_byte]) + base[3:])
1811
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001812 def test_fromisoformat(self):
1813 # Test that isoformat() is reversible
1814 base_dates = [
1815 (1, 1, 1),
1816 (1000, 2, 14),
1817 (1900, 1, 1),
1818 (2000, 2, 29),
1819 (2004, 11, 12),
1820 (2004, 4, 3),
1821 (2017, 5, 30)
1822 ]
1823
1824 for dt_tuple in base_dates:
1825 dt = self.theclass(*dt_tuple)
1826 dt_str = dt.isoformat()
1827 with self.subTest(dt_str=dt_str):
1828 dt_rt = self.theclass.fromisoformat(dt.isoformat())
1829
1830 self.assertEqual(dt, dt_rt)
1831
1832 def test_fromisoformat_subclass(self):
1833 class DateSubclass(self.theclass):
1834 pass
1835
1836 dt = DateSubclass(2014, 12, 14)
1837
1838 dt_rt = DateSubclass.fromisoformat(dt.isoformat())
1839
1840 self.assertIsInstance(dt_rt, DateSubclass)
1841
1842 def test_fromisoformat_fails(self):
1843 # Test that fromisoformat() fails on invalid values
1844 bad_strs = [
1845 '', # Empty string
Paul Ganssle096329f2018-08-23 11:06:20 -04001846 '\ud800', # bpo-34454: Surrogate code point
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001847 '009-03-04', # Not 10 characters
1848 '123456789', # Not a date
1849 '200a-12-04', # Invalid character in year
1850 '2009-1a-04', # Invalid character in month
1851 '2009-12-0a', # Invalid character in day
1852 '2009-01-32', # Invalid day
1853 '2009-02-29', # Invalid leap day
1854 '20090228', # Valid ISO8601 output not from isoformat()
Paul Ganssle096329f2018-08-23 11:06:20 -04001855 '2009\ud80002\ud80028', # Separators are surrogate codepoints
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001856 ]
1857
1858 for bad_str in bad_strs:
1859 with self.assertRaises(ValueError):
1860 self.theclass.fromisoformat(bad_str)
1861
1862 def test_fromisoformat_fails_typeerror(self):
1863 # Test that fromisoformat fails when passed the wrong type
1864 import io
1865
1866 bad_types = [b'2009-03-01', None, io.StringIO('2009-03-01')]
1867 for bad_type in bad_types:
1868 with self.assertRaises(TypeError):
1869 self.theclass.fromisoformat(bad_type)
1870
Paul Ganssle88c09372019-04-29 09:22:03 -04001871 def test_fromisocalendar(self):
1872 # For each test case, assert that fromisocalendar is the
1873 # inverse of the isocalendar function
1874 dates = [
1875 (2016, 4, 3),
1876 (2005, 1, 2), # (2004, 53, 7)
1877 (2008, 12, 30), # (2009, 1, 2)
1878 (2010, 1, 2), # (2009, 53, 6)
1879 (2009, 12, 31), # (2009, 53, 4)
1880 (1900, 1, 1), # Unusual non-leap year (year % 100 == 0)
1881 (1900, 12, 31),
1882 (2000, 1, 1), # Unusual leap year (year % 400 == 0)
1883 (2000, 12, 31),
1884 (2004, 1, 1), # Leap year
1885 (2004, 12, 31),
1886 (1, 1, 1),
1887 (9999, 12, 31),
1888 (MINYEAR, 1, 1),
1889 (MAXYEAR, 12, 31),
1890 ]
1891
1892 for datecomps in dates:
1893 with self.subTest(datecomps=datecomps):
1894 dobj = self.theclass(*datecomps)
1895 isocal = dobj.isocalendar()
1896
1897 d_roundtrip = self.theclass.fromisocalendar(*isocal)
1898
1899 self.assertEqual(dobj, d_roundtrip)
1900
1901 def test_fromisocalendar_value_errors(self):
1902 isocals = [
1903 (2019, 0, 1),
1904 (2019, -1, 1),
1905 (2019, 54, 1),
1906 (2019, 1, 0),
1907 (2019, 1, -1),
1908 (2019, 1, 8),
1909 (2019, 53, 1),
1910 (10000, 1, 1),
1911 (0, 1, 1),
1912 (9999999, 1, 1),
1913 (2<<32, 1, 1),
1914 (2019, 2<<32, 1),
1915 (2019, 1, 2<<32),
1916 ]
1917
1918 for isocal in isocals:
1919 with self.subTest(isocal=isocal):
1920 with self.assertRaises(ValueError):
1921 self.theclass.fromisocalendar(*isocal)
1922
1923 def test_fromisocalendar_type_errors(self):
1924 err_txformers = [
1925 str,
1926 float,
1927 lambda x: None,
1928 ]
1929
1930 # Take a valid base tuple and transform it to contain one argument
1931 # with the wrong type. Repeat this for each argument, e.g.
1932 # [("2019", 1, 1), (2019, "1", 1), (2019, 1, "1"), ...]
1933 isocals = []
1934 base = (2019, 1, 1)
1935 for i in range(3):
1936 for txformer in err_txformers:
1937 err_val = list(base)
1938 err_val[i] = txformer(err_val[i])
1939 isocals.append(tuple(err_val))
1940
1941 for isocal in isocals:
1942 with self.subTest(isocal=isocal):
1943 with self.assertRaises(TypeError):
1944 self.theclass.fromisocalendar(*isocal)
1945
1946
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001947#############################################################################
1948# datetime tests
1949
1950class SubclassDatetime(datetime):
1951 sub_var = 1
1952
1953class TestDateTime(TestDate):
1954
1955 theclass = datetime
1956
1957 def test_basic_attributes(self):
1958 dt = self.theclass(2002, 3, 1, 12, 0)
1959 self.assertEqual(dt.year, 2002)
1960 self.assertEqual(dt.month, 3)
1961 self.assertEqual(dt.day, 1)
1962 self.assertEqual(dt.hour, 12)
1963 self.assertEqual(dt.minute, 0)
1964 self.assertEqual(dt.second, 0)
1965 self.assertEqual(dt.microsecond, 0)
1966
1967 def test_basic_attributes_nonzero(self):
1968 # Make sure all attributes are non-zero so bugs in
1969 # bit-shifting access show up.
1970 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1971 self.assertEqual(dt.year, 2002)
1972 self.assertEqual(dt.month, 3)
1973 self.assertEqual(dt.day, 1)
1974 self.assertEqual(dt.hour, 12)
1975 self.assertEqual(dt.minute, 59)
1976 self.assertEqual(dt.second, 59)
1977 self.assertEqual(dt.microsecond, 8000)
1978
1979 def test_roundtrip(self):
1980 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1981 self.theclass.now()):
1982 # Verify dt -> string -> datetime identity.
1983 s = repr(dt)
1984 self.assertTrue(s.startswith('datetime.'))
1985 s = s[9:]
1986 dt2 = eval(s)
1987 self.assertEqual(dt, dt2)
1988
1989 # Verify identity via reconstructing from pieces.
1990 dt2 = self.theclass(dt.year, dt.month, dt.day,
1991 dt.hour, dt.minute, dt.second,
1992 dt.microsecond)
1993 self.assertEqual(dt, dt2)
1994
1995 def test_isoformat(self):
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001996 t = self.theclass(1, 2, 3, 4, 5, 1, 123)
1997 self.assertEqual(t.isoformat(), "0001-02-03T04:05:01.000123")
1998 self.assertEqual(t.isoformat('T'), "0001-02-03T04:05:01.000123")
1999 self.assertEqual(t.isoformat(' '), "0001-02-03 04:05:01.000123")
2000 self.assertEqual(t.isoformat('\x00'), "0001-02-03\x0004:05:01.000123")
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03002001 # bpo-34482: Check that surrogates are handled properly.
2002 self.assertEqual(t.isoformat('\ud800'),
2003 "0001-02-03\ud80004:05:01.000123")
Alexander Belopolskya2998a62016-03-06 14:58:43 -05002004 self.assertEqual(t.isoformat(timespec='hours'), "0001-02-03T04")
2005 self.assertEqual(t.isoformat(timespec='minutes'), "0001-02-03T04:05")
2006 self.assertEqual(t.isoformat(timespec='seconds'), "0001-02-03T04:05:01")
2007 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
2008 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000123")
2009 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01.000123")
2010 self.assertEqual(t.isoformat(sep=' ', timespec='minutes'), "0001-02-03 04:05")
2011 self.assertRaises(ValueError, t.isoformat, timespec='foo')
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03002012 # bpo-34482: Check that surrogates are handled properly.
2013 self.assertRaises(ValueError, t.isoformat, timespec='\ud800')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002014 # str is ISO format with the separator forced to a blank.
Alexander Belopolskya2998a62016-03-06 14:58:43 -05002015 self.assertEqual(str(t), "0001-02-03 04:05:01.000123")
2016
2017 t = self.theclass(1, 2, 3, 4, 5, 1, 999500, tzinfo=timezone.utc)
2018 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999+00:00")
2019
2020 t = self.theclass(1, 2, 3, 4, 5, 1, 999500)
2021 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999")
2022
2023 t = self.theclass(1, 2, 3, 4, 5, 1)
2024 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01")
2025 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
2026 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000000")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002027
2028 t = self.theclass(2, 3, 2)
2029 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
2030 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
2031 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
2032 # str is ISO format with the separator forced to a blank.
2033 self.assertEqual(str(t), "0002-03-02 00:00:00")
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002034 # ISO format with timezone
2035 tz = FixedOffset(timedelta(seconds=16), 'XXX')
2036 t = self.theclass(2, 3, 2, tzinfo=tz)
2037 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00+00:00:16")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002038
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002039 def test_isoformat_timezone(self):
2040 tzoffsets = [
2041 ('05:00', timedelta(hours=5)),
2042 ('02:00', timedelta(hours=2)),
2043 ('06:27', timedelta(hours=6, minutes=27)),
2044 ('12:32:30', timedelta(hours=12, minutes=32, seconds=30)),
2045 ('02:04:09.123456', timedelta(hours=2, minutes=4, seconds=9, microseconds=123456))
2046 ]
2047
2048 tzinfos = [
2049 ('', None),
2050 ('+00:00', timezone.utc),
2051 ('+00:00', timezone(timedelta(0))),
2052 ]
2053
2054 tzinfos += [
2055 (prefix + expected, timezone(sign * td))
2056 for expected, td in tzoffsets
2057 for prefix, sign in [('-', -1), ('+', 1)]
2058 ]
2059
2060 dt_base = self.theclass(2016, 4, 1, 12, 37, 9)
2061 exp_base = '2016-04-01T12:37:09'
2062
2063 for exp_tz, tzi in tzinfos:
2064 dt = dt_base.replace(tzinfo=tzi)
2065 exp = exp_base + exp_tz
2066 with self.subTest(tzi=tzi):
2067 assert dt.isoformat() == exp
2068
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002069 def test_format(self):
2070 dt = self.theclass(2007, 9, 10, 4, 5, 1, 123)
2071 self.assertEqual(dt.__format__(''), str(dt))
2072
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02002073 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002074 dt.__format__(123)
2075
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002076 # check that a derived class's __str__() gets called
2077 class A(self.theclass):
2078 def __str__(self):
2079 return 'A'
2080 a = A(2007, 9, 10, 4, 5, 1, 123)
2081 self.assertEqual(a.__format__(''), 'A')
2082
2083 # check that a derived class's strftime gets called
2084 class B(self.theclass):
2085 def strftime(self, format_spec):
2086 return 'B'
2087 b = B(2007, 9, 10, 4, 5, 1, 123)
2088 self.assertEqual(b.__format__(''), str(dt))
2089
2090 for fmt in ["m:%m d:%d y:%y",
2091 "m:%m d:%d y:%y H:%H M:%M S:%S",
2092 "%z %Z",
2093 ]:
2094 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
2095 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
2096 self.assertEqual(b.__format__(fmt), 'B')
2097
2098 def test_more_ctime(self):
2099 # Test fields that TestDate doesn't touch.
2100 import time
2101
2102 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
2103 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
2104 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
2105 # out. The difference is that t.ctime() produces " 2" for the day,
2106 # but platform ctime() produces "02" for the day. According to
2107 # C99, t.ctime() is correct here.
2108 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
2109
2110 # So test a case where that difference doesn't matter.
2111 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
2112 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
2113
2114 def test_tz_independent_comparing(self):
2115 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
2116 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
2117 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
2118 self.assertEqual(dt1, dt3)
2119 self.assertTrue(dt2 > dt3)
2120
2121 # Make sure comparison doesn't forget microseconds, and isn't done
2122 # via comparing a float timestamp (an IEEE double doesn't have enough
Leo Ariasc3d95082018-02-03 18:36:10 -06002123 # precision to span microsecond resolution across years 1 through 9999,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002124 # so comparing via timestamp necessarily calls some distinct values
2125 # equal).
2126 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
2127 us = timedelta(microseconds=1)
2128 dt2 = dt1 + us
2129 self.assertEqual(dt2 - dt1, us)
2130 self.assertTrue(dt1 < dt2)
2131
2132 def test_strftime_with_bad_tzname_replace(self):
2133 # verify ok if tzinfo.tzname().replace() returns a non-string
2134 class MyTzInfo(FixedOffset):
2135 def tzname(self, dt):
2136 class MyStr(str):
2137 def replace(self, *args):
2138 return None
2139 return MyStr('name')
2140 t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
2141 self.assertRaises(TypeError, t.strftime, '%Z')
2142
2143 def test_bad_constructor_arguments(self):
2144 # bad years
2145 self.theclass(MINYEAR, 1, 1) # no exception
2146 self.theclass(MAXYEAR, 1, 1) # no exception
2147 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
2148 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
2149 # bad months
2150 self.theclass(2000, 1, 1) # no exception
2151 self.theclass(2000, 12, 1) # no exception
2152 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
2153 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
2154 # bad days
2155 self.theclass(2000, 2, 29) # no exception
2156 self.theclass(2004, 2, 29) # no exception
2157 self.theclass(2400, 2, 29) # no exception
2158 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
2159 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
2160 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
2161 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
2162 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
2163 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
2164 # bad hours
2165 self.theclass(2000, 1, 31, 0) # no exception
2166 self.theclass(2000, 1, 31, 23) # no exception
2167 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
2168 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
2169 # bad minutes
2170 self.theclass(2000, 1, 31, 23, 0) # no exception
2171 self.theclass(2000, 1, 31, 23, 59) # no exception
2172 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
2173 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
2174 # bad seconds
2175 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
2176 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
2177 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
2178 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
2179 # bad microseconds
2180 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
2181 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
2182 self.assertRaises(ValueError, self.theclass,
2183 2000, 1, 31, 23, 59, 59, -1)
2184 self.assertRaises(ValueError, self.theclass,
2185 2000, 1, 31, 23, 59, 59,
2186 1000000)
Alexander Belopolsky47649ab2016-08-08 17:05:40 -04002187 # bad fold
2188 self.assertRaises(ValueError, self.theclass,
2189 2000, 1, 31, fold=-1)
2190 self.assertRaises(ValueError, self.theclass,
2191 2000, 1, 31, fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002192 # Positional fold:
2193 self.assertRaises(TypeError, self.theclass,
2194 2000, 1, 31, 23, 59, 59, 0, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04002195
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002196 def test_hash_equality(self):
2197 d = self.theclass(2000, 12, 31, 23, 30, 17)
2198 e = self.theclass(2000, 12, 31, 23, 30, 17)
2199 self.assertEqual(d, e)
2200 self.assertEqual(hash(d), hash(e))
2201
2202 dic = {d: 1}
2203 dic[e] = 2
2204 self.assertEqual(len(dic), 1)
2205 self.assertEqual(dic[d], 2)
2206 self.assertEqual(dic[e], 2)
2207
2208 d = self.theclass(2001, 1, 1, 0, 5, 17)
2209 e = self.theclass(2001, 1, 1, 0, 5, 17)
2210 self.assertEqual(d, e)
2211 self.assertEqual(hash(d), hash(e))
2212
2213 dic = {d: 1}
2214 dic[e] = 2
2215 self.assertEqual(len(dic), 1)
2216 self.assertEqual(dic[d], 2)
2217 self.assertEqual(dic[e], 2)
2218
2219 def test_computations(self):
2220 a = self.theclass(2002, 1, 31)
2221 b = self.theclass(1956, 1, 31)
2222 diff = a-b
2223 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
2224 self.assertEqual(diff.seconds, 0)
2225 self.assertEqual(diff.microseconds, 0)
2226 a = self.theclass(2002, 3, 2, 17, 6)
2227 millisec = timedelta(0, 0, 1000)
2228 hour = timedelta(0, 3600)
2229 day = timedelta(1)
2230 week = timedelta(7)
2231 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
2232 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
2233 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
2234 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
2235 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
2236 self.assertEqual(a - hour, a + -hour)
2237 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
2238 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
2239 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
2240 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
2241 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
2242 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
2243 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
2244 self.assertEqual((a + week) - a, week)
2245 self.assertEqual((a + day) - a, day)
2246 self.assertEqual((a + hour) - a, hour)
2247 self.assertEqual((a + millisec) - a, millisec)
2248 self.assertEqual((a - week) - a, -week)
2249 self.assertEqual((a - day) - a, -day)
2250 self.assertEqual((a - hour) - a, -hour)
2251 self.assertEqual((a - millisec) - a, -millisec)
2252 self.assertEqual(a - (a + week), -week)
2253 self.assertEqual(a - (a + day), -day)
2254 self.assertEqual(a - (a + hour), -hour)
2255 self.assertEqual(a - (a + millisec), -millisec)
2256 self.assertEqual(a - (a - week), week)
2257 self.assertEqual(a - (a - day), day)
2258 self.assertEqual(a - (a - hour), hour)
2259 self.assertEqual(a - (a - millisec), millisec)
2260 self.assertEqual(a + (week + day + hour + millisec),
2261 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
2262 self.assertEqual(a + (week + day + hour + millisec),
2263 (((a + week) + day) + hour) + millisec)
2264 self.assertEqual(a - (week + day + hour + millisec),
2265 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
2266 self.assertEqual(a - (week + day + hour + millisec),
2267 (((a - week) - day) - hour) - millisec)
2268 # Add/sub ints or floats should be illegal
2269 for i in 1, 1.0:
2270 self.assertRaises(TypeError, lambda: a+i)
2271 self.assertRaises(TypeError, lambda: a-i)
2272 self.assertRaises(TypeError, lambda: i+a)
2273 self.assertRaises(TypeError, lambda: i-a)
2274
2275 # delta - datetime is senseless.
2276 self.assertRaises(TypeError, lambda: day - a)
2277 # mixing datetime and (delta or datetime) via * or // is senseless
2278 self.assertRaises(TypeError, lambda: day * a)
2279 self.assertRaises(TypeError, lambda: a * day)
2280 self.assertRaises(TypeError, lambda: day // a)
2281 self.assertRaises(TypeError, lambda: a // day)
2282 self.assertRaises(TypeError, lambda: a * a)
2283 self.assertRaises(TypeError, lambda: a // a)
2284 # datetime + datetime is senseless
2285 self.assertRaises(TypeError, lambda: a + a)
2286
2287 def test_pickling(self):
2288 args = 6, 7, 23, 20, 59, 1, 64**2
2289 orig = self.theclass(*args)
2290 for pickler, unpickler, proto in pickle_choices:
2291 green = pickler.dumps(orig, proto)
2292 derived = unpickler.loads(green)
2293 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02002294 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002295
2296 def test_more_pickling(self):
2297 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
Serhiy Storchakabad12572014-12-15 14:03:42 +02002298 for proto in range(pickle.HIGHEST_PROTOCOL + 1):
2299 s = pickle.dumps(a, proto)
2300 b = pickle.loads(s)
2301 self.assertEqual(b.year, 2003)
2302 self.assertEqual(b.month, 2)
2303 self.assertEqual(b.day, 7)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002304
2305 def test_pickling_subclass_datetime(self):
2306 args = 6, 7, 23, 20, 59, 1, 64**2
2307 orig = SubclassDatetime(*args)
2308 for pickler, unpickler, proto in pickle_choices:
2309 green = pickler.dumps(orig, proto)
2310 derived = unpickler.loads(green)
2311 self.assertEqual(orig, derived)
scaramallionc304c9a2020-10-19 01:49:48 +11002312 self.assertTrue(isinstance(derived, SubclassDatetime))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002313
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02002314 def test_compat_unpickle(self):
2315 tests = [
2316 b'cdatetime\ndatetime\n('
2317 b"S'\\x07\\xdf\\x0b\\x1b\\x14;\\x01\\x00\\x10\\x00'\ntR.",
2318
2319 b'cdatetime\ndatetime\n('
2320 b'U\n\x07\xdf\x0b\x1b\x14;\x01\x00\x10\x00tR.',
2321
2322 b'\x80\x02cdatetime\ndatetime\n'
2323 b'U\n\x07\xdf\x0b\x1b\x14;\x01\x00\x10\x00\x85R.',
2324 ]
2325 args = 2015, 11, 27, 20, 59, 1, 64**2
2326 expected = self.theclass(*args)
2327 for data in tests:
2328 for loads in pickle_loads:
2329 derived = loads(data, encoding='latin1')
2330 self.assertEqual(derived, expected)
2331
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002332 def test_more_compare(self):
2333 # The test_compare() inherited from TestDate covers the error cases.
2334 # We just want to test lexicographic ordering on the members datetime
2335 # has that date lacks.
2336 args = [2000, 11, 29, 20, 58, 16, 999998]
2337 t1 = self.theclass(*args)
2338 t2 = self.theclass(*args)
2339 self.assertEqual(t1, t2)
2340 self.assertTrue(t1 <= t2)
2341 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002342 self.assertFalse(t1 != t2)
2343 self.assertFalse(t1 < t2)
2344 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002345
2346 for i in range(len(args)):
2347 newargs = args[:]
2348 newargs[i] = args[i] + 1
2349 t2 = self.theclass(*newargs) # this is larger than t1
2350 self.assertTrue(t1 < t2)
2351 self.assertTrue(t2 > t1)
2352 self.assertTrue(t1 <= t2)
2353 self.assertTrue(t2 >= t1)
2354 self.assertTrue(t1 != t2)
2355 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002356 self.assertFalse(t1 == t2)
2357 self.assertFalse(t2 == t1)
2358 self.assertFalse(t1 > t2)
2359 self.assertFalse(t2 < t1)
2360 self.assertFalse(t1 >= t2)
2361 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002362
2363
2364 # A helper for timestamp constructor tests.
2365 def verify_field_equality(self, expected, got):
2366 self.assertEqual(expected.tm_year, got.year)
2367 self.assertEqual(expected.tm_mon, got.month)
2368 self.assertEqual(expected.tm_mday, got.day)
2369 self.assertEqual(expected.tm_hour, got.hour)
2370 self.assertEqual(expected.tm_min, got.minute)
2371 self.assertEqual(expected.tm_sec, got.second)
2372
2373 def test_fromtimestamp(self):
2374 import time
2375
2376 ts = time.time()
2377 expected = time.localtime(ts)
2378 got = self.theclass.fromtimestamp(ts)
2379 self.verify_field_equality(expected, got)
2380
2381 def test_utcfromtimestamp(self):
2382 import time
2383
2384 ts = time.time()
2385 expected = time.gmtime(ts)
2386 got = self.theclass.utcfromtimestamp(ts)
2387 self.verify_field_equality(expected, got)
2388
Alexander Belopolskya4415142012-06-08 12:33:09 -04002389 # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in
2390 # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0).
2391 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
2392 def test_timestamp_naive(self):
2393 t = self.theclass(1970, 1, 1)
2394 self.assertEqual(t.timestamp(), 18000.0)
2395 t = self.theclass(1970, 1, 1, 1, 2, 3, 4)
2396 self.assertEqual(t.timestamp(),
2397 18000.0 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002398 # Missing hour
2399 t0 = self.theclass(2012, 3, 11, 2, 30)
2400 t1 = t0.replace(fold=1)
2401 self.assertEqual(self.theclass.fromtimestamp(t1.timestamp()),
2402 t0 - timedelta(hours=1))
2403 self.assertEqual(self.theclass.fromtimestamp(t0.timestamp()),
2404 t1 + timedelta(hours=1))
Alexander Belopolskya4415142012-06-08 12:33:09 -04002405 # Ambiguous hour defaults to DST
2406 t = self.theclass(2012, 11, 4, 1, 30)
2407 self.assertEqual(self.theclass.fromtimestamp(t.timestamp()), t)
2408
2409 # Timestamp may raise an overflow error on some platforms
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002410 # XXX: Do we care to support the first and last year?
2411 for t in [self.theclass(2,1,1), self.theclass(9998,12,12)]:
Alexander Belopolskya4415142012-06-08 12:33:09 -04002412 try:
2413 s = t.timestamp()
2414 except OverflowError:
2415 pass
2416 else:
2417 self.assertEqual(self.theclass.fromtimestamp(s), t)
2418
2419 def test_timestamp_aware(self):
2420 t = self.theclass(1970, 1, 1, tzinfo=timezone.utc)
2421 self.assertEqual(t.timestamp(), 0.0)
2422 t = self.theclass(1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone.utc)
2423 self.assertEqual(t.timestamp(),
2424 3600 + 2*60 + 3 + 4*1e-6)
2425 t = self.theclass(1970, 1, 1, 1, 2, 3, 4,
2426 tzinfo=timezone(timedelta(hours=-5), 'EST'))
2427 self.assertEqual(t.timestamp(),
2428 18000 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002429
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002430 @support.run_with_tz('MSK-03') # Something east of Greenwich
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002431 def test_microsecond_rounding(self):
Victor Stinner5ebfe422015-09-18 14:52:15 +02002432 for fts in [self.theclass.fromtimestamp,
2433 self.theclass.utcfromtimestamp]:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002434 zero = fts(0)
2435 self.assertEqual(zero.second, 0)
2436 self.assertEqual(zero.microsecond, 0)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002437 one = fts(1e-6)
Victor Stinner8050ca92012-03-14 00:17:05 +01002438 try:
2439 minus_one = fts(-1e-6)
2440 except OSError:
2441 # localtime(-1) and gmtime(-1) is not supported on Windows
2442 pass
2443 else:
2444 self.assertEqual(minus_one.second, 59)
2445 self.assertEqual(minus_one.microsecond, 999999)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002446
Victor Stinner8050ca92012-03-14 00:17:05 +01002447 t = fts(-1e-8)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002448 self.assertEqual(t, zero)
Victor Stinner8050ca92012-03-14 00:17:05 +01002449 t = fts(-9e-7)
2450 self.assertEqual(t, minus_one)
2451 t = fts(-1e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002452 self.assertEqual(t, zero)
Victor Stinner8820a352015-09-05 10:50:20 +02002453 t = fts(-1/2**7)
2454 self.assertEqual(t.second, 59)
Victor Stinner7667f582015-09-09 01:02:23 +02002455 self.assertEqual(t.microsecond, 992188)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002456
2457 t = fts(1e-7)
2458 self.assertEqual(t, zero)
2459 t = fts(9e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002460 self.assertEqual(t, one)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002461 t = fts(0.99999949)
2462 self.assertEqual(t.second, 0)
2463 self.assertEqual(t.microsecond, 999999)
2464 t = fts(0.9999999)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002465 self.assertEqual(t.second, 1)
2466 self.assertEqual(t.microsecond, 0)
Victor Stinneradfefa52015-09-04 23:57:25 +02002467 t = fts(1/2**7)
2468 self.assertEqual(t.second, 0)
Victor Stinner7667f582015-09-09 01:02:23 +02002469 self.assertEqual(t.microsecond, 7812)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002470
Victor Stinnerb67f0962017-02-10 10:34:02 +01002471 def test_timestamp_limits(self):
2472 # minimum timestamp
2473 min_dt = self.theclass.min.replace(tzinfo=timezone.utc)
2474 min_ts = min_dt.timestamp()
Victor Stinner6f37e362017-02-10 11:45:14 +01002475 try:
2476 # date 0001-01-01 00:00:00+00:00: timestamp=-62135596800
2477 self.assertEqual(self.theclass.fromtimestamp(min_ts, tz=timezone.utc),
2478 min_dt)
Victor Stinner2a35c932017-02-10 12:37:21 +01002479 except (OverflowError, OSError) as exc:
2480 # the date 0001-01-01 doesn't fit into 32-bit time_t,
2481 # or platform doesn't support such very old date
Victor Stinner6f37e362017-02-10 11:45:14 +01002482 self.skipTest(str(exc))
Victor Stinnerb67f0962017-02-10 10:34:02 +01002483
2484 # maximum timestamp: set seconds to zero to avoid rounding issues
2485 max_dt = self.theclass.max.replace(tzinfo=timezone.utc,
2486 second=0, microsecond=0)
2487 max_ts = max_dt.timestamp()
2488 # date 9999-12-31 23:59:00+00:00: timestamp 253402300740
2489 self.assertEqual(self.theclass.fromtimestamp(max_ts, tz=timezone.utc),
2490 max_dt)
2491
2492 # number of seconds greater than 1 year: make sure that the new date
2493 # is not valid in datetime.datetime limits
2494 delta = 3600 * 24 * 400
2495
2496 # too small
2497 ts = min_ts - delta
2498 # converting a Python int to C time_t can raise a OverflowError,
2499 # especially on 32-bit platforms.
2500 with self.assertRaises((ValueError, OverflowError)):
2501 self.theclass.fromtimestamp(ts)
2502 with self.assertRaises((ValueError, OverflowError)):
2503 self.theclass.utcfromtimestamp(ts)
2504
2505 # too big
2506 ts = max_dt.timestamp() + delta
2507 with self.assertRaises((ValueError, OverflowError)):
2508 self.theclass.fromtimestamp(ts)
2509 with self.assertRaises((ValueError, OverflowError)):
2510 self.theclass.utcfromtimestamp(ts)
2511
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002512 def test_insane_fromtimestamp(self):
2513 # It's possible that some platform maps time_t to double,
2514 # and that this test will fail there. This test should
2515 # exempt such platforms (provided they return reasonable
2516 # results!).
2517 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002518 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002519 insane)
2520
2521 def test_insane_utcfromtimestamp(self):
2522 # It's possible that some platform maps time_t to double,
2523 # and that this test will fail there. This test should
2524 # exempt such platforms (provided they return reasonable
2525 # results!).
2526 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002527 self.assertRaises(OverflowError, self.theclass.utcfromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002528 insane)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002529
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002530 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2531 def test_negative_float_fromtimestamp(self):
2532 # The result is tz-dependent; at least test that this doesn't
2533 # fail (like it did before bug 1646728 was fixed).
2534 self.theclass.fromtimestamp(-1.05)
2535
2536 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2537 def test_negative_float_utcfromtimestamp(self):
2538 d = self.theclass.utcfromtimestamp(-1.05)
2539 self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
2540
2541 def test_utcnow(self):
2542 import time
2543
2544 # Call it a success if utcnow() and utcfromtimestamp() are within
2545 # a second of each other.
2546 tolerance = timedelta(seconds=1)
2547 for dummy in range(3):
2548 from_now = self.theclass.utcnow()
2549 from_timestamp = self.theclass.utcfromtimestamp(time.time())
2550 if abs(from_timestamp - from_now) <= tolerance:
2551 break
2552 # Else try again a few times.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002553 self.assertLessEqual(abs(from_timestamp - from_now), tolerance)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002554
2555 def test_strptime(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002556 string = '2004-12-01 13:02:47.197'
2557 format = '%Y-%m-%d %H:%M:%S.%f'
2558 expected = _strptime._strptime_datetime(self.theclass, string, format)
2559 got = self.theclass.strptime(string, format)
2560 self.assertEqual(expected, got)
2561 self.assertIs(type(expected), self.theclass)
2562 self.assertIs(type(got), self.theclass)
2563
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03002564 # bpo-34482: Check that surrogates are handled properly.
2565 inputs = [
2566 ('2004-12-01\ud80013:02:47.197', '%Y-%m-%d\ud800%H:%M:%S.%f'),
2567 ('2004\ud80012-01 13:02:47.197', '%Y\ud800%m-%d %H:%M:%S.%f'),
2568 ('2004-12-01 13:02\ud80047.197', '%Y-%m-%d %H:%M\ud800%S.%f'),
2569 ]
2570 for string, format in inputs:
2571 with self.subTest(string=string, format=format):
2572 expected = _strptime._strptime_datetime(self.theclass, string,
2573 format)
2574 got = self.theclass.strptime(string, format)
2575 self.assertEqual(expected, got)
2576
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002577 strptime = self.theclass.strptime
Mike Gleen6b9c2042019-06-18 19:14:57 +01002578
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002579 self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
2580 self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
Mario Corchero32318932017-10-26 01:35:41 +01002581 self.assertEqual(
2582 strptime("-00:02:01.000003", "%z").utcoffset(),
2583 -timedelta(minutes=2, seconds=1, microseconds=3)
2584 )
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002585 # Only local timezone and UTC are supported
2586 for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
2587 (-_time.timezone, _time.tzname[0])):
2588 if tzseconds < 0:
2589 sign = '-'
2590 seconds = -tzseconds
2591 else:
2592 sign ='+'
2593 seconds = tzseconds
2594 hours, minutes = divmod(seconds//60, 60)
2595 dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
Martin Panterfca22322015-11-16 09:22:19 +00002596 dt = strptime(dtstr, "%z %Z")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002597 self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
2598 self.assertEqual(dt.tzname(), tzname)
2599 # Can produce inconsistent datetime
2600 dtstr, fmt = "+1234 UTC", "%z %Z"
2601 dt = strptime(dtstr, fmt)
2602 self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE)
2603 self.assertEqual(dt.tzname(), 'UTC')
2604 # yet will roundtrip
2605 self.assertEqual(dt.strftime(fmt), dtstr)
2606
2607 # Produce naive datetime if no %z is provided
2608 self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
2609
2610 with self.assertRaises(ValueError): strptime("-2400", "%z")
2611 with self.assertRaises(ValueError): strptime("-000", "%z")
2612
Mike Gleen6b9c2042019-06-18 19:14:57 +01002613 def test_strptime_single_digit(self):
2614 # bpo-34903: Check that single digit dates and times are allowed.
2615
2616 strptime = self.theclass.strptime
2617
2618 with self.assertRaises(ValueError):
2619 # %y does require two digits.
2620 newdate = strptime('01/02/3 04:05:06', '%d/%m/%y %H:%M:%S')
2621 dt1 = self.theclass(2003, 2, 1, 4, 5, 6)
2622 dt2 = self.theclass(2003, 1, 2, 4, 5, 6)
2623 dt3 = self.theclass(2003, 2, 1, 0, 0, 0)
2624 dt4 = self.theclass(2003, 1, 25, 0, 0, 0)
2625 inputs = [
2626 ('%d', '1/02/03 4:5:6', '%d/%m/%y %H:%M:%S', dt1),
2627 ('%m', '01/2/03 4:5:6', '%d/%m/%y %H:%M:%S', dt1),
2628 ('%H', '01/02/03 4:05:06', '%d/%m/%y %H:%M:%S', dt1),
2629 ('%M', '01/02/03 04:5:06', '%d/%m/%y %H:%M:%S', dt1),
2630 ('%S', '01/02/03 04:05:6', '%d/%m/%y %H:%M:%S', dt1),
2631 ('%j', '2/03 04am:05:06', '%j/%y %I%p:%M:%S',dt2),
2632 ('%I', '02/03 4am:05:06', '%j/%y %I%p:%M:%S',dt2),
2633 ('%w', '6/04/03', '%w/%U/%y', dt3),
2634 # %u requires a single digit.
2635 ('%W', '6/4/2003', '%u/%W/%Y', dt3),
2636 ('%V', '6/4/2003', '%u/%V/%G', dt4),
2637 ]
2638 for reason, string, format, target in inputs:
2639 reason = 'test single digit ' + reason
2640 with self.subTest(reason=reason,
2641 string=string,
2642 format=format,
2643 target=target):
2644 newdate = strptime(string, format)
2645 self.assertEqual(newdate, target, msg=reason)
2646
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002647 def test_more_timetuple(self):
2648 # This tests fields beyond those tested by the TestDate.test_timetuple.
2649 t = self.theclass(2004, 12, 31, 6, 22, 33)
2650 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
2651 self.assertEqual(t.timetuple(),
2652 (t.year, t.month, t.day,
2653 t.hour, t.minute, t.second,
2654 t.weekday(),
2655 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
2656 -1))
2657 tt = t.timetuple()
2658 self.assertEqual(tt.tm_year, t.year)
2659 self.assertEqual(tt.tm_mon, t.month)
2660 self.assertEqual(tt.tm_mday, t.day)
2661 self.assertEqual(tt.tm_hour, t.hour)
2662 self.assertEqual(tt.tm_min, t.minute)
2663 self.assertEqual(tt.tm_sec, t.second)
2664 self.assertEqual(tt.tm_wday, t.weekday())
2665 self.assertEqual(tt.tm_yday, t.toordinal() -
2666 date(t.year, 1, 1).toordinal() + 1)
2667 self.assertEqual(tt.tm_isdst, -1)
2668
2669 def test_more_strftime(self):
2670 # This tests fields beyond those tested by the TestDate.test_strftime.
2671 t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
2672 self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
2673 "12 31 04 000047 33 22 06 366")
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04002674 for (s, us), z in [((33, 123), "33.000123"), ((33, 0), "33"),]:
2675 tz = timezone(-timedelta(hours=2, seconds=s, microseconds=us))
2676 t = t.replace(tzinfo=tz)
2677 self.assertEqual(t.strftime("%z"), "-0200" + z)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002678
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03002679 # bpo-34482: Check that surrogates don't cause a crash.
2680 try:
2681 t.strftime('%y\ud800%m %H\ud800%M')
2682 except UnicodeEncodeError:
2683 pass
2684
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002685 def test_extract(self):
2686 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2687 self.assertEqual(dt.date(), date(2002, 3, 4))
2688 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2689
2690 def test_combine(self):
2691 d = date(2002, 3, 4)
2692 t = time(18, 45, 3, 1234)
2693 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2694 combine = self.theclass.combine
2695 dt = combine(d, t)
2696 self.assertEqual(dt, expected)
2697
2698 dt = combine(time=t, date=d)
2699 self.assertEqual(dt, expected)
2700
2701 self.assertEqual(d, dt.date())
2702 self.assertEqual(t, dt.time())
2703 self.assertEqual(dt, combine(dt.date(), dt.time()))
2704
2705 self.assertRaises(TypeError, combine) # need an arg
2706 self.assertRaises(TypeError, combine, d) # need two args
2707 self.assertRaises(TypeError, combine, t, d) # args reversed
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002708 self.assertRaises(TypeError, combine, d, t, 1) # wrong tzinfo type
2709 self.assertRaises(TypeError, combine, d, t, 1, 2) # too many args
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002710 self.assertRaises(TypeError, combine, "date", "time") # wrong types
2711 self.assertRaises(TypeError, combine, d, "time") # wrong type
2712 self.assertRaises(TypeError, combine, "date", t) # wrong type
2713
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002714 # tzinfo= argument
2715 dt = combine(d, t, timezone.utc)
2716 self.assertIs(dt.tzinfo, timezone.utc)
2717 dt = combine(d, t, tzinfo=timezone.utc)
2718 self.assertIs(dt.tzinfo, timezone.utc)
2719 t = time()
2720 dt = combine(dt, t)
2721 self.assertEqual(dt.date(), d)
2722 self.assertEqual(dt.time(), t)
2723
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002724 def test_replace(self):
2725 cls = self.theclass
2726 args = [1, 2, 3, 4, 5, 6, 7]
2727 base = cls(*args)
2728 self.assertEqual(base, base.replace())
2729
2730 i = 0
2731 for name, newval in (("year", 2),
2732 ("month", 3),
2733 ("day", 4),
2734 ("hour", 5),
2735 ("minute", 6),
2736 ("second", 7),
2737 ("microsecond", 8)):
2738 newargs = args[:]
2739 newargs[i] = newval
2740 expected = cls(*newargs)
2741 got = base.replace(**{name: newval})
2742 self.assertEqual(expected, got)
2743 i += 1
2744
2745 # Out of bounds.
2746 base = cls(2000, 2, 29)
2747 self.assertRaises(ValueError, base.replace, year=2001)
2748
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002749 @support.run_with_tz('EDT4')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002750 def test_astimezone(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002751 dt = self.theclass.now()
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002752 f = FixedOffset(44, "0044")
2753 dt_utc = dt.replace(tzinfo=timezone(timedelta(hours=-4), 'EDT'))
2754 self.assertEqual(dt.astimezone(), dt_utc) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002755 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
2756 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002757 dt_f = dt.replace(tzinfo=f) + timedelta(hours=4, minutes=44)
2758 self.assertEqual(dt.astimezone(f), dt_f) # naive
2759 self.assertEqual(dt.astimezone(tz=f), dt_f) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002760
2761 class Bogus(tzinfo):
2762 def utcoffset(self, dt): return None
2763 def dst(self, dt): return timedelta(0)
2764 bog = Bogus()
2765 self.assertRaises(ValueError, dt.astimezone, bog) # naive
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002766 self.assertEqual(dt.replace(tzinfo=bog).astimezone(f), dt_f)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002767
2768 class AlsoBogus(tzinfo):
2769 def utcoffset(self, dt): return timedelta(0)
2770 def dst(self, dt): return None
2771 alsobog = AlsoBogus()
2772 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
2773
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002774 class Broken(tzinfo):
2775 def utcoffset(self, dt): return 1
2776 def dst(self, dt): return 1
2777 broken = Broken()
2778 dt_broken = dt.replace(tzinfo=broken)
2779 with self.assertRaises(TypeError):
2780 dt_broken.astimezone()
2781
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002782 def test_subclass_datetime(self):
2783
2784 class C(self.theclass):
2785 theAnswer = 42
2786
2787 def __new__(cls, *args, **kws):
2788 temp = kws.copy()
2789 extra = temp.pop('extra')
2790 result = self.theclass.__new__(cls, *args, **temp)
2791 result.extra = extra
2792 return result
2793
2794 def newmeth(self, start):
2795 return start + self.year + self.month + self.second
2796
2797 args = 2003, 4, 14, 12, 13, 41
2798
2799 dt1 = self.theclass(*args)
2800 dt2 = C(*args, **{'extra': 7})
2801
2802 self.assertEqual(dt2.__class__, C)
2803 self.assertEqual(dt2.theAnswer, 42)
2804 self.assertEqual(dt2.extra, 7)
2805 self.assertEqual(dt1.toordinal(), dt2.toordinal())
2806 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
2807 dt1.second - 7)
2808
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002809 def test_subclass_alternate_constructors_datetime(self):
2810 # Test that alternate constructors call the constructor
2811 class DateTimeSubclass(self.theclass):
2812 def __new__(cls, *args, **kwargs):
2813 result = self.theclass.__new__(cls, *args, **kwargs)
2814 result.extra = 7
2815
2816 return result
2817
2818 args = (2003, 4, 14, 12, 30, 15, 123456)
2819 d_isoformat = '2003-04-14T12:30:15.123456' # Equivalent isoformat()
2820 utc_ts = 1050323415.123456 # UTC timestamp
2821
2822 base_d = DateTimeSubclass(*args)
2823 self.assertIsInstance(base_d, DateTimeSubclass)
2824 self.assertEqual(base_d.extra, 7)
2825
2826 # Timestamp depends on time zone, so we'll calculate the equivalent here
2827 ts = base_d.timestamp()
2828
2829 test_cases = [
Paul Ganssle89427cd2019-02-04 14:42:04 -05002830 ('fromtimestamp', (ts,), base_d),
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002831 # See https://bugs.python.org/issue32417
Paul Ganssle89427cd2019-02-04 14:42:04 -05002832 ('fromtimestamp', (ts, timezone.utc),
2833 base_d.astimezone(timezone.utc)),
2834 ('utcfromtimestamp', (utc_ts,), base_d),
2835 ('fromisoformat', (d_isoformat,), base_d),
2836 ('strptime', (d_isoformat, '%Y-%m-%dT%H:%M:%S.%f'), base_d),
2837 ('combine', (date(*args[0:3]), time(*args[3:])), base_d),
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002838 ]
2839
Paul Ganssle89427cd2019-02-04 14:42:04 -05002840 for constr_name, constr_args, expected in test_cases:
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002841 for base_obj in (DateTimeSubclass, base_d):
2842 # Test both the classmethod and method
2843 with self.subTest(base_obj_type=type(base_obj),
2844 constr_name=constr_name):
Paul Ganssle89427cd2019-02-04 14:42:04 -05002845 constructor = getattr(base_obj, constr_name)
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002846
Paul Ganssle89427cd2019-02-04 14:42:04 -05002847 dt = constructor(*constr_args)
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002848
2849 # Test that it creates the right subclass
2850 self.assertIsInstance(dt, DateTimeSubclass)
2851
2852 # Test that it's equal to the base object
Paul Ganssle89427cd2019-02-04 14:42:04 -05002853 self.assertEqual(dt, expected)
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002854
2855 # Test that it called the constructor
2856 self.assertEqual(dt.extra, 7)
2857
Paul Ganssle89427cd2019-02-04 14:42:04 -05002858 def test_subclass_now(self):
2859 # Test that alternate constructors call the constructor
2860 class DateTimeSubclass(self.theclass):
2861 def __new__(cls, *args, **kwargs):
2862 result = self.theclass.__new__(cls, *args, **kwargs)
2863 result.extra = 7
2864
2865 return result
2866
2867 test_cases = [
2868 ('now', 'now', {}),
2869 ('utcnow', 'utcnow', {}),
2870 ('now_utc', 'now', {'tz': timezone.utc}),
2871 ('now_fixed', 'now', {'tz': timezone(timedelta(hours=-5), "EST")}),
2872 ]
2873
2874 for name, meth_name, kwargs in test_cases:
2875 with self.subTest(name):
2876 constr = getattr(DateTimeSubclass, meth_name)
2877 dt = constr(**kwargs)
2878
2879 self.assertIsInstance(dt, DateTimeSubclass)
2880 self.assertEqual(dt.extra, 7)
2881
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002882 def test_fromisoformat_datetime(self):
2883 # Test that isoformat() is reversible
2884 base_dates = [
2885 (1, 1, 1),
2886 (1900, 1, 1),
2887 (2004, 11, 12),
2888 (2017, 5, 30)
2889 ]
2890
2891 base_times = [
2892 (0, 0, 0, 0),
2893 (0, 0, 0, 241000),
2894 (0, 0, 0, 234567),
2895 (12, 30, 45, 234567)
2896 ]
2897
2898 separators = [' ', 'T']
2899
2900 tzinfos = [None, timezone.utc,
2901 timezone(timedelta(hours=-5)),
2902 timezone(timedelta(hours=2))]
2903
2904 dts = [self.theclass(*date_tuple, *time_tuple, tzinfo=tzi)
2905 for date_tuple in base_dates
2906 for time_tuple in base_times
2907 for tzi in tzinfos]
2908
2909 for dt in dts:
2910 for sep in separators:
2911 dtstr = dt.isoformat(sep=sep)
2912
2913 with self.subTest(dtstr=dtstr):
2914 dt_rt = self.theclass.fromisoformat(dtstr)
2915 self.assertEqual(dt, dt_rt)
2916
2917 def test_fromisoformat_timezone(self):
2918 base_dt = self.theclass(2014, 12, 30, 12, 30, 45, 217456)
2919
2920 tzoffsets = [
2921 timedelta(hours=5), timedelta(hours=2),
2922 timedelta(hours=6, minutes=27),
2923 timedelta(hours=12, minutes=32, seconds=30),
2924 timedelta(hours=2, minutes=4, seconds=9, microseconds=123456)
2925 ]
2926
2927 tzoffsets += [-1 * td for td in tzoffsets]
2928
2929 tzinfos = [None, timezone.utc,
2930 timezone(timedelta(hours=0))]
2931
2932 tzinfos += [timezone(td) for td in tzoffsets]
2933
2934 for tzi in tzinfos:
2935 dt = base_dt.replace(tzinfo=tzi)
2936 dtstr = dt.isoformat()
2937
2938 with self.subTest(tstr=dtstr):
2939 dt_rt = self.theclass.fromisoformat(dtstr)
2940 assert dt == dt_rt, dt_rt
2941
2942 def test_fromisoformat_separators(self):
2943 separators = [
2944 ' ', 'T', '\u007f', # 1-bit widths
2945 '\u0080', 'ʁ', # 2-bit widths
2946 'ᛇ', '時', # 3-bit widths
Paul Ganssle096329f2018-08-23 11:06:20 -04002947 '🐍', # 4-bit widths
2948 '\ud800', # bpo-34454: Surrogate code point
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002949 ]
2950
2951 for sep in separators:
2952 dt = self.theclass(2018, 1, 31, 23, 59, 47, 124789)
2953 dtstr = dt.isoformat(sep=sep)
2954
2955 with self.subTest(dtstr=dtstr):
2956 dt_rt = self.theclass.fromisoformat(dtstr)
2957 self.assertEqual(dt, dt_rt)
2958
2959 def test_fromisoformat_ambiguous(self):
2960 # Test strings like 2018-01-31+12:15 (where +12:15 is not a time zone)
2961 separators = ['+', '-']
2962 for sep in separators:
2963 dt = self.theclass(2018, 1, 31, 12, 15)
2964 dtstr = dt.isoformat(sep=sep)
2965
2966 with self.subTest(dtstr=dtstr):
2967 dt_rt = self.theclass.fromisoformat(dtstr)
2968 self.assertEqual(dt, dt_rt)
2969
2970 def test_fromisoformat_timespecs(self):
2971 datetime_bases = [
2972 (2009, 12, 4, 8, 17, 45, 123456),
2973 (2009, 12, 4, 8, 17, 45, 0)]
2974
2975 tzinfos = [None, timezone.utc,
2976 timezone(timedelta(hours=-5)),
2977 timezone(timedelta(hours=2)),
2978 timezone(timedelta(hours=6, minutes=27))]
2979
2980 timespecs = ['hours', 'minutes', 'seconds',
2981 'milliseconds', 'microseconds']
2982
2983 for ip, ts in enumerate(timespecs):
2984 for tzi in tzinfos:
2985 for dt_tuple in datetime_bases:
2986 if ts == 'milliseconds':
2987 new_microseconds = 1000 * (dt_tuple[6] // 1000)
2988 dt_tuple = dt_tuple[0:6] + (new_microseconds,)
2989
2990 dt = self.theclass(*(dt_tuple[0:(4 + ip)]), tzinfo=tzi)
2991 dtstr = dt.isoformat(timespec=ts)
2992 with self.subTest(dtstr=dtstr):
2993 dt_rt = self.theclass.fromisoformat(dtstr)
2994 self.assertEqual(dt, dt_rt)
2995
2996 def test_fromisoformat_fails_datetime(self):
2997 # Test that fromisoformat() fails on invalid values
2998 bad_strs = [
2999 '', # Empty string
Paul Ganssle096329f2018-08-23 11:06:20 -04003000 '\ud800', # bpo-34454: Surrogate code point
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003001 '2009.04-19T03', # Wrong first separator
3002 '2009-04.19T03', # Wrong second separator
3003 '2009-04-19T0a', # Invalid hours
3004 '2009-04-19T03:1a:45', # Invalid minutes
3005 '2009-04-19T03:15:4a', # Invalid seconds
3006 '2009-04-19T03;15:45', # Bad first time separator
3007 '2009-04-19T03:15;45', # Bad second time separator
3008 '2009-04-19T03:15:4500:00', # Bad time zone separator
3009 '2009-04-19T03:15:45.2345', # Too many digits for milliseconds
3010 '2009-04-19T03:15:45.1234567', # Too many digits for microseconds
3011 '2009-04-19T03:15:45.123456+24:30', # Invalid time zone offset
3012 '2009-04-19T03:15:45.123456-24:30', # Invalid negative offset
3013 '2009-04-10ᛇᛇᛇᛇᛇ12:15', # Too many unicode separators
Paul Ganssle096329f2018-08-23 11:06:20 -04003014 '2009-04\ud80010T12:15', # Surrogate char in date
3015 '2009-04-10T12\ud80015', # Surrogate char in time
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003016 '2009-04-19T1', # Incomplete hours
3017 '2009-04-19T12:3', # Incomplete minutes
3018 '2009-04-19T12:30:4', # Incomplete seconds
3019 '2009-04-19T12:', # Ends with time separator
3020 '2009-04-19T12:30:', # Ends with time separator
3021 '2009-04-19T12:30:45.', # Ends with time separator
3022 '2009-04-19T12:30:45.123456+', # Ends with timzone separator
3023 '2009-04-19T12:30:45.123456-', # Ends with timzone separator
3024 '2009-04-19T12:30:45.123456-05:00a', # Extra text
3025 '2009-04-19T12:30:45.123-05:00a', # Extra text
3026 '2009-04-19T12:30:45-05:00a', # Extra text
3027 ]
3028
3029 for bad_str in bad_strs:
3030 with self.subTest(bad_str=bad_str):
3031 with self.assertRaises(ValueError):
3032 self.theclass.fromisoformat(bad_str)
3033
Paul Ganssle3df85402018-10-22 12:32:52 -04003034 def test_fromisoformat_fails_surrogate(self):
3035 # Test that when fromisoformat() fails with a surrogate character as
3036 # the separator, the error message contains the original string
3037 dtstr = "2018-01-03\ud80001:0113"
3038
3039 with self.assertRaisesRegex(ValueError, re.escape(repr(dtstr))):
3040 self.theclass.fromisoformat(dtstr)
3041
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003042 def test_fromisoformat_utc(self):
3043 dt_str = '2014-04-19T13:21:13+00:00'
3044 dt = self.theclass.fromisoformat(dt_str)
3045
3046 self.assertIs(dt.tzinfo, timezone.utc)
3047
3048 def test_fromisoformat_subclass(self):
3049 class DateTimeSubclass(self.theclass):
3050 pass
3051
3052 dt = DateTimeSubclass(2014, 12, 14, 9, 30, 45, 457390,
3053 tzinfo=timezone(timedelta(hours=10, minutes=45)))
3054
3055 dt_rt = DateTimeSubclass.fromisoformat(dt.isoformat())
3056
3057 self.assertEqual(dt, dt_rt)
3058 self.assertIsInstance(dt_rt, DateTimeSubclass)
3059
3060
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003061class TestSubclassDateTime(TestDateTime):
3062 theclass = SubclassDatetime
3063 # Override tests not designed for subclass
Zachary Ware9fe6d862013-12-08 00:20:35 -06003064 @unittest.skip('not appropriate for subclasses')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003065 def test_roundtrip(self):
3066 pass
3067
3068class SubclassTime(time):
3069 sub_var = 1
3070
3071class TestTime(HarmlessMixedComparison, unittest.TestCase):
3072
3073 theclass = time
3074
3075 def test_basic_attributes(self):
3076 t = self.theclass(12, 0)
3077 self.assertEqual(t.hour, 12)
3078 self.assertEqual(t.minute, 0)
3079 self.assertEqual(t.second, 0)
3080 self.assertEqual(t.microsecond, 0)
3081
3082 def test_basic_attributes_nonzero(self):
3083 # Make sure all attributes are non-zero so bugs in
3084 # bit-shifting access show up.
3085 t = self.theclass(12, 59, 59, 8000)
3086 self.assertEqual(t.hour, 12)
3087 self.assertEqual(t.minute, 59)
3088 self.assertEqual(t.second, 59)
3089 self.assertEqual(t.microsecond, 8000)
3090
3091 def test_roundtrip(self):
3092 t = self.theclass(1, 2, 3, 4)
3093
3094 # Verify t -> string -> time identity.
3095 s = repr(t)
3096 self.assertTrue(s.startswith('datetime.'))
3097 s = s[9:]
3098 t2 = eval(s)
3099 self.assertEqual(t, t2)
3100
3101 # Verify identity via reconstructing from pieces.
3102 t2 = self.theclass(t.hour, t.minute, t.second,
3103 t.microsecond)
3104 self.assertEqual(t, t2)
3105
3106 def test_comparing(self):
3107 args = [1, 2, 3, 4]
3108 t1 = self.theclass(*args)
3109 t2 = self.theclass(*args)
3110 self.assertEqual(t1, t2)
3111 self.assertTrue(t1 <= t2)
3112 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003113 self.assertFalse(t1 != t2)
3114 self.assertFalse(t1 < t2)
3115 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003116
3117 for i in range(len(args)):
3118 newargs = args[:]
3119 newargs[i] = args[i] + 1
3120 t2 = self.theclass(*newargs) # this is larger than t1
3121 self.assertTrue(t1 < t2)
3122 self.assertTrue(t2 > t1)
3123 self.assertTrue(t1 <= t2)
3124 self.assertTrue(t2 >= t1)
3125 self.assertTrue(t1 != t2)
3126 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003127 self.assertFalse(t1 == t2)
3128 self.assertFalse(t2 == t1)
3129 self.assertFalse(t1 > t2)
3130 self.assertFalse(t2 < t1)
3131 self.assertFalse(t1 >= t2)
3132 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003133
3134 for badarg in OTHERSTUFF:
3135 self.assertEqual(t1 == badarg, False)
3136 self.assertEqual(t1 != badarg, True)
3137 self.assertEqual(badarg == t1, False)
3138 self.assertEqual(badarg != t1, True)
3139
3140 self.assertRaises(TypeError, lambda: t1 <= badarg)
3141 self.assertRaises(TypeError, lambda: t1 < badarg)
3142 self.assertRaises(TypeError, lambda: t1 > badarg)
3143 self.assertRaises(TypeError, lambda: t1 >= badarg)
3144 self.assertRaises(TypeError, lambda: badarg <= t1)
3145 self.assertRaises(TypeError, lambda: badarg < t1)
3146 self.assertRaises(TypeError, lambda: badarg > t1)
3147 self.assertRaises(TypeError, lambda: badarg >= t1)
3148
3149 def test_bad_constructor_arguments(self):
3150 # bad hours
3151 self.theclass(0, 0) # no exception
3152 self.theclass(23, 0) # no exception
3153 self.assertRaises(ValueError, self.theclass, -1, 0)
3154 self.assertRaises(ValueError, self.theclass, 24, 0)
3155 # bad minutes
3156 self.theclass(23, 0) # no exception
3157 self.theclass(23, 59) # no exception
3158 self.assertRaises(ValueError, self.theclass, 23, -1)
3159 self.assertRaises(ValueError, self.theclass, 23, 60)
3160 # bad seconds
3161 self.theclass(23, 59, 0) # no exception
3162 self.theclass(23, 59, 59) # no exception
3163 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
3164 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
3165 # bad microseconds
3166 self.theclass(23, 59, 59, 0) # no exception
3167 self.theclass(23, 59, 59, 999999) # no exception
3168 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
3169 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
3170
3171 def test_hash_equality(self):
3172 d = self.theclass(23, 30, 17)
3173 e = self.theclass(23, 30, 17)
3174 self.assertEqual(d, e)
3175 self.assertEqual(hash(d), hash(e))
3176
3177 dic = {d: 1}
3178 dic[e] = 2
3179 self.assertEqual(len(dic), 1)
3180 self.assertEqual(dic[d], 2)
3181 self.assertEqual(dic[e], 2)
3182
3183 d = self.theclass(0, 5, 17)
3184 e = self.theclass(0, 5, 17)
3185 self.assertEqual(d, e)
3186 self.assertEqual(hash(d), hash(e))
3187
3188 dic = {d: 1}
3189 dic[e] = 2
3190 self.assertEqual(len(dic), 1)
3191 self.assertEqual(dic[d], 2)
3192 self.assertEqual(dic[e], 2)
3193
3194 def test_isoformat(self):
3195 t = self.theclass(4, 5, 1, 123)
3196 self.assertEqual(t.isoformat(), "04:05:01.000123")
3197 self.assertEqual(t.isoformat(), str(t))
3198
3199 t = self.theclass()
3200 self.assertEqual(t.isoformat(), "00:00:00")
3201 self.assertEqual(t.isoformat(), str(t))
3202
3203 t = self.theclass(microsecond=1)
3204 self.assertEqual(t.isoformat(), "00:00:00.000001")
3205 self.assertEqual(t.isoformat(), str(t))
3206
3207 t = self.theclass(microsecond=10)
3208 self.assertEqual(t.isoformat(), "00:00:00.000010")
3209 self.assertEqual(t.isoformat(), str(t))
3210
3211 t = self.theclass(microsecond=100)
3212 self.assertEqual(t.isoformat(), "00:00:00.000100")
3213 self.assertEqual(t.isoformat(), str(t))
3214
3215 t = self.theclass(microsecond=1000)
3216 self.assertEqual(t.isoformat(), "00:00:00.001000")
3217 self.assertEqual(t.isoformat(), str(t))
3218
3219 t = self.theclass(microsecond=10000)
3220 self.assertEqual(t.isoformat(), "00:00:00.010000")
3221 self.assertEqual(t.isoformat(), str(t))
3222
3223 t = self.theclass(microsecond=100000)
3224 self.assertEqual(t.isoformat(), "00:00:00.100000")
3225 self.assertEqual(t.isoformat(), str(t))
3226
Alexander Belopolskya2998a62016-03-06 14:58:43 -05003227 t = self.theclass(hour=12, minute=34, second=56, microsecond=123456)
3228 self.assertEqual(t.isoformat(timespec='hours'), "12")
3229 self.assertEqual(t.isoformat(timespec='minutes'), "12:34")
3230 self.assertEqual(t.isoformat(timespec='seconds'), "12:34:56")
3231 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.123")
3232 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.123456")
3233 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56.123456")
3234 self.assertRaises(ValueError, t.isoformat, timespec='monkey')
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03003235 # bpo-34482: Check that surrogates are handled properly.
3236 self.assertRaises(ValueError, t.isoformat, timespec='\ud800')
Alexander Belopolskya2998a62016-03-06 14:58:43 -05003237
3238 t = self.theclass(hour=12, minute=34, second=56, microsecond=999500)
3239 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.999")
3240
3241 t = self.theclass(hour=12, minute=34, second=56, microsecond=0)
3242 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.000")
3243 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.000000")
3244 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56")
3245
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003246 def test_isoformat_timezone(self):
3247 tzoffsets = [
3248 ('05:00', timedelta(hours=5)),
3249 ('02:00', timedelta(hours=2)),
3250 ('06:27', timedelta(hours=6, minutes=27)),
3251 ('12:32:30', timedelta(hours=12, minutes=32, seconds=30)),
3252 ('02:04:09.123456', timedelta(hours=2, minutes=4, seconds=9, microseconds=123456))
3253 ]
3254
3255 tzinfos = [
3256 ('', None),
3257 ('+00:00', timezone.utc),
3258 ('+00:00', timezone(timedelta(0))),
3259 ]
3260
3261 tzinfos += [
3262 (prefix + expected, timezone(sign * td))
3263 for expected, td in tzoffsets
3264 for prefix, sign in [('-', -1), ('+', 1)]
3265 ]
3266
3267 t_base = self.theclass(12, 37, 9)
3268 exp_base = '12:37:09'
3269
3270 for exp_tz, tzi in tzinfos:
3271 t = t_base.replace(tzinfo=tzi)
3272 exp = exp_base + exp_tz
3273 with self.subTest(tzi=tzi):
3274 assert t.isoformat() == exp
3275
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003276 def test_1653736(self):
3277 # verify it doesn't accept extra keyword arguments
3278 t = self.theclass(second=1)
3279 self.assertRaises(TypeError, t.isoformat, foo=3)
3280
3281 def test_strftime(self):
3282 t = self.theclass(1, 2, 3, 4)
3283 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
3284 # A naive object replaces %z and %Z with empty strings.
3285 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
3286
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03003287 # bpo-34482: Check that surrogates don't cause a crash.
3288 try:
3289 t.strftime('%H\ud800%M')
3290 except UnicodeEncodeError:
3291 pass
3292
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003293 def test_format(self):
3294 t = self.theclass(1, 2, 3, 4)
3295 self.assertEqual(t.__format__(''), str(t))
3296
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02003297 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04003298 t.__format__(123)
3299
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003300 # check that a derived class's __str__() gets called
3301 class A(self.theclass):
3302 def __str__(self):
3303 return 'A'
3304 a = A(1, 2, 3, 4)
3305 self.assertEqual(a.__format__(''), 'A')
3306
3307 # check that a derived class's strftime gets called
3308 class B(self.theclass):
3309 def strftime(self, format_spec):
3310 return 'B'
3311 b = B(1, 2, 3, 4)
3312 self.assertEqual(b.__format__(''), str(t))
3313
3314 for fmt in ['%H %M %S',
3315 ]:
3316 self.assertEqual(t.__format__(fmt), t.strftime(fmt))
3317 self.assertEqual(a.__format__(fmt), t.strftime(fmt))
3318 self.assertEqual(b.__format__(fmt), 'B')
3319
3320 def test_str(self):
3321 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
3322 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
3323 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
3324 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
3325 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
3326
3327 def test_repr(self):
3328 name = 'datetime.' + self.theclass.__name__
3329 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
3330 "%s(1, 2, 3, 4)" % name)
3331 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
3332 "%s(10, 2, 3, 4000)" % name)
3333 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
3334 "%s(0, 2, 3, 400000)" % name)
3335 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
3336 "%s(12, 2, 3)" % name)
3337 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
3338 "%s(23, 15)" % name)
3339
3340 def test_resolution_info(self):
3341 self.assertIsInstance(self.theclass.min, self.theclass)
3342 self.assertIsInstance(self.theclass.max, self.theclass)
3343 self.assertIsInstance(self.theclass.resolution, timedelta)
3344 self.assertTrue(self.theclass.max > self.theclass.min)
3345
3346 def test_pickling(self):
3347 args = 20, 59, 16, 64**2
3348 orig = self.theclass(*args)
3349 for pickler, unpickler, proto in pickle_choices:
3350 green = pickler.dumps(orig, proto)
3351 derived = unpickler.loads(green)
3352 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003353 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003354
3355 def test_pickling_subclass_time(self):
3356 args = 20, 59, 16, 64**2
3357 orig = SubclassTime(*args)
3358 for pickler, unpickler, proto in pickle_choices:
3359 green = pickler.dumps(orig, proto)
3360 derived = unpickler.loads(green)
3361 self.assertEqual(orig, derived)
scaramallionc304c9a2020-10-19 01:49:48 +11003362 self.assertTrue(isinstance(derived, SubclassTime))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003363
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02003364 def test_compat_unpickle(self):
3365 tests = [
Justin Blanchard122376d2019-08-29 03:36:15 -04003366 (b"cdatetime\ntime\n(S'\\x14;\\x10\\x00\\x10\\x00'\ntR.",
3367 (20, 59, 16, 64**2)),
3368 (b'cdatetime\ntime\n(U\x06\x14;\x10\x00\x10\x00tR.',
3369 (20, 59, 16, 64**2)),
3370 (b'\x80\x02cdatetime\ntime\nU\x06\x14;\x10\x00\x10\x00\x85R.',
3371 (20, 59, 16, 64**2)),
3372 (b"cdatetime\ntime\n(S'\\x14;\\x19\\x00\\x10\\x00'\ntR.",
3373 (20, 59, 25, 64**2)),
3374 (b'cdatetime\ntime\n(U\x06\x14;\x19\x00\x10\x00tR.',
3375 (20, 59, 25, 64**2)),
3376 (b'\x80\x02cdatetime\ntime\nU\x06\x14;\x19\x00\x10\x00\x85R.',
3377 (20, 59, 25, 64**2)),
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02003378 ]
Justin Blanchard122376d2019-08-29 03:36:15 -04003379 for i, (data, args) in enumerate(tests):
3380 with self.subTest(i=i):
3381 expected = self.theclass(*args)
3382 for loads in pickle_loads:
3383 derived = loads(data, encoding='latin1')
3384 self.assertEqual(derived, expected)
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02003385
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003386 def test_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003387 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003388 cls = self.theclass
3389 self.assertTrue(cls(1))
3390 self.assertTrue(cls(0, 1))
3391 self.assertTrue(cls(0, 0, 1))
3392 self.assertTrue(cls(0, 0, 0, 1))
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003393 self.assertTrue(cls(0))
3394 self.assertTrue(cls())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003395
3396 def test_replace(self):
3397 cls = self.theclass
3398 args = [1, 2, 3, 4]
3399 base = cls(*args)
3400 self.assertEqual(base, base.replace())
3401
3402 i = 0
3403 for name, newval in (("hour", 5),
3404 ("minute", 6),
3405 ("second", 7),
3406 ("microsecond", 8)):
3407 newargs = args[:]
3408 newargs[i] = newval
3409 expected = cls(*newargs)
3410 got = base.replace(**{name: newval})
3411 self.assertEqual(expected, got)
3412 i += 1
3413
3414 # Out of bounds.
3415 base = cls(1)
3416 self.assertRaises(ValueError, base.replace, hour=24)
3417 self.assertRaises(ValueError, base.replace, minute=-1)
3418 self.assertRaises(ValueError, base.replace, second=100)
3419 self.assertRaises(ValueError, base.replace, microsecond=1000000)
3420
Paul Ganssle191e9932017-11-09 16:34:29 -05003421 def test_subclass_replace(self):
3422 class TimeSubclass(self.theclass):
3423 pass
3424
3425 ctime = TimeSubclass(12, 30)
3426 self.assertIs(type(ctime.replace(hour=10)), TimeSubclass)
3427
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003428 def test_subclass_time(self):
3429
3430 class C(self.theclass):
3431 theAnswer = 42
3432
3433 def __new__(cls, *args, **kws):
3434 temp = kws.copy()
3435 extra = temp.pop('extra')
3436 result = self.theclass.__new__(cls, *args, **temp)
3437 result.extra = extra
3438 return result
3439
3440 def newmeth(self, start):
3441 return start + self.hour + self.second
3442
3443 args = 4, 5, 6
3444
3445 dt1 = self.theclass(*args)
3446 dt2 = C(*args, **{'extra': 7})
3447
3448 self.assertEqual(dt2.__class__, C)
3449 self.assertEqual(dt2.theAnswer, 42)
3450 self.assertEqual(dt2.extra, 7)
3451 self.assertEqual(dt1.isoformat(), dt2.isoformat())
3452 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
3453
3454 def test_backdoor_resistance(self):
3455 # see TestDate.test_backdoor_resistance().
3456 base = '2:59.0'
3457 for hour_byte in ' ', '9', chr(24), '\xff':
3458 self.assertRaises(TypeError, self.theclass,
3459 hour_byte + base[1:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04003460 # Good bytes, but bad tzinfo:
3461 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
3462 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003463
3464# A mixin for classes with a tzinfo= argument. Subclasses must define
Martin Panter46f50722016-05-26 05:35:26 +00003465# theclass as a class attribute, and theclass(1, 1, 1, tzinfo=whatever)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003466# must be legit (which is true for time and datetime).
3467class TZInfoBase:
3468
3469 def test_argument_passing(self):
3470 cls = self.theclass
3471 # A datetime passes itself on, a time passes None.
3472 class introspective(tzinfo):
3473 def tzname(self, dt): return dt and "real" or "none"
3474 def utcoffset(self, dt):
3475 return timedelta(minutes = dt and 42 or -42)
3476 dst = utcoffset
3477
3478 obj = cls(1, 2, 3, tzinfo=introspective())
3479
3480 expected = cls is time and "none" or "real"
3481 self.assertEqual(obj.tzname(), expected)
3482
3483 expected = timedelta(minutes=(cls is time and -42 or 42))
3484 self.assertEqual(obj.utcoffset(), expected)
3485 self.assertEqual(obj.dst(), expected)
3486
3487 def test_bad_tzinfo_classes(self):
3488 cls = self.theclass
3489 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
3490
3491 class NiceTry(object):
3492 def __init__(self): pass
3493 def utcoffset(self, dt): pass
3494 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
3495
3496 class BetterTry(tzinfo):
3497 def __init__(self): pass
3498 def utcoffset(self, dt): pass
3499 b = BetterTry()
3500 t = cls(1, 1, 1, tzinfo=b)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003501 self.assertIs(t.tzinfo, b)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003502
3503 def test_utc_offset_out_of_bounds(self):
3504 class Edgy(tzinfo):
3505 def __init__(self, offset):
3506 self.offset = timedelta(minutes=offset)
3507 def utcoffset(self, dt):
3508 return self.offset
3509
3510 cls = self.theclass
3511 for offset, legit in ((-1440, False),
3512 (-1439, True),
3513 (1439, True),
3514 (1440, False)):
3515 if cls is time:
3516 t = cls(1, 2, 3, tzinfo=Edgy(offset))
3517 elif cls is datetime:
3518 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
3519 else:
3520 assert 0, "impossible"
3521 if legit:
3522 aofs = abs(offset)
3523 h, m = divmod(aofs, 60)
3524 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
3525 if isinstance(t, datetime):
3526 t = t.timetz()
3527 self.assertEqual(str(t), "01:02:03" + tag)
3528 else:
3529 self.assertRaises(ValueError, str, t)
3530
3531 def test_tzinfo_classes(self):
3532 cls = self.theclass
3533 class C1(tzinfo):
3534 def utcoffset(self, dt): return None
3535 def dst(self, dt): return None
3536 def tzname(self, dt): return None
3537 for t in (cls(1, 1, 1),
3538 cls(1, 1, 1, tzinfo=None),
3539 cls(1, 1, 1, tzinfo=C1())):
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003540 self.assertIsNone(t.utcoffset())
3541 self.assertIsNone(t.dst())
3542 self.assertIsNone(t.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003543
3544 class C3(tzinfo):
3545 def utcoffset(self, dt): return timedelta(minutes=-1439)
3546 def dst(self, dt): return timedelta(minutes=1439)
3547 def tzname(self, dt): return "aname"
3548 t = cls(1, 1, 1, tzinfo=C3())
3549 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
3550 self.assertEqual(t.dst(), timedelta(minutes=1439))
3551 self.assertEqual(t.tzname(), "aname")
3552
3553 # Wrong types.
3554 class C4(tzinfo):
3555 def utcoffset(self, dt): return "aname"
3556 def dst(self, dt): return 7
3557 def tzname(self, dt): return 0
3558 t = cls(1, 1, 1, tzinfo=C4())
3559 self.assertRaises(TypeError, t.utcoffset)
3560 self.assertRaises(TypeError, t.dst)
3561 self.assertRaises(TypeError, t.tzname)
3562
3563 # Offset out of range.
3564 class C6(tzinfo):
3565 def utcoffset(self, dt): return timedelta(hours=-24)
3566 def dst(self, dt): return timedelta(hours=24)
3567 t = cls(1, 1, 1, tzinfo=C6())
3568 self.assertRaises(ValueError, t.utcoffset)
3569 self.assertRaises(ValueError, t.dst)
3570
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04003571 # Not a whole number of seconds.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003572 class C7(tzinfo):
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04003573 def utcoffset(self, dt): return timedelta(microseconds=61)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003574 def dst(self, dt): return timedelta(microseconds=-81)
3575 t = cls(1, 1, 1, tzinfo=C7())
Alexander Belopolsky018d3532017-07-31 10:26:50 -04003576 self.assertEqual(t.utcoffset(), timedelta(microseconds=61))
3577 self.assertEqual(t.dst(), timedelta(microseconds=-81))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003578
3579 def test_aware_compare(self):
3580 cls = self.theclass
3581
3582 # Ensure that utcoffset() gets ignored if the comparands have
3583 # the same tzinfo member.
3584 class OperandDependentOffset(tzinfo):
3585 def utcoffset(self, t):
3586 if t.minute < 10:
3587 # d0 and d1 equal after adjustment
3588 return timedelta(minutes=t.minute)
3589 else:
3590 # d2 off in the weeds
3591 return timedelta(minutes=59)
3592
3593 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
3594 d0 = base.replace(minute=3)
3595 d1 = base.replace(minute=9)
3596 d2 = base.replace(minute=11)
3597 for x in d0, d1, d2:
3598 for y in d0, d1, d2:
3599 for op in lt, le, gt, ge, eq, ne:
3600 got = op(x, y)
3601 expected = op(x.minute, y.minute)
3602 self.assertEqual(got, expected)
3603
3604 # However, if they're different members, uctoffset is not ignored.
penguindustin96466302019-05-06 14:57:17 -04003605 # Note that a time can't actually have an operand-dependent offset,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003606 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
3607 # so skip this test for time.
3608 if cls is not time:
3609 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
3610 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
3611 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
3612 for x in d0, d1, d2:
3613 for y in d0, d1, d2:
3614 got = (x > y) - (x < y)
3615 if (x is d0 or x is d1) and (y is d0 or y is d1):
3616 expected = 0
3617 elif x is y is d2:
3618 expected = 0
3619 elif x is d2:
3620 expected = -1
3621 else:
3622 assert y is d2
3623 expected = 1
3624 self.assertEqual(got, expected)
3625
3626
3627# Testing time objects with a non-None tzinfo.
3628class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
3629 theclass = time
3630
3631 def test_empty(self):
3632 t = self.theclass()
3633 self.assertEqual(t.hour, 0)
3634 self.assertEqual(t.minute, 0)
3635 self.assertEqual(t.second, 0)
3636 self.assertEqual(t.microsecond, 0)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003637 self.assertIsNone(t.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003638
3639 def test_zones(self):
3640 est = FixedOffset(-300, "EST", 1)
3641 utc = FixedOffset(0, "UTC", -2)
3642 met = FixedOffset(60, "MET", 3)
3643 t1 = time( 7, 47, tzinfo=est)
3644 t2 = time(12, 47, tzinfo=utc)
3645 t3 = time(13, 47, tzinfo=met)
3646 t4 = time(microsecond=40)
3647 t5 = time(microsecond=40, tzinfo=utc)
3648
3649 self.assertEqual(t1.tzinfo, est)
3650 self.assertEqual(t2.tzinfo, utc)
3651 self.assertEqual(t3.tzinfo, met)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003652 self.assertIsNone(t4.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003653 self.assertEqual(t5.tzinfo, utc)
3654
3655 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
3656 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
3657 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003658 self.assertIsNone(t4.utcoffset())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003659 self.assertRaises(TypeError, t1.utcoffset, "no args")
3660
3661 self.assertEqual(t1.tzname(), "EST")
3662 self.assertEqual(t2.tzname(), "UTC")
3663 self.assertEqual(t3.tzname(), "MET")
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003664 self.assertIsNone(t4.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003665 self.assertRaises(TypeError, t1.tzname, "no args")
3666
3667 self.assertEqual(t1.dst(), timedelta(minutes=1))
3668 self.assertEqual(t2.dst(), timedelta(minutes=-2))
3669 self.assertEqual(t3.dst(), timedelta(minutes=3))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003670 self.assertIsNone(t4.dst())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003671 self.assertRaises(TypeError, t1.dst, "no args")
3672
3673 self.assertEqual(hash(t1), hash(t2))
3674 self.assertEqual(hash(t1), hash(t3))
3675 self.assertEqual(hash(t2), hash(t3))
3676
3677 self.assertEqual(t1, t2)
3678 self.assertEqual(t1, t3)
3679 self.assertEqual(t2, t3)
Alexander Belopolsky08313822012-06-15 20:19:47 -04003680 self.assertNotEqual(t4, t5) # mixed tz-aware & naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003681 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
3682 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
3683
3684 self.assertEqual(str(t1), "07:47:00-05:00")
3685 self.assertEqual(str(t2), "12:47:00+00:00")
3686 self.assertEqual(str(t3), "13:47:00+01:00")
3687 self.assertEqual(str(t4), "00:00:00.000040")
3688 self.assertEqual(str(t5), "00:00:00.000040+00:00")
3689
3690 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
3691 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
3692 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
3693 self.assertEqual(t4.isoformat(), "00:00:00.000040")
3694 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
3695
3696 d = 'datetime.time'
3697 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
3698 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
3699 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
3700 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
3701 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
3702
3703 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
3704 "07:47:00 %Z=EST %z=-0500")
3705 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
3706 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
3707
3708 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
3709 t1 = time(23, 59, tzinfo=yuck)
3710 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
3711 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
3712
3713 # Check that an invalid tzname result raises an exception.
3714 class Badtzname(tzinfo):
Alexander Belopolskye239d232010-12-08 23:31:48 +00003715 tz = 42
3716 def tzname(self, dt): return self.tz
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003717 t = time(2, 3, 4, tzinfo=Badtzname())
3718 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
3719 self.assertRaises(TypeError, t.strftime, "%Z")
3720
Alexander Belopolskye239d232010-12-08 23:31:48 +00003721 # Issue #6697:
Utkarsh Upadhyay287c5592017-07-21 02:14:54 +02003722 if '_Fast' in self.__class__.__name__:
Alexander Belopolskye239d232010-12-08 23:31:48 +00003723 Badtzname.tz = '\ud800'
3724 self.assertRaises(ValueError, t.strftime, "%Z")
3725
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003726 def test_hash_edge_cases(self):
3727 # Offsets that overflow a basic time.
3728 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
3729 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
3730 self.assertEqual(hash(t1), hash(t2))
3731
3732 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
3733 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
3734 self.assertEqual(hash(t1), hash(t2))
3735
3736 def test_pickling(self):
3737 # Try one without a tzinfo.
3738 args = 20, 59, 16, 64**2
3739 orig = self.theclass(*args)
3740 for pickler, unpickler, proto in pickle_choices:
3741 green = pickler.dumps(orig, proto)
3742 derived = unpickler.loads(green)
3743 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003744 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003745
3746 # Try one with a tzinfo.
3747 tinfo = PicklableFixedOffset(-300, 'cookie')
3748 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
3749 for pickler, unpickler, proto in pickle_choices:
3750 green = pickler.dumps(orig, proto)
3751 derived = unpickler.loads(green)
3752 self.assertEqual(orig, derived)
3753 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3754 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3755 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003756 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003757
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02003758 def test_compat_unpickle(self):
3759 tests = [
3760 b"cdatetime\ntime\n(S'\\x05\\x06\\x07\\x01\\xe2@'\n"
3761 b"ctest.datetimetester\nPicklableFixedOffset\n(tR"
3762 b"(dS'_FixedOffset__offset'\ncdatetime\ntimedelta\n"
3763 b"(I-1\nI68400\nI0\ntRs"
3764 b"S'_FixedOffset__dstoffset'\nNs"
3765 b"S'_FixedOffset__name'\nS'cookie'\nsbtR.",
3766
3767 b'cdatetime\ntime\n(U\x06\x05\x06\x07\x01\xe2@'
3768 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
3769 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
3770 b'(J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00tR'
3771 b'U\x17_FixedOffset__dstoffsetN'
3772 b'U\x12_FixedOffset__nameU\x06cookieubtR.',
3773
3774 b'\x80\x02cdatetime\ntime\nU\x06\x05\x06\x07\x01\xe2@'
3775 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
3776 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
3777 b'J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00\x87R'
3778 b'U\x17_FixedOffset__dstoffsetN'
3779 b'U\x12_FixedOffset__nameU\x06cookieub\x86R.',
3780 ]
3781
3782 tinfo = PicklableFixedOffset(-300, 'cookie')
3783 expected = self.theclass(5, 6, 7, 123456, tzinfo=tinfo)
3784 for data in tests:
3785 for loads in pickle_loads:
3786 derived = loads(data, encoding='latin1')
3787 self.assertEqual(derived, expected, repr(data))
3788 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3789 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3790 self.assertEqual(derived.tzname(), 'cookie')
3791
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003792 def test_more_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003793 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003794 cls = self.theclass
3795
3796 t = cls(0, tzinfo=FixedOffset(-300, ""))
3797 self.assertTrue(t)
3798
3799 t = cls(5, tzinfo=FixedOffset(-300, ""))
3800 self.assertTrue(t)
3801
3802 t = cls(5, tzinfo=FixedOffset(300, ""))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003803 self.assertTrue(t)
3804
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003805 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
3806 self.assertTrue(t)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003807
3808 def test_replace(self):
3809 cls = self.theclass
3810 z100 = FixedOffset(100, "+100")
3811 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3812 args = [1, 2, 3, 4, z100]
3813 base = cls(*args)
3814 self.assertEqual(base, base.replace())
3815
3816 i = 0
3817 for name, newval in (("hour", 5),
3818 ("minute", 6),
3819 ("second", 7),
3820 ("microsecond", 8),
3821 ("tzinfo", zm200)):
3822 newargs = args[:]
3823 newargs[i] = newval
3824 expected = cls(*newargs)
3825 got = base.replace(**{name: newval})
3826 self.assertEqual(expected, got)
3827 i += 1
3828
3829 # Ensure we can get rid of a tzinfo.
3830 self.assertEqual(base.tzname(), "+100")
3831 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003832 self.assertIsNone(base2.tzinfo)
3833 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003834
3835 # Ensure we can add one.
3836 base3 = base2.replace(tzinfo=z100)
3837 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003838 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003839
3840 # Out of bounds.
3841 base = cls(1)
3842 self.assertRaises(ValueError, base.replace, hour=24)
3843 self.assertRaises(ValueError, base.replace, minute=-1)
3844 self.assertRaises(ValueError, base.replace, second=100)
3845 self.assertRaises(ValueError, base.replace, microsecond=1000000)
3846
3847 def test_mixed_compare(self):
Serhiy Storchaka17e52642019-08-04 12:38:46 +03003848 t1 = self.theclass(1, 2, 3)
3849 t2 = self.theclass(1, 2, 3)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003850 self.assertEqual(t1, t2)
3851 t2 = t2.replace(tzinfo=None)
3852 self.assertEqual(t1, t2)
3853 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3854 self.assertEqual(t1, t2)
3855 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04003856 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003857
3858 # In time w/ identical tzinfo objects, utcoffset is ignored.
3859 class Varies(tzinfo):
3860 def __init__(self):
3861 self.offset = timedelta(minutes=22)
3862 def utcoffset(self, t):
3863 self.offset += timedelta(minutes=1)
3864 return self.offset
3865
3866 v = Varies()
3867 t1 = t2.replace(tzinfo=v)
3868 t2 = t2.replace(tzinfo=v)
3869 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3870 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3871 self.assertEqual(t1, t2)
3872
3873 # But if they're not identical, it isn't ignored.
3874 t2 = t2.replace(tzinfo=Varies())
3875 self.assertTrue(t1 < t2) # t1's offset counter still going up
3876
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003877 def test_fromisoformat(self):
3878 time_examples = [
3879 (0, 0, 0, 0),
3880 (23, 59, 59, 999999),
3881 ]
3882
3883 hh = (9, 12, 20)
3884 mm = (5, 30)
3885 ss = (4, 45)
3886 usec = (0, 245000, 678901)
3887
3888 time_examples += list(itertools.product(hh, mm, ss, usec))
3889
3890 tzinfos = [None, timezone.utc,
3891 timezone(timedelta(hours=2)),
3892 timezone(timedelta(hours=6, minutes=27))]
3893
3894 for ttup in time_examples:
3895 for tzi in tzinfos:
3896 t = self.theclass(*ttup, tzinfo=tzi)
3897 tstr = t.isoformat()
3898
3899 with self.subTest(tstr=tstr):
3900 t_rt = self.theclass.fromisoformat(tstr)
3901 self.assertEqual(t, t_rt)
3902
3903 def test_fromisoformat_timezone(self):
3904 base_time = self.theclass(12, 30, 45, 217456)
3905
3906 tzoffsets = [
3907 timedelta(hours=5), timedelta(hours=2),
3908 timedelta(hours=6, minutes=27),
3909 timedelta(hours=12, minutes=32, seconds=30),
3910 timedelta(hours=2, minutes=4, seconds=9, microseconds=123456)
3911 ]
3912
3913 tzoffsets += [-1 * td for td in tzoffsets]
3914
3915 tzinfos = [None, timezone.utc,
3916 timezone(timedelta(hours=0))]
3917
3918 tzinfos += [timezone(td) for td in tzoffsets]
3919
3920 for tzi in tzinfos:
3921 t = base_time.replace(tzinfo=tzi)
3922 tstr = t.isoformat()
3923
3924 with self.subTest(tstr=tstr):
3925 t_rt = self.theclass.fromisoformat(tstr)
3926 assert t == t_rt, t_rt
3927
3928 def test_fromisoformat_timespecs(self):
3929 time_bases = [
3930 (8, 17, 45, 123456),
3931 (8, 17, 45, 0)
3932 ]
3933
3934 tzinfos = [None, timezone.utc,
3935 timezone(timedelta(hours=-5)),
3936 timezone(timedelta(hours=2)),
3937 timezone(timedelta(hours=6, minutes=27))]
3938
3939 timespecs = ['hours', 'minutes', 'seconds',
3940 'milliseconds', 'microseconds']
3941
3942 for ip, ts in enumerate(timespecs):
3943 for tzi in tzinfos:
3944 for t_tuple in time_bases:
3945 if ts == 'milliseconds':
3946 new_microseconds = 1000 * (t_tuple[-1] // 1000)
3947 t_tuple = t_tuple[0:-1] + (new_microseconds,)
3948
3949 t = self.theclass(*(t_tuple[0:(1 + ip)]), tzinfo=tzi)
3950 tstr = t.isoformat(timespec=ts)
3951 with self.subTest(tstr=tstr):
3952 t_rt = self.theclass.fromisoformat(tstr)
3953 self.assertEqual(t, t_rt)
3954
3955 def test_fromisoformat_fails(self):
3956 bad_strs = [
3957 '', # Empty string
Paul Ganssle096329f2018-08-23 11:06:20 -04003958 '12\ud80000', # Invalid separator - surrogate char
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003959 '12:', # Ends on a separator
3960 '12:30:', # Ends on a separator
3961 '12:30:15.', # Ends on a separator
3962 '1', # Incomplete hours
3963 '12:3', # Incomplete minutes
3964 '12:30:1', # Incomplete seconds
3965 '1a:30:45.334034', # Invalid character in hours
3966 '12:a0:45.334034', # Invalid character in minutes
3967 '12:30:a5.334034', # Invalid character in seconds
3968 '12:30:45.1234', # Too many digits for milliseconds
3969 '12:30:45.1234567', # Too many digits for microseconds
3970 '12:30:45.123456+24:30', # Invalid time zone offset
3971 '12:30:45.123456-24:30', # Invalid negative offset
3972 '12:30:45', # Uses full-width unicode colons
3973 '12:30:45․123456', # Uses \u2024 in place of decimal point
3974 '12:30:45a', # Extra at tend of basic time
3975 '12:30:45.123a', # Extra at end of millisecond time
3976 '12:30:45.123456a', # Extra at end of microsecond time
3977 '12:30:45.123456+12:00:30a', # Extra at end of full time
3978 ]
3979
3980 for bad_str in bad_strs:
3981 with self.subTest(bad_str=bad_str):
3982 with self.assertRaises(ValueError):
3983 self.theclass.fromisoformat(bad_str)
3984
3985 def test_fromisoformat_fails_typeerror(self):
3986 # Test the fromisoformat fails when passed the wrong type
3987 import io
3988
3989 bad_types = [b'12:30:45', None, io.StringIO('12:30:45')]
3990
3991 for bad_type in bad_types:
3992 with self.assertRaises(TypeError):
3993 self.theclass.fromisoformat(bad_type)
3994
3995 def test_fromisoformat_subclass(self):
3996 class TimeSubclass(self.theclass):
3997 pass
3998
3999 tsc = TimeSubclass(12, 14, 45, 203745, tzinfo=timezone.utc)
4000 tsc_rt = TimeSubclass.fromisoformat(tsc.isoformat())
4001
4002 self.assertEqual(tsc, tsc_rt)
4003 self.assertIsInstance(tsc_rt, TimeSubclass)
4004
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004005 def test_subclass_timetz(self):
4006
4007 class C(self.theclass):
4008 theAnswer = 42
4009
4010 def __new__(cls, *args, **kws):
4011 temp = kws.copy()
4012 extra = temp.pop('extra')
4013 result = self.theclass.__new__(cls, *args, **temp)
4014 result.extra = extra
4015 return result
4016
4017 def newmeth(self, start):
4018 return start + self.hour + self.second
4019
4020 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
4021
4022 dt1 = self.theclass(*args)
4023 dt2 = C(*args, **{'extra': 7})
4024
4025 self.assertEqual(dt2.__class__, C)
4026 self.assertEqual(dt2.theAnswer, 42)
4027 self.assertEqual(dt2.extra, 7)
4028 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
4029 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
4030
4031
4032# Testing datetime objects with a non-None tzinfo.
4033
4034class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
4035 theclass = datetime
4036
4037 def test_trivial(self):
4038 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
4039 self.assertEqual(dt.year, 1)
4040 self.assertEqual(dt.month, 2)
4041 self.assertEqual(dt.day, 3)
4042 self.assertEqual(dt.hour, 4)
4043 self.assertEqual(dt.minute, 5)
4044 self.assertEqual(dt.second, 6)
4045 self.assertEqual(dt.microsecond, 7)
4046 self.assertEqual(dt.tzinfo, None)
4047
4048 def test_even_more_compare(self):
4049 # The test_compare() and test_more_compare() inherited from TestDate
4050 # and TestDateTime covered non-tzinfo cases.
4051
4052 # Smallest possible after UTC adjustment.
4053 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
4054 # Largest possible after UTC adjustment.
4055 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
4056 tzinfo=FixedOffset(-1439, ""))
4057
4058 # Make sure those compare correctly, and w/o overflow.
4059 self.assertTrue(t1 < t2)
4060 self.assertTrue(t1 != t2)
4061 self.assertTrue(t2 > t1)
4062
4063 self.assertEqual(t1, t1)
4064 self.assertEqual(t2, t2)
4065
4066 # Equal afer adjustment.
4067 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
4068 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
4069 self.assertEqual(t1, t2)
4070
4071 # Change t1 not to subtract a minute, and t1 should be larger.
4072 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
4073 self.assertTrue(t1 > t2)
4074
4075 # Change t1 to subtract 2 minutes, and t1 should be smaller.
4076 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
4077 self.assertTrue(t1 < t2)
4078
4079 # Back to the original t1, but make seconds resolve it.
4080 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
4081 second=1)
4082 self.assertTrue(t1 > t2)
4083
4084 # Likewise, but make microseconds resolve it.
4085 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
4086 microsecond=1)
4087 self.assertTrue(t1 > t2)
4088
Alexander Belopolsky08313822012-06-15 20:19:47 -04004089 # Make t2 naive and it should differ.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004090 t2 = self.theclass.min
Alexander Belopolsky08313822012-06-15 20:19:47 -04004091 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004092 self.assertEqual(t2, t2)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04004093 # and > comparison should fail
4094 with self.assertRaises(TypeError):
4095 t1 > t2
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004096
4097 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
4098 class Naive(tzinfo):
4099 def utcoffset(self, dt): return None
4100 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
Alexander Belopolsky08313822012-06-15 20:19:47 -04004101 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004102 self.assertEqual(t2, t2)
4103
4104 # OTOH, it's OK to compare two of these mixing the two ways of being
4105 # naive.
4106 t1 = self.theclass(5, 6, 7)
4107 self.assertEqual(t1, t2)
4108
4109 # Try a bogus uctoffset.
4110 class Bogus(tzinfo):
4111 def utcoffset(self, dt):
4112 return timedelta(minutes=1440) # out of bounds
4113 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
4114 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
4115 self.assertRaises(ValueError, lambda: t1 == t2)
4116
4117 def test_pickling(self):
4118 # Try one without a tzinfo.
4119 args = 6, 7, 23, 20, 59, 1, 64**2
4120 orig = self.theclass(*args)
4121 for pickler, unpickler, proto in pickle_choices:
4122 green = pickler.dumps(orig, proto)
4123 derived = unpickler.loads(green)
4124 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02004125 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004126
4127 # Try one with a tzinfo.
4128 tinfo = PicklableFixedOffset(-300, 'cookie')
4129 orig = self.theclass(*args, **{'tzinfo': tinfo})
4130 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
4131 for pickler, unpickler, proto in pickle_choices:
4132 green = pickler.dumps(orig, proto)
4133 derived = unpickler.loads(green)
4134 self.assertEqual(orig, derived)
4135 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
4136 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
4137 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02004138 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004139
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02004140 def test_compat_unpickle(self):
4141 tests = [
4142 b'cdatetime\ndatetime\n'
4143 b"(S'\\x07\\xdf\\x0b\\x1b\\x14;\\x01\\x01\\xe2@'\n"
4144 b'ctest.datetimetester\nPicklableFixedOffset\n(tR'
4145 b"(dS'_FixedOffset__offset'\ncdatetime\ntimedelta\n"
4146 b'(I-1\nI68400\nI0\ntRs'
4147 b"S'_FixedOffset__dstoffset'\nNs"
4148 b"S'_FixedOffset__name'\nS'cookie'\nsbtR.",
4149
4150 b'cdatetime\ndatetime\n'
4151 b'(U\n\x07\xdf\x0b\x1b\x14;\x01\x01\xe2@'
4152 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
4153 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
4154 b'(J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00tR'
4155 b'U\x17_FixedOffset__dstoffsetN'
4156 b'U\x12_FixedOffset__nameU\x06cookieubtR.',
4157
4158 b'\x80\x02cdatetime\ndatetime\n'
4159 b'U\n\x07\xdf\x0b\x1b\x14;\x01\x01\xe2@'
4160 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
4161 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
4162 b'J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00\x87R'
4163 b'U\x17_FixedOffset__dstoffsetN'
4164 b'U\x12_FixedOffset__nameU\x06cookieub\x86R.',
4165 ]
4166 args = 2015, 11, 27, 20, 59, 1, 123456
4167 tinfo = PicklableFixedOffset(-300, 'cookie')
4168 expected = self.theclass(*args, **{'tzinfo': tinfo})
4169 for data in tests:
4170 for loads in pickle_loads:
4171 derived = loads(data, encoding='latin1')
4172 self.assertEqual(derived, expected)
4173 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
4174 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
4175 self.assertEqual(derived.tzname(), 'cookie')
4176
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004177 def test_extreme_hashes(self):
4178 # If an attempt is made to hash these via subtracting the offset
4179 # then hashing a datetime object, OverflowError results. The
4180 # Python implementation used to blow up here.
4181 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
4182 hash(t)
4183 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
4184 tzinfo=FixedOffset(-1439, ""))
4185 hash(t)
4186
4187 # OTOH, an OOB offset should blow up.
4188 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
4189 self.assertRaises(ValueError, hash, t)
4190
4191 def test_zones(self):
4192 est = FixedOffset(-300, "EST")
4193 utc = FixedOffset(0, "UTC")
4194 met = FixedOffset(60, "MET")
4195 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
4196 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
4197 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
4198 self.assertEqual(t1.tzinfo, est)
4199 self.assertEqual(t2.tzinfo, utc)
4200 self.assertEqual(t3.tzinfo, met)
4201 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
4202 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
4203 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
4204 self.assertEqual(t1.tzname(), "EST")
4205 self.assertEqual(t2.tzname(), "UTC")
4206 self.assertEqual(t3.tzname(), "MET")
4207 self.assertEqual(hash(t1), hash(t2))
4208 self.assertEqual(hash(t1), hash(t3))
4209 self.assertEqual(hash(t2), hash(t3))
4210 self.assertEqual(t1, t2)
4211 self.assertEqual(t1, t3)
4212 self.assertEqual(t2, t3)
4213 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
4214 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
4215 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
4216 d = 'datetime.datetime(2002, 3, 19, '
4217 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
4218 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
4219 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
4220
4221 def test_combine(self):
4222 met = FixedOffset(60, "MET")
4223 d = date(2002, 3, 4)
4224 tz = time(18, 45, 3, 1234, tzinfo=met)
4225 dt = datetime.combine(d, tz)
4226 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
4227 tzinfo=met))
4228
4229 def test_extract(self):
4230 met = FixedOffset(60, "MET")
4231 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
4232 self.assertEqual(dt.date(), date(2002, 3, 4))
4233 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
4234 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
4235
4236 def test_tz_aware_arithmetic(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004237 now = self.theclass.now()
4238 tz55 = FixedOffset(-330, "west 5:30")
4239 timeaware = now.time().replace(tzinfo=tz55)
4240 nowaware = self.theclass.combine(now.date(), timeaware)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004241 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004242 self.assertEqual(nowaware.timetz(), timeaware)
4243
4244 # Can't mix aware and non-aware.
4245 self.assertRaises(TypeError, lambda: now - nowaware)
4246 self.assertRaises(TypeError, lambda: nowaware - now)
4247
4248 # And adding datetime's doesn't make sense, aware or not.
4249 self.assertRaises(TypeError, lambda: now + nowaware)
4250 self.assertRaises(TypeError, lambda: nowaware + now)
4251 self.assertRaises(TypeError, lambda: nowaware + nowaware)
4252
4253 # Subtracting should yield 0.
4254 self.assertEqual(now - now, timedelta(0))
4255 self.assertEqual(nowaware - nowaware, timedelta(0))
4256
4257 # Adding a delta should preserve tzinfo.
4258 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
4259 nowawareplus = nowaware + delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004260 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004261 nowawareplus2 = delta + nowaware
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004262 self.assertIs(nowawareplus2.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004263 self.assertEqual(nowawareplus, nowawareplus2)
4264
4265 # that - delta should be what we started with, and that - what we
4266 # started with should be delta.
4267 diff = nowawareplus - delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004268 self.assertIs(diff.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004269 self.assertEqual(nowaware, diff)
4270 self.assertRaises(TypeError, lambda: delta - nowawareplus)
4271 self.assertEqual(nowawareplus - nowaware, delta)
4272
4273 # Make up a random timezone.
4274 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
4275 # Attach it to nowawareplus.
4276 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004277 self.assertIs(nowawareplus.tzinfo, tzr)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004278 # Make sure the difference takes the timezone adjustments into account.
4279 got = nowaware - nowawareplus
4280 # Expected: (nowaware base - nowaware offset) -
4281 # (nowawareplus base - nowawareplus offset) =
4282 # (nowaware base - nowawareplus base) +
4283 # (nowawareplus offset - nowaware offset) =
4284 # -delta + nowawareplus offset - nowaware offset
4285 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
4286 self.assertEqual(got, expected)
4287
4288 # Try max possible difference.
4289 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
4290 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
4291 tzinfo=FixedOffset(-1439, "max"))
4292 maxdiff = max - min
4293 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
4294 timedelta(minutes=2*1439))
4295 # Different tzinfo, but the same offset
4296 tza = timezone(HOUR, 'A')
4297 tzb = timezone(HOUR, 'B')
4298 delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
4299 self.assertEqual(delta, self.theclass.min - self.theclass.max)
4300
4301 def test_tzinfo_now(self):
4302 meth = self.theclass.now
4303 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4304 base = meth()
4305 # Try with and without naming the keyword.
4306 off42 = FixedOffset(42, "42")
4307 another = meth(off42)
4308 again = meth(tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004309 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004310 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
4311 # Bad argument with and w/o naming the keyword.
4312 self.assertRaises(TypeError, meth, 16)
4313 self.assertRaises(TypeError, meth, tzinfo=16)
4314 # Bad keyword name.
4315 self.assertRaises(TypeError, meth, tinfo=off42)
4316 # Too many args.
4317 self.assertRaises(TypeError, meth, off42, off42)
4318
4319 # We don't know which time zone we're in, and don't have a tzinfo
4320 # class to represent it, so seeing whether a tz argument actually
4321 # does a conversion is tricky.
4322 utc = FixedOffset(0, "utc", 0)
4323 for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
4324 timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
4325 for dummy in range(3):
4326 now = datetime.now(weirdtz)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004327 self.assertIs(now.tzinfo, weirdtz)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004328 utcnow = datetime.utcnow().replace(tzinfo=utc)
4329 now2 = utcnow.astimezone(weirdtz)
4330 if abs(now - now2) < timedelta(seconds=30):
4331 break
4332 # Else the code is broken, or more than 30 seconds passed between
4333 # calls; assuming the latter, just try again.
4334 else:
4335 # Three strikes and we're out.
4336 self.fail("utcnow(), now(tz), or astimezone() may be broken")
4337
4338 def test_tzinfo_fromtimestamp(self):
4339 import time
4340 meth = self.theclass.fromtimestamp
4341 ts = time.time()
4342 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4343 base = meth(ts)
4344 # Try with and without naming the keyword.
4345 off42 = FixedOffset(42, "42")
4346 another = meth(ts, off42)
4347 again = meth(ts, tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004348 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004349 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
4350 # Bad argument with and w/o naming the keyword.
4351 self.assertRaises(TypeError, meth, ts, 16)
4352 self.assertRaises(TypeError, meth, ts, tzinfo=16)
4353 # Bad keyword name.
4354 self.assertRaises(TypeError, meth, ts, tinfo=off42)
4355 # Too many args.
4356 self.assertRaises(TypeError, meth, ts, off42, off42)
4357 # Too few args.
4358 self.assertRaises(TypeError, meth)
4359
4360 # Try to make sure tz= actually does some conversion.
4361 timestamp = 1000000000
4362 utcdatetime = datetime.utcfromtimestamp(timestamp)
4363 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
4364 # But on some flavor of Mac, it's nowhere near that. So we can't have
4365 # any idea here what time that actually is, we can only test that
4366 # relative changes match.
4367 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
4368 tz = FixedOffset(utcoffset, "tz", 0)
4369 expected = utcdatetime + utcoffset
4370 got = datetime.fromtimestamp(timestamp, tz)
4371 self.assertEqual(expected, got.replace(tzinfo=None))
4372
4373 def test_tzinfo_utcnow(self):
4374 meth = self.theclass.utcnow
4375 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4376 base = meth()
4377 # Try with and without naming the keyword; for whatever reason,
4378 # utcnow() doesn't accept a tzinfo argument.
4379 off42 = FixedOffset(42, "42")
4380 self.assertRaises(TypeError, meth, off42)
4381 self.assertRaises(TypeError, meth, tzinfo=off42)
4382
4383 def test_tzinfo_utcfromtimestamp(self):
4384 import time
4385 meth = self.theclass.utcfromtimestamp
4386 ts = time.time()
4387 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4388 base = meth(ts)
4389 # Try with and without naming the keyword; for whatever reason,
4390 # utcfromtimestamp() doesn't accept a tzinfo argument.
4391 off42 = FixedOffset(42, "42")
4392 self.assertRaises(TypeError, meth, ts, off42)
4393 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
4394
4395 def test_tzinfo_timetuple(self):
4396 # TestDateTime tested most of this. datetime adds a twist to the
4397 # DST flag.
4398 class DST(tzinfo):
4399 def __init__(self, dstvalue):
4400 if isinstance(dstvalue, int):
4401 dstvalue = timedelta(minutes=dstvalue)
4402 self.dstvalue = dstvalue
4403 def dst(self, dt):
4404 return self.dstvalue
4405
4406 cls = self.theclass
4407 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
4408 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
4409 t = d.timetuple()
4410 self.assertEqual(1, t.tm_year)
4411 self.assertEqual(1, t.tm_mon)
4412 self.assertEqual(1, t.tm_mday)
4413 self.assertEqual(10, t.tm_hour)
4414 self.assertEqual(20, t.tm_min)
4415 self.assertEqual(30, t.tm_sec)
4416 self.assertEqual(0, t.tm_wday)
4417 self.assertEqual(1, t.tm_yday)
4418 self.assertEqual(flag, t.tm_isdst)
4419
4420 # dst() returns wrong type.
4421 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
4422
4423 # dst() at the edge.
4424 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
4425 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
4426
4427 # dst() out of range.
4428 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
4429 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
4430
4431 def test_utctimetuple(self):
4432 class DST(tzinfo):
4433 def __init__(self, dstvalue=0):
4434 if isinstance(dstvalue, int):
4435 dstvalue = timedelta(minutes=dstvalue)
4436 self.dstvalue = dstvalue
4437 def dst(self, dt):
4438 return self.dstvalue
4439
4440 cls = self.theclass
4441 # This can't work: DST didn't implement utcoffset.
4442 self.assertRaises(NotImplementedError,
4443 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
4444
4445 class UOFS(DST):
4446 def __init__(self, uofs, dofs=None):
4447 DST.__init__(self, dofs)
4448 self.uofs = timedelta(minutes=uofs)
4449 def utcoffset(self, dt):
4450 return self.uofs
4451
4452 for dstvalue in -33, 33, 0, None:
4453 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
4454 t = d.utctimetuple()
4455 self.assertEqual(d.year, t.tm_year)
4456 self.assertEqual(d.month, t.tm_mon)
4457 self.assertEqual(d.day, t.tm_mday)
4458 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
4459 self.assertEqual(13, t.tm_min)
4460 self.assertEqual(d.second, t.tm_sec)
4461 self.assertEqual(d.weekday(), t.tm_wday)
4462 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
4463 t.tm_yday)
4464 # Ensure tm_isdst is 0 regardless of what dst() says: DST
4465 # is never in effect for a UTC time.
4466 self.assertEqual(0, t.tm_isdst)
4467
4468 # For naive datetime, utctimetuple == timetuple except for isdst
4469 d = cls(1, 2, 3, 10, 20, 30, 40)
4470 t = d.utctimetuple()
4471 self.assertEqual(t[:-1], d.timetuple()[:-1])
4472 self.assertEqual(0, t.tm_isdst)
4473 # Same if utcoffset is None
4474 class NOFS(DST):
4475 def utcoffset(self, dt):
4476 return None
4477 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
4478 t = d.utctimetuple()
4479 self.assertEqual(t[:-1], d.timetuple()[:-1])
4480 self.assertEqual(0, t.tm_isdst)
4481 # Check that bad tzinfo is detected
4482 class BOFS(DST):
4483 def utcoffset(self, dt):
4484 return "EST"
4485 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
4486 self.assertRaises(TypeError, d.utctimetuple)
4487
4488 # Check that utctimetuple() is the same as
4489 # astimezone(utc).timetuple()
4490 d = cls(2010, 11, 13, 14, 15, 16, 171819)
4491 for tz in [timezone.min, timezone.utc, timezone.max]:
4492 dtz = d.replace(tzinfo=tz)
4493 self.assertEqual(dtz.utctimetuple()[:-1],
4494 dtz.astimezone(timezone.utc).timetuple()[:-1])
4495 # At the edges, UTC adjustment can produce years out-of-range
4496 # for a datetime object. Ensure that an OverflowError is
4497 # raised.
4498 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
4499 # That goes back 1 minute less than a full day.
4500 self.assertRaises(OverflowError, tiny.utctimetuple)
4501
4502 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
4503 # That goes forward 1 minute less than a full day.
4504 self.assertRaises(OverflowError, huge.utctimetuple)
4505 # More overflow cases
4506 tiny = cls.min.replace(tzinfo=timezone(MINUTE))
4507 self.assertRaises(OverflowError, tiny.utctimetuple)
4508 huge = cls.max.replace(tzinfo=timezone(-MINUTE))
4509 self.assertRaises(OverflowError, huge.utctimetuple)
4510
4511 def test_tzinfo_isoformat(self):
4512 zero = FixedOffset(0, "+00:00")
4513 plus = FixedOffset(220, "+03:40")
4514 minus = FixedOffset(-231, "-03:51")
4515 unknown = FixedOffset(None, "")
4516
4517 cls = self.theclass
4518 datestr = '0001-02-03'
4519 for ofs in None, zero, plus, minus, unknown:
4520 for us in 0, 987001:
4521 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
4522 timestr = '04:05:59' + (us and '.987001' or '')
4523 ofsstr = ofs is not None and d.tzname() or ''
4524 tailstr = timestr + ofsstr
4525 iso = d.isoformat()
4526 self.assertEqual(iso, datestr + 'T' + tailstr)
4527 self.assertEqual(iso, d.isoformat('T'))
4528 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
4529 self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
4530 self.assertEqual(str(d), datestr + ' ' + tailstr)
4531
4532 def test_replace(self):
4533 cls = self.theclass
4534 z100 = FixedOffset(100, "+100")
4535 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
4536 args = [1, 2, 3, 4, 5, 6, 7, z100]
4537 base = cls(*args)
4538 self.assertEqual(base, base.replace())
4539
4540 i = 0
4541 for name, newval in (("year", 2),
4542 ("month", 3),
4543 ("day", 4),
4544 ("hour", 5),
4545 ("minute", 6),
4546 ("second", 7),
4547 ("microsecond", 8),
4548 ("tzinfo", zm200)):
4549 newargs = args[:]
4550 newargs[i] = newval
4551 expected = cls(*newargs)
4552 got = base.replace(**{name: newval})
4553 self.assertEqual(expected, got)
4554 i += 1
4555
4556 # Ensure we can get rid of a tzinfo.
4557 self.assertEqual(base.tzname(), "+100")
4558 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004559 self.assertIsNone(base2.tzinfo)
4560 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004561
4562 # Ensure we can add one.
4563 base3 = base2.replace(tzinfo=z100)
4564 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004565 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004566
4567 # Out of bounds.
4568 base = cls(2000, 2, 29)
4569 self.assertRaises(ValueError, base.replace, year=2001)
4570
4571 def test_more_astimezone(self):
4572 # The inherited test_astimezone covered some trivial and error cases.
4573 fnone = FixedOffset(None, "None")
4574 f44m = FixedOffset(44, "44")
4575 fm5h = FixedOffset(-timedelta(hours=5), "m300")
4576
4577 dt = self.theclass.now(tz=f44m)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004578 self.assertIs(dt.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004579 # Replacing with degenerate tzinfo raises an exception.
4580 self.assertRaises(ValueError, dt.astimezone, fnone)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004581 # Replacing with same tzinfo makes no change.
4582 x = dt.astimezone(dt.tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004583 self.assertIs(x.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004584 self.assertEqual(x.date(), dt.date())
4585 self.assertEqual(x.time(), dt.time())
4586
4587 # Replacing with different tzinfo does adjust.
4588 got = dt.astimezone(fm5h)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004589 self.assertIs(got.tzinfo, fm5h)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004590 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
4591 expected = dt - dt.utcoffset() # in effect, convert to UTC
4592 expected += fm5h.utcoffset(dt) # and from there to local time
4593 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
4594 self.assertEqual(got.date(), expected.date())
4595 self.assertEqual(got.time(), expected.time())
4596 self.assertEqual(got.timetz(), expected.timetz())
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004597 self.assertIs(got.tzinfo, expected.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004598 self.assertEqual(got, expected)
4599
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004600 @support.run_with_tz('UTC')
4601 def test_astimezone_default_utc(self):
4602 dt = self.theclass.now(timezone.utc)
4603 self.assertEqual(dt.astimezone(None), dt)
4604 self.assertEqual(dt.astimezone(), dt)
4605
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004606 # Note that offset in TZ variable has the opposite sign to that
4607 # produced by %z directive.
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004608 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4609 def test_astimezone_default_eastern(self):
4610 dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
4611 local = dt.astimezone()
4612 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004613 self.assertEqual(local.strftime("%z %Z"), "-0500 EST")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004614 dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc)
4615 local = dt.astimezone()
4616 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004617 self.assertEqual(local.strftime("%z %Z"), "-0400 EDT")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004618
Alexander Belopolsky1dcf4f92016-03-25 15:42:59 -04004619 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4620 def test_astimezone_default_near_fold(self):
4621 # Issue #26616.
4622 u = datetime(2015, 11, 1, 5, tzinfo=timezone.utc)
4623 t = u.astimezone()
4624 s = t.astimezone()
4625 self.assertEqual(t.tzinfo, s.tzinfo)
4626
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004627 def test_aware_subtract(self):
4628 cls = self.theclass
4629
4630 # Ensure that utcoffset() is ignored when the operands have the
4631 # same tzinfo member.
4632 class OperandDependentOffset(tzinfo):
4633 def utcoffset(self, t):
4634 if t.minute < 10:
4635 # d0 and d1 equal after adjustment
4636 return timedelta(minutes=t.minute)
4637 else:
4638 # d2 off in the weeds
4639 return timedelta(minutes=59)
4640
4641 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
4642 d0 = base.replace(minute=3)
4643 d1 = base.replace(minute=9)
4644 d2 = base.replace(minute=11)
4645 for x in d0, d1, d2:
4646 for y in d0, d1, d2:
4647 got = x - y
4648 expected = timedelta(minutes=x.minute - y.minute)
4649 self.assertEqual(got, expected)
4650
4651 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
4652 # ignored.
4653 base = cls(8, 9, 10, 11, 12, 13, 14)
4654 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
4655 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
4656 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
4657 for x in d0, d1, d2:
4658 for y in d0, d1, d2:
4659 got = x - y
4660 if (x is d0 or x is d1) and (y is d0 or y is d1):
4661 expected = timedelta(0)
4662 elif x is y is d2:
4663 expected = timedelta(0)
4664 elif x is d2:
4665 expected = timedelta(minutes=(11-59)-0)
4666 else:
4667 assert y is d2
4668 expected = timedelta(minutes=0-(11-59))
4669 self.assertEqual(got, expected)
4670
4671 def test_mixed_compare(self):
4672 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
4673 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
4674 self.assertEqual(t1, t2)
4675 t2 = t2.replace(tzinfo=None)
4676 self.assertEqual(t1, t2)
4677 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
4678 self.assertEqual(t1, t2)
4679 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04004680 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004681
4682 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
4683 class Varies(tzinfo):
4684 def __init__(self):
4685 self.offset = timedelta(minutes=22)
4686 def utcoffset(self, t):
4687 self.offset += timedelta(minutes=1)
4688 return self.offset
4689
4690 v = Varies()
4691 t1 = t2.replace(tzinfo=v)
4692 t2 = t2.replace(tzinfo=v)
4693 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
4694 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
4695 self.assertEqual(t1, t2)
4696
4697 # But if they're not identical, it isn't ignored.
4698 t2 = t2.replace(tzinfo=Varies())
4699 self.assertTrue(t1 < t2) # t1's offset counter still going up
4700
4701 def test_subclass_datetimetz(self):
4702
4703 class C(self.theclass):
4704 theAnswer = 42
4705
4706 def __new__(cls, *args, **kws):
4707 temp = kws.copy()
4708 extra = temp.pop('extra')
4709 result = self.theclass.__new__(cls, *args, **temp)
4710 result.extra = extra
4711 return result
4712
4713 def newmeth(self, start):
4714 return start + self.hour + self.year
4715
4716 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
4717
4718 dt1 = self.theclass(*args)
4719 dt2 = C(*args, **{'extra': 7})
4720
4721 self.assertEqual(dt2.__class__, C)
4722 self.assertEqual(dt2.theAnswer, 42)
4723 self.assertEqual(dt2.extra, 7)
4724 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
4725 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
4726
4727# Pain to set up DST-aware tzinfo classes.
4728
4729def first_sunday_on_or_after(dt):
4730 days_to_go = 6 - dt.weekday()
4731 if days_to_go:
4732 dt += timedelta(days_to_go)
4733 return dt
4734
4735ZERO = timedelta(0)
4736MINUTE = timedelta(minutes=1)
4737HOUR = timedelta(hours=1)
4738DAY = timedelta(days=1)
4739# In the US, DST starts at 2am (standard time) on the first Sunday in April.
4740DSTSTART = datetime(1, 4, 1, 2)
4741# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
4742# which is the first Sunday on or after Oct 25. Because we view 1:MM as
4743# being standard time on that day, there is no spelling in local time of
4744# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
4745DSTEND = datetime(1, 10, 25, 1)
4746
4747class USTimeZone(tzinfo):
4748
4749 def __init__(self, hours, reprname, stdname, dstname):
4750 self.stdoffset = timedelta(hours=hours)
4751 self.reprname = reprname
4752 self.stdname = stdname
4753 self.dstname = dstname
4754
4755 def __repr__(self):
4756 return self.reprname
4757
4758 def tzname(self, dt):
4759 if self.dst(dt):
4760 return self.dstname
4761 else:
4762 return self.stdname
4763
4764 def utcoffset(self, dt):
4765 return self.stdoffset + self.dst(dt)
4766
4767 def dst(self, dt):
4768 if dt is None or dt.tzinfo is None:
4769 # An exception instead may be sensible here, in one or more of
4770 # the cases.
4771 return ZERO
4772 assert dt.tzinfo is self
4773
4774 # Find first Sunday in April.
4775 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
4776 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
4777
4778 # Find last Sunday in October.
4779 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
4780 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
4781
4782 # Can't compare naive to aware objects, so strip the timezone from
4783 # dt first.
4784 if start <= dt.replace(tzinfo=None) < end:
4785 return HOUR
4786 else:
4787 return ZERO
4788
4789Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
4790Central = USTimeZone(-6, "Central", "CST", "CDT")
4791Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
4792Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
4793utc_real = FixedOffset(0, "UTC", 0)
4794# For better test coverage, we want another flavor of UTC that's west of
4795# the Eastern and Pacific timezones.
4796utc_fake = FixedOffset(-12*60, "UTCfake", 0)
4797
4798class TestTimezoneConversions(unittest.TestCase):
4799 # The DST switch times for 2002, in std time.
4800 dston = datetime(2002, 4, 7, 2)
4801 dstoff = datetime(2002, 10, 27, 1)
4802
4803 theclass = datetime
4804
4805 # Check a time that's inside DST.
4806 def checkinside(self, dt, tz, utc, dston, dstoff):
4807 self.assertEqual(dt.dst(), HOUR)
4808
4809 # Conversion to our own timezone is always an identity.
4810 self.assertEqual(dt.astimezone(tz), dt)
4811
4812 asutc = dt.astimezone(utc)
4813 there_and_back = asutc.astimezone(tz)
4814
4815 # Conversion to UTC and back isn't always an identity here,
4816 # because there are redundant spellings (in local time) of
4817 # UTC time when DST begins: the clock jumps from 1:59:59
4818 # to 3:00:00, and a local time of 2:MM:SS doesn't really
4819 # make sense then. The classes above treat 2:MM:SS as
4820 # daylight time then (it's "after 2am"), really an alias
4821 # for 1:MM:SS standard time. The latter form is what
4822 # conversion back from UTC produces.
4823 if dt.date() == dston.date() and dt.hour == 2:
4824 # We're in the redundant hour, and coming back from
4825 # UTC gives the 1:MM:SS standard-time spelling.
4826 self.assertEqual(there_and_back + HOUR, dt)
4827 # Although during was considered to be in daylight
4828 # time, there_and_back is not.
4829 self.assertEqual(there_and_back.dst(), ZERO)
4830 # They're the same times in UTC.
4831 self.assertEqual(there_and_back.astimezone(utc),
4832 dt.astimezone(utc))
4833 else:
4834 # We're not in the redundant hour.
4835 self.assertEqual(dt, there_and_back)
4836
4837 # Because we have a redundant spelling when DST begins, there is
Ezio Melotti3b3499b2011-03-16 11:35:38 +02004838 # (unfortunately) an hour when DST ends that can't be spelled at all in
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004839 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
4840 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
4841 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
4842 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
4843 # expressed in local time. Nevertheless, we want conversion back
4844 # from UTC to mimic the local clock's "repeat an hour" behavior.
4845 nexthour_utc = asutc + HOUR
4846 nexthour_tz = nexthour_utc.astimezone(tz)
4847 if dt.date() == dstoff.date() and dt.hour == 0:
4848 # We're in the hour before the last DST hour. The last DST hour
4849 # is ineffable. We want the conversion back to repeat 1:MM.
4850 self.assertEqual(nexthour_tz, dt.replace(hour=1))
4851 nexthour_utc += HOUR
4852 nexthour_tz = nexthour_utc.astimezone(tz)
4853 self.assertEqual(nexthour_tz, dt.replace(hour=1))
4854 else:
4855 self.assertEqual(nexthour_tz - dt, HOUR)
4856
4857 # Check a time that's outside DST.
4858 def checkoutside(self, dt, tz, utc):
4859 self.assertEqual(dt.dst(), ZERO)
4860
4861 # Conversion to our own timezone is always an identity.
4862 self.assertEqual(dt.astimezone(tz), dt)
4863
4864 # Converting to UTC and back is an identity too.
4865 asutc = dt.astimezone(utc)
4866 there_and_back = asutc.astimezone(tz)
4867 self.assertEqual(dt, there_and_back)
4868
4869 def convert_between_tz_and_utc(self, tz, utc):
4870 dston = self.dston.replace(tzinfo=tz)
4871 # Because 1:MM on the day DST ends is taken as being standard time,
4872 # there is no spelling in tz for the last hour of daylight time.
4873 # For purposes of the test, the last hour of DST is 0:MM, which is
4874 # taken as being daylight time (and 1:MM is taken as being standard
4875 # time).
4876 dstoff = self.dstoff.replace(tzinfo=tz)
4877 for delta in (timedelta(weeks=13),
4878 DAY,
4879 HOUR,
4880 timedelta(minutes=1),
4881 timedelta(microseconds=1)):
4882
4883 self.checkinside(dston, tz, utc, dston, dstoff)
4884 for during in dston + delta, dstoff - delta:
4885 self.checkinside(during, tz, utc, dston, dstoff)
4886
4887 self.checkoutside(dstoff, tz, utc)
4888 for outside in dston - delta, dstoff + delta:
4889 self.checkoutside(outside, tz, utc)
4890
4891 def test_easy(self):
4892 # Despite the name of this test, the endcases are excruciating.
4893 self.convert_between_tz_and_utc(Eastern, utc_real)
4894 self.convert_between_tz_and_utc(Pacific, utc_real)
4895 self.convert_between_tz_and_utc(Eastern, utc_fake)
4896 self.convert_between_tz_and_utc(Pacific, utc_fake)
4897 # The next is really dancing near the edge. It works because
4898 # Pacific and Eastern are far enough apart that their "problem
4899 # hours" don't overlap.
4900 self.convert_between_tz_and_utc(Eastern, Pacific)
4901 self.convert_between_tz_and_utc(Pacific, Eastern)
4902 # OTOH, these fail! Don't enable them. The difficulty is that
4903 # the edge case tests assume that every hour is representable in
4904 # the "utc" class. This is always true for a fixed-offset tzinfo
4905 # class (lke utc_real and utc_fake), but not for Eastern or Central.
4906 # For these adjacent DST-aware time zones, the range of time offsets
4907 # tested ends up creating hours in the one that aren't representable
4908 # in the other. For the same reason, we would see failures in the
4909 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
4910 # offset deltas in convert_between_tz_and_utc().
4911 #
4912 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
4913 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
4914
4915 def test_tricky(self):
4916 # 22:00 on day before daylight starts.
4917 fourback = self.dston - timedelta(hours=4)
4918 ninewest = FixedOffset(-9*60, "-0900", 0)
4919 fourback = fourback.replace(tzinfo=ninewest)
4920 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
4921 # 2", we should get the 3 spelling.
4922 # If we plug 22:00 the day before into Eastern, it "looks like std
4923 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
4924 # to 22:00 lands on 2:00, which makes no sense in local time (the
4925 # local clock jumps from 1 to 3). The point here is to make sure we
4926 # get the 3 spelling.
4927 expected = self.dston.replace(hour=3)
4928 got = fourback.astimezone(Eastern).replace(tzinfo=None)
4929 self.assertEqual(expected, got)
4930
4931 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
4932 # case we want the 1:00 spelling.
4933 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
4934 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
4935 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
4936 # spelling.
4937 expected = self.dston.replace(hour=1)
4938 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
4939 self.assertEqual(expected, got)
4940
4941 # Now on the day DST ends, we want "repeat an hour" behavior.
4942 # UTC 4:MM 5:MM 6:MM 7:MM checking these
4943 # EST 23:MM 0:MM 1:MM 2:MM
4944 # EDT 0:MM 1:MM 2:MM 3:MM
4945 # wall 0:MM 1:MM 1:MM 2:MM against these
4946 for utc in utc_real, utc_fake:
4947 for tz in Eastern, Pacific:
4948 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
4949 # Convert that to UTC.
4950 first_std_hour -= tz.utcoffset(None)
4951 # Adjust for possibly fake UTC.
4952 asutc = first_std_hour + utc.utcoffset(None)
4953 # First UTC hour to convert; this is 4:00 when utc=utc_real &
4954 # tz=Eastern.
4955 asutcbase = asutc.replace(tzinfo=utc)
4956 for tzhour in (0, 1, 1, 2):
4957 expectedbase = self.dstoff.replace(hour=tzhour)
4958 for minute in 0, 30, 59:
4959 expected = expectedbase.replace(minute=minute)
4960 asutc = asutcbase.replace(minute=minute)
4961 astz = asutc.astimezone(tz)
4962 self.assertEqual(astz.replace(tzinfo=None), expected)
4963 asutcbase += HOUR
4964
4965
4966 def test_bogus_dst(self):
4967 class ok(tzinfo):
4968 def utcoffset(self, dt): return HOUR
4969 def dst(self, dt): return HOUR
4970
4971 now = self.theclass.now().replace(tzinfo=utc_real)
4972 # Doesn't blow up.
4973 now.astimezone(ok())
4974
4975 # Does blow up.
4976 class notok(ok):
4977 def dst(self, dt): return None
4978 self.assertRaises(ValueError, now.astimezone, notok())
4979
4980 # Sometimes blow up. In the following, tzinfo.dst()
4981 # implementation may return None or not None depending on
4982 # whether DST is assumed to be in effect. In this situation,
4983 # a ValueError should be raised by astimezone().
4984 class tricky_notok(ok):
4985 def dst(self, dt):
4986 if dt.year == 2000:
4987 return None
4988 else:
4989 return 10*HOUR
4990 dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
4991 self.assertRaises(ValueError, dt.astimezone, tricky_notok())
4992
4993 def test_fromutc(self):
4994 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
4995 now = datetime.utcnow().replace(tzinfo=utc_real)
4996 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
4997 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
4998 enow = Eastern.fromutc(now) # doesn't blow up
4999 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
5000 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
5001 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
5002
5003 # Always converts UTC to standard time.
5004 class FauxUSTimeZone(USTimeZone):
5005 def fromutc(self, dt):
5006 return dt + self.stdoffset
5007 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
5008
5009 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
5010 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
5011 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
5012
5013 # Check around DST start.
5014 start = self.dston.replace(hour=4, tzinfo=Eastern)
5015 fstart = start.replace(tzinfo=FEastern)
5016 for wall in 23, 0, 1, 3, 4, 5:
5017 expected = start.replace(hour=wall)
5018 if wall == 23:
5019 expected -= timedelta(days=1)
5020 got = Eastern.fromutc(start)
5021 self.assertEqual(expected, got)
5022
5023 expected = fstart + FEastern.stdoffset
5024 got = FEastern.fromutc(fstart)
5025 self.assertEqual(expected, got)
5026
5027 # Ensure astimezone() calls fromutc() too.
5028 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
5029 self.assertEqual(expected, got)
5030
5031 start += HOUR
5032 fstart += HOUR
5033
5034 # Check around DST end.
5035 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
5036 fstart = start.replace(tzinfo=FEastern)
5037 for wall in 0, 1, 1, 2, 3, 4:
5038 expected = start.replace(hour=wall)
5039 got = Eastern.fromutc(start)
5040 self.assertEqual(expected, got)
5041
5042 expected = fstart + FEastern.stdoffset
5043 got = FEastern.fromutc(fstart)
5044 self.assertEqual(expected, got)
5045
5046 # Ensure astimezone() calls fromutc() too.
5047 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
5048 self.assertEqual(expected, got)
5049
5050 start += HOUR
5051 fstart += HOUR
5052
5053
5054#############################################################################
5055# oddballs
5056
5057class Oddballs(unittest.TestCase):
5058
5059 def test_bug_1028306(self):
5060 # Trying to compare a date to a datetime should act like a mixed-
5061 # type comparison, despite that datetime is a subclass of date.
5062 as_date = date.today()
5063 as_datetime = datetime.combine(as_date, time())
5064 self.assertTrue(as_date != as_datetime)
5065 self.assertTrue(as_datetime != as_date)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02005066 self.assertFalse(as_date == as_datetime)
5067 self.assertFalse(as_datetime == as_date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00005068 self.assertRaises(TypeError, lambda: as_date < as_datetime)
5069 self.assertRaises(TypeError, lambda: as_datetime < as_date)
5070 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
5071 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
5072 self.assertRaises(TypeError, lambda: as_date > as_datetime)
5073 self.assertRaises(TypeError, lambda: as_datetime > as_date)
5074 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
5075 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
5076
Raymond Hettinger15f44ab2016-08-30 10:47:49 -07005077 # Nevertheless, comparison should work with the base-class (date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00005078 # projection if use of a date method is forced.
5079 self.assertEqual(as_date.__eq__(as_datetime), True)
5080 different_day = (as_date.day + 1) % 20 + 1
5081 as_different = as_datetime.replace(day= different_day)
5082 self.assertEqual(as_date.__eq__(as_different), False)
5083
5084 # And date should compare with other subclasses of date. If a
5085 # subclass wants to stop this, it's up to the subclass to do so.
5086 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
5087 self.assertEqual(as_date, date_sc)
5088 self.assertEqual(date_sc, as_date)
5089
5090 # Ditto for datetimes.
5091 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
5092 as_date.day, 0, 0, 0)
5093 self.assertEqual(as_datetime, datetime_sc)
5094 self.assertEqual(datetime_sc, as_datetime)
5095
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04005096 def test_extra_attributes(self):
5097 for x in [date.today(),
5098 time(),
5099 datetime.utcnow(),
5100 timedelta(),
5101 tzinfo(),
5102 timezone(timedelta())]:
5103 with self.assertRaises(AttributeError):
5104 x.abc = 1
5105
5106 def test_check_arg_types(self):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04005107 class Number:
5108 def __init__(self, value):
5109 self.value = value
5110 def __int__(self):
5111 return self.value
5112
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04005113 class Float(float):
5114 pass
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04005115
Serhiy Storchaka578c3952020-05-26 18:43:38 +03005116 for xx in [10.0, Float(10.9),
5117 decimal.Decimal(10), decimal.Decimal('10.9'),
5118 Number(10), Number(10.9),
5119 '10']:
5120 self.assertRaises(TypeError, datetime, xx, 10, 10, 10, 10, 10, 10)
5121 self.assertRaises(TypeError, datetime, 10, xx, 10, 10, 10, 10, 10)
5122 self.assertRaises(TypeError, datetime, 10, 10, xx, 10, 10, 10, 10)
5123 self.assertRaises(TypeError, datetime, 10, 10, 10, xx, 10, 10, 10)
5124 self.assertRaises(TypeError, datetime, 10, 10, 10, 10, xx, 10, 10)
5125 self.assertRaises(TypeError, datetime, 10, 10, 10, 10, 10, xx, 10)
5126 self.assertRaises(TypeError, datetime, 10, 10, 10, 10, 10, 10, xx)
5127
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04005128
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005129#############################################################################
5130# Local Time Disambiguation
5131
5132# An experimental reimplementation of fromutc that respects the "fold" flag.
5133
5134class tzinfo2(tzinfo):
5135
5136 def fromutc(self, dt):
5137 "datetime in UTC -> datetime in local time."
5138
5139 if not isinstance(dt, datetime):
5140 raise TypeError("fromutc() requires a datetime argument")
5141 if dt.tzinfo is not self:
5142 raise ValueError("dt.tzinfo is not self")
5143 # Returned value satisfies
5144 # dt + ldt.utcoffset() = ldt
5145 off0 = dt.replace(fold=0).utcoffset()
5146 off1 = dt.replace(fold=1).utcoffset()
5147 if off0 is None or off1 is None or dt.dst() is None:
5148 raise ValueError
5149 if off0 == off1:
5150 ldt = dt + off0
5151 off1 = ldt.utcoffset()
5152 if off0 == off1:
5153 return ldt
5154 # Now, we discovered both possible offsets, so
5155 # we can just try four possible solutions:
5156 for off in [off0, off1]:
5157 ldt = dt + off
5158 if ldt.utcoffset() == off:
5159 return ldt
5160 ldt = ldt.replace(fold=1)
5161 if ldt.utcoffset() == off:
5162 return ldt
5163
5164 raise ValueError("No suitable local time found")
5165
5166# Reimplementing simplified US timezones to respect the "fold" flag:
5167
5168class USTimeZone2(tzinfo2):
5169
5170 def __init__(self, hours, reprname, stdname, dstname):
5171 self.stdoffset = timedelta(hours=hours)
5172 self.reprname = reprname
5173 self.stdname = stdname
5174 self.dstname = dstname
5175
5176 def __repr__(self):
5177 return self.reprname
5178
5179 def tzname(self, dt):
5180 if self.dst(dt):
5181 return self.dstname
5182 else:
5183 return self.stdname
5184
5185 def utcoffset(self, dt):
5186 return self.stdoffset + self.dst(dt)
5187
5188 def dst(self, dt):
5189 if dt is None or dt.tzinfo is None:
5190 # An exception instead may be sensible here, in one or more of
5191 # the cases.
5192 return ZERO
5193 assert dt.tzinfo is self
5194
5195 # Find first Sunday in April.
5196 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
5197 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
5198
5199 # Find last Sunday in October.
5200 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
5201 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
5202
5203 # Can't compare naive to aware objects, so strip the timezone from
5204 # dt first.
5205 dt = dt.replace(tzinfo=None)
5206 if start + HOUR <= dt < end:
5207 # DST is in effect.
5208 return HOUR
5209 elif end <= dt < end + HOUR:
5210 # Fold (an ambiguous hour): use dt.fold to disambiguate.
5211 return ZERO if dt.fold else HOUR
5212 elif start <= dt < start + HOUR:
5213 # Gap (a non-existent hour): reverse the fold rule.
5214 return HOUR if dt.fold else ZERO
5215 else:
5216 # DST is off.
5217 return ZERO
5218
5219Eastern2 = USTimeZone2(-5, "Eastern2", "EST", "EDT")
5220Central2 = USTimeZone2(-6, "Central2", "CST", "CDT")
5221Mountain2 = USTimeZone2(-7, "Mountain2", "MST", "MDT")
5222Pacific2 = USTimeZone2(-8, "Pacific2", "PST", "PDT")
5223
5224# Europe_Vilnius_1941 tzinfo implementation reproduces the following
5225# 1941 transition from Olson's tzdist:
5226#
5227# Zone NAME GMTOFF RULES FORMAT [UNTIL]
5228# ZoneEurope/Vilnius 1:00 - CET 1940 Aug 3
5229# 3:00 - MSK 1941 Jun 24
5230# 1:00 C-Eur CE%sT 1944 Aug
5231#
5232# $ zdump -v Europe/Vilnius | grep 1941
5233# Europe/Vilnius Mon Jun 23 20:59:59 1941 UTC = Mon Jun 23 23:59:59 1941 MSK isdst=0 gmtoff=10800
5234# Europe/Vilnius Mon Jun 23 21:00:00 1941 UTC = Mon Jun 23 23:00:00 1941 CEST isdst=1 gmtoff=7200
5235
5236class Europe_Vilnius_1941(tzinfo):
5237 def _utc_fold(self):
5238 return [datetime(1941, 6, 23, 21, tzinfo=self), # Mon Jun 23 21:00:00 1941 UTC
5239 datetime(1941, 6, 23, 22, tzinfo=self)] # Mon Jun 23 22:00:00 1941 UTC
5240
5241 def _loc_fold(self):
5242 return [datetime(1941, 6, 23, 23, tzinfo=self), # Mon Jun 23 23:00:00 1941 MSK / CEST
5243 datetime(1941, 6, 24, 0, tzinfo=self)] # Mon Jun 24 00:00:00 1941 CEST
5244
5245 def utcoffset(self, dt):
5246 fold_start, fold_stop = self._loc_fold()
5247 if dt < fold_start:
5248 return 3 * HOUR
5249 if dt < fold_stop:
5250 return (2 if dt.fold else 3) * HOUR
5251 # if dt >= fold_stop
5252 return 2 * HOUR
5253
5254 def dst(self, dt):
5255 fold_start, fold_stop = self._loc_fold()
5256 if dt < fold_start:
5257 return 0 * HOUR
5258 if dt < fold_stop:
5259 return (1 if dt.fold else 0) * HOUR
5260 # if dt >= fold_stop
5261 return 1 * HOUR
5262
5263 def tzname(self, dt):
5264 fold_start, fold_stop = self._loc_fold()
5265 if dt < fold_start:
5266 return 'MSK'
5267 if dt < fold_stop:
5268 return ('MSK', 'CEST')[dt.fold]
5269 # if dt >= fold_stop
5270 return 'CEST'
5271
5272 def fromutc(self, dt):
5273 assert dt.fold == 0
5274 assert dt.tzinfo is self
5275 if dt.year != 1941:
5276 raise NotImplementedError
5277 fold_start, fold_stop = self._utc_fold()
5278 if dt < fold_start:
5279 return dt + 3 * HOUR
5280 if dt < fold_stop:
5281 return (dt + 2 * HOUR).replace(fold=1)
5282 # if dt >= fold_stop
5283 return dt + 2 * HOUR
5284
5285
5286class TestLocalTimeDisambiguation(unittest.TestCase):
5287
5288 def test_vilnius_1941_fromutc(self):
5289 Vilnius = Europe_Vilnius_1941()
5290
5291 gdt = datetime(1941, 6, 23, 20, 59, 59, tzinfo=timezone.utc)
5292 ldt = gdt.astimezone(Vilnius)
5293 self.assertEqual(ldt.strftime("%c %Z%z"),
5294 'Mon Jun 23 23:59:59 1941 MSK+0300')
5295 self.assertEqual(ldt.fold, 0)
5296 self.assertFalse(ldt.dst())
5297
5298 gdt = datetime(1941, 6, 23, 21, tzinfo=timezone.utc)
5299 ldt = gdt.astimezone(Vilnius)
5300 self.assertEqual(ldt.strftime("%c %Z%z"),
5301 'Mon Jun 23 23:00:00 1941 CEST+0200')
5302 self.assertEqual(ldt.fold, 1)
5303 self.assertTrue(ldt.dst())
5304
5305 gdt = datetime(1941, 6, 23, 22, tzinfo=timezone.utc)
5306 ldt = gdt.astimezone(Vilnius)
5307 self.assertEqual(ldt.strftime("%c %Z%z"),
5308 'Tue Jun 24 00:00:00 1941 CEST+0200')
5309 self.assertEqual(ldt.fold, 0)
5310 self.assertTrue(ldt.dst())
5311
5312 def test_vilnius_1941_toutc(self):
5313 Vilnius = Europe_Vilnius_1941()
5314
5315 ldt = datetime(1941, 6, 23, 22, 59, 59, tzinfo=Vilnius)
5316 gdt = ldt.astimezone(timezone.utc)
5317 self.assertEqual(gdt.strftime("%c %Z"),
5318 'Mon Jun 23 19:59:59 1941 UTC')
5319
5320 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius)
5321 gdt = ldt.astimezone(timezone.utc)
5322 self.assertEqual(gdt.strftime("%c %Z"),
5323 'Mon Jun 23 20:59:59 1941 UTC')
5324
5325 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius, fold=1)
5326 gdt = ldt.astimezone(timezone.utc)
5327 self.assertEqual(gdt.strftime("%c %Z"),
5328 'Mon Jun 23 21:59:59 1941 UTC')
5329
5330 ldt = datetime(1941, 6, 24, 0, tzinfo=Vilnius)
5331 gdt = ldt.astimezone(timezone.utc)
5332 self.assertEqual(gdt.strftime("%c %Z"),
5333 'Mon Jun 23 22:00:00 1941 UTC')
5334
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005335 def test_constructors(self):
5336 t = time(0, fold=1)
5337 dt = datetime(1, 1, 1, fold=1)
5338 self.assertEqual(t.fold, 1)
5339 self.assertEqual(dt.fold, 1)
5340 with self.assertRaises(TypeError):
5341 time(0, 0, 0, 0, None, 0)
5342
5343 def test_member(self):
5344 dt = datetime(1, 1, 1, fold=1)
5345 t = dt.time()
5346 self.assertEqual(t.fold, 1)
5347 t = dt.timetz()
5348 self.assertEqual(t.fold, 1)
5349
5350 def test_replace(self):
5351 t = time(0)
5352 dt = datetime(1, 1, 1)
5353 self.assertEqual(t.replace(fold=1).fold, 1)
5354 self.assertEqual(dt.replace(fold=1).fold, 1)
5355 self.assertEqual(t.replace(fold=0).fold, 0)
5356 self.assertEqual(dt.replace(fold=0).fold, 0)
5357 # Check that replacement of other fields does not change "fold".
5358 t = t.replace(fold=1, tzinfo=Eastern)
5359 dt = dt.replace(fold=1, tzinfo=Eastern)
5360 self.assertEqual(t.replace(tzinfo=None).fold, 1)
5361 self.assertEqual(dt.replace(tzinfo=None).fold, 1)
Serhiy Storchaka314d6fc2017-03-31 22:48:16 +03005362 # Out of bounds.
5363 with self.assertRaises(ValueError):
5364 t.replace(fold=2)
5365 with self.assertRaises(ValueError):
5366 dt.replace(fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005367 # Check that fold is a keyword-only argument
5368 with self.assertRaises(TypeError):
5369 t.replace(1, 1, 1, None, 1)
5370 with self.assertRaises(TypeError):
5371 dt.replace(1, 1, 1, 1, 1, 1, 1, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04005372
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005373 def test_comparison(self):
5374 t = time(0)
5375 dt = datetime(1, 1, 1)
5376 self.assertEqual(t, t.replace(fold=1))
5377 self.assertEqual(dt, dt.replace(fold=1))
5378
5379 def test_hash(self):
5380 t = time(0)
5381 dt = datetime(1, 1, 1)
5382 self.assertEqual(hash(t), hash(t.replace(fold=1)))
5383 self.assertEqual(hash(dt), hash(dt.replace(fold=1)))
5384
5385 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5386 def test_fromtimestamp(self):
5387 s = 1414906200
5388 dt0 = datetime.fromtimestamp(s)
5389 dt1 = datetime.fromtimestamp(s + 3600)
5390 self.assertEqual(dt0.fold, 0)
5391 self.assertEqual(dt1.fold, 1)
5392
5393 @support.run_with_tz('Australia/Lord_Howe')
5394 def test_fromtimestamp_lord_howe(self):
5395 tm = _time.localtime(1.4e9)
5396 if _time.strftime('%Z%z', tm) != 'LHST+1030':
5397 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
5398 # $ TZ=Australia/Lord_Howe date -r 1428158700
5399 # Sun Apr 5 01:45:00 LHDT 2015
5400 # $ TZ=Australia/Lord_Howe date -r 1428160500
5401 # Sun Apr 5 01:45:00 LHST 2015
5402 s = 1428158700
5403 t0 = datetime.fromtimestamp(s)
5404 t1 = datetime.fromtimestamp(s + 1800)
5405 self.assertEqual(t0, t1)
5406 self.assertEqual(t0.fold, 0)
5407 self.assertEqual(t1.fold, 1)
5408
Ammar Askar96d1e692018-07-25 09:54:58 -07005409 def test_fromtimestamp_low_fold_detection(self):
5410 # Ensure that fold detection doesn't cause an
5411 # OSError for really low values, see bpo-29097
5412 self.assertEqual(datetime.fromtimestamp(0).fold, 0)
5413
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005414 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5415 def test_timestamp(self):
5416 dt0 = datetime(2014, 11, 2, 1, 30)
5417 dt1 = dt0.replace(fold=1)
5418 self.assertEqual(dt0.timestamp() + 3600,
5419 dt1.timestamp())
5420
5421 @support.run_with_tz('Australia/Lord_Howe')
5422 def test_timestamp_lord_howe(self):
5423 tm = _time.localtime(1.4e9)
5424 if _time.strftime('%Z%z', tm) != 'LHST+1030':
5425 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
5426 t = datetime(2015, 4, 5, 1, 45)
5427 s0 = t.replace(fold=0).timestamp()
5428 s1 = t.replace(fold=1).timestamp()
5429 self.assertEqual(s0 + 1800, s1)
5430
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005431 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5432 def test_astimezone(self):
5433 dt0 = datetime(2014, 11, 2, 1, 30)
5434 dt1 = dt0.replace(fold=1)
5435 # Convert both naive instances to aware.
5436 adt0 = dt0.astimezone()
5437 adt1 = dt1.astimezone()
5438 # Check that the first instance in DST zone and the second in STD
5439 self.assertEqual(adt0.tzname(), 'EDT')
5440 self.assertEqual(adt1.tzname(), 'EST')
5441 self.assertEqual(adt0 + HOUR, adt1)
5442 # Aware instances with fixed offset tzinfo's always have fold=0
5443 self.assertEqual(adt0.fold, 0)
5444 self.assertEqual(adt1.fold, 0)
5445
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005446 def test_pickle_fold(self):
5447 t = time(fold=1)
5448 dt = datetime(1, 1, 1, fold=1)
5449 for pickler, unpickler, proto in pickle_choices:
5450 for x in [t, dt]:
5451 s = pickler.dumps(x, proto)
5452 y = unpickler.loads(s)
5453 self.assertEqual(x, y)
5454 self.assertEqual((0 if proto < 4 else x.fold), y.fold)
5455
5456 def test_repr(self):
5457 t = time(fold=1)
5458 dt = datetime(1, 1, 1, fold=1)
5459 self.assertEqual(repr(t), 'datetime.time(0, 0, fold=1)')
5460 self.assertEqual(repr(dt),
5461 'datetime.datetime(1, 1, 1, 0, 0, fold=1)')
5462
5463 def test_dst(self):
5464 # Let's first establish that things work in regular times.
5465 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
5466 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
5467 self.assertEqual(dt_summer.dst(), HOUR)
5468 self.assertEqual(dt_winter.dst(), ZERO)
5469 # The disambiguation flag is ignored
5470 self.assertEqual(dt_summer.replace(fold=1).dst(), HOUR)
5471 self.assertEqual(dt_winter.replace(fold=1).dst(), ZERO)
5472
5473 # Pick local time in the fold.
5474 for minute in [0, 30, 59]:
5475 dt = datetime(2002, 10, 27, 1, minute, tzinfo=Eastern2)
5476 # With fold=0 (the default) it is in DST.
5477 self.assertEqual(dt.dst(), HOUR)
5478 # With fold=1 it is in STD.
5479 self.assertEqual(dt.replace(fold=1).dst(), ZERO)
5480
5481 # Pick local time in the gap.
5482 for minute in [0, 30, 59]:
5483 dt = datetime(2002, 4, 7, 2, minute, tzinfo=Eastern2)
5484 # With fold=0 (the default) it is in STD.
5485 self.assertEqual(dt.dst(), ZERO)
5486 # With fold=1 it is in DST.
5487 self.assertEqual(dt.replace(fold=1).dst(), HOUR)
5488
5489
5490 def test_utcoffset(self):
5491 # Let's first establish that things work in regular times.
5492 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
5493 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
5494 self.assertEqual(dt_summer.utcoffset(), -4 * HOUR)
5495 self.assertEqual(dt_winter.utcoffset(), -5 * HOUR)
5496 # The disambiguation flag is ignored
5497 self.assertEqual(dt_summer.replace(fold=1).utcoffset(), -4 * HOUR)
5498 self.assertEqual(dt_winter.replace(fold=1).utcoffset(), -5 * HOUR)
5499
5500 def test_fromutc(self):
5501 # Let's first establish that things work in regular times.
5502 u_summer = datetime(2002, 10, 27, 6, tzinfo=Eastern2) - timedelta.resolution
5503 u_winter = datetime(2002, 10, 27, 7, tzinfo=Eastern2)
5504 t_summer = Eastern2.fromutc(u_summer)
5505 t_winter = Eastern2.fromutc(u_winter)
5506 self.assertEqual(t_summer, u_summer - 4 * HOUR)
5507 self.assertEqual(t_winter, u_winter - 5 * HOUR)
5508 self.assertEqual(t_summer.fold, 0)
5509 self.assertEqual(t_winter.fold, 0)
5510
5511 # What happens in the fall-back fold?
5512 u = datetime(2002, 10, 27, 5, 30, tzinfo=Eastern2)
5513 t0 = Eastern2.fromutc(u)
5514 u += HOUR
5515 t1 = Eastern2.fromutc(u)
5516 self.assertEqual(t0, t1)
5517 self.assertEqual(t0.fold, 0)
5518 self.assertEqual(t1.fold, 1)
5519 # The tricky part is when u is in the local fold:
5520 u = datetime(2002, 10, 27, 1, 30, tzinfo=Eastern2)
5521 t = Eastern2.fromutc(u)
5522 self.assertEqual((t.day, t.hour), (26, 21))
5523 # .. or gets into the local fold after a standard time adjustment
5524 u = datetime(2002, 10, 27, 6, 30, tzinfo=Eastern2)
5525 t = Eastern2.fromutc(u)
5526 self.assertEqual((t.day, t.hour), (27, 1))
5527
5528 # What happens in the spring-forward gap?
5529 u = datetime(2002, 4, 7, 2, 0, tzinfo=Eastern2)
5530 t = Eastern2.fromutc(u)
5531 self.assertEqual((t.day, t.hour), (6, 21))
5532
5533 def test_mixed_compare_regular(self):
5534 t = datetime(2000, 1, 1, tzinfo=Eastern2)
5535 self.assertEqual(t, t.astimezone(timezone.utc))
5536 t = datetime(2000, 6, 1, tzinfo=Eastern2)
5537 self.assertEqual(t, t.astimezone(timezone.utc))
5538
5539 def test_mixed_compare_fold(self):
5540 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
5541 t_fold_utc = t_fold.astimezone(timezone.utc)
5542 self.assertNotEqual(t_fold, t_fold_utc)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04005543 self.assertNotEqual(t_fold_utc, t_fold)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005544
5545 def test_mixed_compare_gap(self):
5546 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
5547 t_gap_utc = t_gap.astimezone(timezone.utc)
5548 self.assertNotEqual(t_gap, t_gap_utc)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04005549 self.assertNotEqual(t_gap_utc, t_gap)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005550
5551 def test_hash_aware(self):
5552 t = datetime(2000, 1, 1, tzinfo=Eastern2)
5553 self.assertEqual(hash(t), hash(t.replace(fold=1)))
5554 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
5555 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
5556 self.assertEqual(hash(t_fold), hash(t_fold.replace(fold=1)))
5557 self.assertEqual(hash(t_gap), hash(t_gap.replace(fold=1)))
5558
5559SEC = timedelta(0, 1)
5560
5561def pairs(iterable):
5562 a, b = itertools.tee(iterable)
5563 next(b, None)
5564 return zip(a, b)
5565
5566class ZoneInfo(tzinfo):
5567 zoneroot = '/usr/share/zoneinfo'
5568 def __init__(self, ut, ti):
5569 """
5570
5571 :param ut: array
5572 Array of transition point timestamps
5573 :param ti: list
5574 A list of (offset, isdst, abbr) tuples
5575 :return: None
5576 """
5577 self.ut = ut
5578 self.ti = ti
5579 self.lt = self.invert(ut, ti)
5580
5581 @staticmethod
5582 def invert(ut, ti):
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04005583 lt = (array('q', ut), array('q', ut))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005584 if ut:
5585 offset = ti[0][0] // SEC
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04005586 lt[0][0] += offset
5587 lt[1][0] += offset
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005588 for i in range(1, len(ut)):
5589 lt[0][i] += ti[i-1][0] // SEC
5590 lt[1][i] += ti[i][0] // SEC
5591 return lt
5592
5593 @classmethod
5594 def fromfile(cls, fileobj):
5595 if fileobj.read(4).decode() != "TZif":
5596 raise ValueError("not a zoneinfo file")
5597 fileobj.seek(32)
5598 counts = array('i')
5599 counts.fromfile(fileobj, 3)
5600 if sys.byteorder != 'big':
5601 counts.byteswap()
5602
5603 ut = array('i')
5604 ut.fromfile(fileobj, counts[0])
5605 if sys.byteorder != 'big':
5606 ut.byteswap()
5607
5608 type_indices = array('B')
5609 type_indices.fromfile(fileobj, counts[0])
5610
5611 ttis = []
5612 for i in range(counts[1]):
5613 ttis.append(struct.unpack(">lbb", fileobj.read(6)))
5614
5615 abbrs = fileobj.read(counts[2])
5616
5617 # Convert ttis
5618 for i, (gmtoff, isdst, abbrind) in enumerate(ttis):
5619 abbr = abbrs[abbrind:abbrs.find(0, abbrind)].decode()
5620 ttis[i] = (timedelta(0, gmtoff), isdst, abbr)
5621
5622 ti = [None] * len(ut)
5623 for i, idx in enumerate(type_indices):
5624 ti[i] = ttis[idx]
5625
5626 self = cls(ut, ti)
5627
5628 return self
5629
5630 @classmethod
5631 def fromname(cls, name):
5632 path = os.path.join(cls.zoneroot, name)
5633 with open(path, 'rb') as f:
5634 return cls.fromfile(f)
5635
5636 EPOCHORDINAL = date(1970, 1, 1).toordinal()
5637
5638 def fromutc(self, dt):
5639 """datetime in UTC -> datetime in local time."""
5640
5641 if not isinstance(dt, datetime):
5642 raise TypeError("fromutc() requires a datetime argument")
5643 if dt.tzinfo is not self:
5644 raise ValueError("dt.tzinfo is not self")
5645
5646 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
5647 + dt.hour * 3600
5648 + dt.minute * 60
5649 + dt.second)
5650
5651 if timestamp < self.ut[1]:
5652 tti = self.ti[0]
5653 fold = 0
5654 else:
5655 idx = bisect.bisect_right(self.ut, timestamp)
5656 assert self.ut[idx-1] <= timestamp
5657 assert idx == len(self.ut) or timestamp < self.ut[idx]
5658 tti_prev, tti = self.ti[idx-2:idx]
5659 # Detect fold
5660 shift = tti_prev[0] - tti[0]
5661 fold = (shift > timedelta(0, timestamp - self.ut[idx-1]))
5662 dt += tti[0]
5663 if fold:
5664 return dt.replace(fold=1)
5665 else:
5666 return dt
5667
5668 def _find_ti(self, dt, i):
5669 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
5670 + dt.hour * 3600
5671 + dt.minute * 60
5672 + dt.second)
5673 lt = self.lt[dt.fold]
5674 idx = bisect.bisect_right(lt, timestamp)
5675
5676 return self.ti[max(0, idx - 1)][i]
5677
5678 def utcoffset(self, dt):
5679 return self._find_ti(dt, 0)
5680
5681 def dst(self, dt):
5682 isdst = self._find_ti(dt, 1)
5683 # XXX: We cannot accurately determine the "save" value,
5684 # so let's return 1h whenever DST is in effect. Since
5685 # we don't use dst() in fromutc(), it is unlikely that
5686 # it will be needed for anything more than bool(dst()).
5687 return ZERO if isdst else HOUR
5688
5689 def tzname(self, dt):
5690 return self._find_ti(dt, 2)
5691
5692 @classmethod
5693 def zonenames(cls, zonedir=None):
5694 if zonedir is None:
5695 zonedir = cls.zoneroot
Alexander Belopolsky1b8f26c2016-08-11 11:01:45 -04005696 zone_tab = os.path.join(zonedir, 'zone.tab')
5697 try:
5698 f = open(zone_tab)
5699 except OSError:
5700 return
5701 with f:
5702 for line in f:
5703 line = line.strip()
5704 if line and not line.startswith('#'):
5705 yield line.split()[2]
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005706
5707 @classmethod
5708 def stats(cls, start_year=1):
5709 count = gap_count = fold_count = zeros_count = 0
5710 min_gap = min_fold = timedelta.max
5711 max_gap = max_fold = ZERO
5712 min_gap_datetime = max_gap_datetime = datetime.min
5713 min_gap_zone = max_gap_zone = None
5714 min_fold_datetime = max_fold_datetime = datetime.min
5715 min_fold_zone = max_fold_zone = None
5716 stats_since = datetime(start_year, 1, 1) # Starting from 1970 eliminates a lot of noise
5717 for zonename in cls.zonenames():
5718 count += 1
5719 tz = cls.fromname(zonename)
5720 for dt, shift in tz.transitions():
5721 if dt < stats_since:
5722 continue
5723 if shift > ZERO:
5724 gap_count += 1
5725 if (shift, dt) > (max_gap, max_gap_datetime):
5726 max_gap = shift
5727 max_gap_zone = zonename
5728 max_gap_datetime = dt
5729 if (shift, datetime.max - dt) < (min_gap, datetime.max - min_gap_datetime):
5730 min_gap = shift
5731 min_gap_zone = zonename
5732 min_gap_datetime = dt
5733 elif shift < ZERO:
5734 fold_count += 1
5735 shift = -shift
5736 if (shift, dt) > (max_fold, max_fold_datetime):
5737 max_fold = shift
5738 max_fold_zone = zonename
5739 max_fold_datetime = dt
5740 if (shift, datetime.max - dt) < (min_fold, datetime.max - min_fold_datetime):
5741 min_fold = shift
5742 min_fold_zone = zonename
5743 min_fold_datetime = dt
5744 else:
5745 zeros_count += 1
5746 trans_counts = (gap_count, fold_count, zeros_count)
5747 print("Number of zones: %5d" % count)
5748 print("Number of transitions: %5d = %d (gaps) + %d (folds) + %d (zeros)" %
5749 ((sum(trans_counts),) + trans_counts))
5750 print("Min gap: %16s at %s in %s" % (min_gap, min_gap_datetime, min_gap_zone))
5751 print("Max gap: %16s at %s in %s" % (max_gap, max_gap_datetime, max_gap_zone))
5752 print("Min fold: %16s at %s in %s" % (min_fold, min_fold_datetime, min_fold_zone))
5753 print("Max fold: %16s at %s in %s" % (max_fold, max_fold_datetime, max_fold_zone))
5754
5755
5756 def transitions(self):
5757 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
5758 shift = ti[0] - prev_ti[0]
5759 yield datetime.utcfromtimestamp(t), shift
5760
5761 def nondst_folds(self):
5762 """Find all folds with the same value of isdst on both sides of the transition."""
5763 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
5764 shift = ti[0] - prev_ti[0]
5765 if shift < ZERO and ti[1] == prev_ti[1]:
5766 yield datetime.utcfromtimestamp(t), -shift, prev_ti[2], ti[2]
5767
5768 @classmethod
5769 def print_all_nondst_folds(cls, same_abbr=False, start_year=1):
5770 count = 0
5771 for zonename in cls.zonenames():
5772 tz = cls.fromname(zonename)
5773 for dt, shift, prev_abbr, abbr in tz.nondst_folds():
5774 if dt.year < start_year or same_abbr and prev_abbr != abbr:
5775 continue
5776 count += 1
5777 print("%3d) %-30s %s %10s %5s -> %s" %
5778 (count, zonename, dt, shift, prev_abbr, abbr))
5779
5780 def folds(self):
5781 for t, shift in self.transitions():
5782 if shift < ZERO:
5783 yield t, -shift
5784
5785 def gaps(self):
5786 for t, shift in self.transitions():
5787 if shift > ZERO:
5788 yield t, shift
5789
5790 def zeros(self):
5791 for t, shift in self.transitions():
5792 if not shift:
5793 yield t
5794
5795
5796class ZoneInfoTest(unittest.TestCase):
5797 zonename = 'America/New_York'
5798
5799 def setUp(self):
hliu08e7ff6a2019-09-10 18:28:11 +08005800 if sys.platform == "vxworks":
5801 self.skipTest("Skipping zoneinfo tests on VxWorks")
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005802 if sys.platform == "win32":
5803 self.skipTest("Skipping zoneinfo tests on Windows")
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04005804 try:
5805 self.tz = ZoneInfo.fromname(self.zonename)
5806 except FileNotFoundError as err:
5807 self.skipTest("Skipping %s: %s" % (self.zonename, err))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005808
5809 def assertEquivDatetimes(self, a, b):
5810 self.assertEqual((a.replace(tzinfo=None), a.fold, id(a.tzinfo)),
5811 (b.replace(tzinfo=None), b.fold, id(b.tzinfo)))
5812
5813 def test_folds(self):
5814 tz = self.tz
5815 for dt, shift in tz.folds():
5816 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
5817 udt = dt + x
5818 ldt = tz.fromutc(udt.replace(tzinfo=tz))
5819 self.assertEqual(ldt.fold, 1)
5820 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
5821 self.assertEquivDatetimes(adt, ldt)
5822 utcoffset = ldt.utcoffset()
5823 self.assertEqual(ldt.replace(tzinfo=None), udt + utcoffset)
5824 # Round trip
5825 self.assertEquivDatetimes(ldt.astimezone(timezone.utc),
5826 udt.replace(tzinfo=timezone.utc))
5827
5828
5829 for x in [-timedelta.resolution, shift]:
5830 udt = dt + x
5831 udt = udt.replace(tzinfo=tz)
5832 ldt = tz.fromutc(udt)
5833 self.assertEqual(ldt.fold, 0)
5834
5835 def test_gaps(self):
5836 tz = self.tz
5837 for dt, shift in tz.gaps():
5838 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
5839 udt = dt + x
5840 udt = udt.replace(tzinfo=tz)
5841 ldt = tz.fromutc(udt)
5842 self.assertEqual(ldt.fold, 0)
5843 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
5844 self.assertEquivDatetimes(adt, ldt)
5845 utcoffset = ldt.utcoffset()
5846 self.assertEqual(ldt.replace(tzinfo=None), udt.replace(tzinfo=None) + utcoffset)
5847 # Create a local time inside the gap
5848 ldt = tz.fromutc(dt.replace(tzinfo=tz)) - shift + x
5849 self.assertLess(ldt.replace(fold=1).utcoffset(),
5850 ldt.replace(fold=0).utcoffset(),
5851 "At %s." % ldt)
5852
5853 for x in [-timedelta.resolution, shift]:
5854 udt = dt + x
5855 ldt = tz.fromutc(udt.replace(tzinfo=tz))
5856 self.assertEqual(ldt.fold, 0)
5857
5858 def test_system_transitions(self):
5859 if ('Riyadh8' in self.zonename or
5860 # From tzdata NEWS file:
5861 # The files solar87, solar88, and solar89 are no longer distributed.
5862 # They were a negative experiment - that is, a demonstration that
5863 # tz data can represent solar time only with some difficulty and error.
5864 # Their presence in the distribution caused confusion, as Riyadh
5865 # civil time was generally not solar time in those years.
5866 self.zonename.startswith('right/')):
5867 self.skipTest("Skipping %s" % self.zonename)
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04005868 tz = self.tz
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005869 TZ = os.environ.get('TZ')
5870 os.environ['TZ'] = self.zonename
5871 try:
5872 _time.tzset()
5873 for udt, shift in tz.transitions():
Alexander Belopolsky10c2dd22016-08-12 19:08:15 -04005874 if udt.year >= 2037:
5875 # System support for times around the end of 32-bit time_t
5876 # and later is flaky on many systems.
5877 break
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005878 s0 = (udt - datetime(1970, 1, 1)) // SEC
5879 ss = shift // SEC # shift seconds
5880 for x in [-40 * 3600, -20*3600, -1, 0,
5881 ss - 1, ss + 20 * 3600, ss + 40 * 3600]:
5882 s = s0 + x
5883 sdt = datetime.fromtimestamp(s)
5884 tzdt = datetime.fromtimestamp(s, tz).replace(tzinfo=None)
5885 self.assertEquivDatetimes(sdt, tzdt)
5886 s1 = sdt.timestamp()
5887 self.assertEqual(s, s1)
5888 if ss > 0: # gap
5889 # Create local time inside the gap
5890 dt = datetime.fromtimestamp(s0) - shift / 2
5891 ts0 = dt.timestamp()
5892 ts1 = dt.replace(fold=1).timestamp()
5893 self.assertEqual(ts0, s0 + ss / 2)
5894 self.assertEqual(ts1, s0 - ss / 2)
5895 finally:
5896 if TZ is None:
5897 del os.environ['TZ']
5898 else:
5899 os.environ['TZ'] = TZ
5900 _time.tzset()
5901
5902
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04005903class ZoneInfoCompleteTest(unittest.TestSuite):
5904 def __init__(self):
5905 tests = []
5906 if is_resource_enabled('tzdata'):
5907 for name in ZoneInfo.zonenames():
5908 Test = type('ZoneInfoTest[%s]' % name, (ZoneInfoTest,), {})
5909 Test.zonename = name
5910 for method in dir(Test):
5911 if method.startswith('test_'):
5912 tests.append(Test(method))
5913 super().__init__(tests)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005914
5915# Iran had a sub-minute UTC offset before 1946.
5916class IranTest(ZoneInfoTest):
Alexander Belopolsky07e2a0a2016-07-30 11:41:02 -04005917 zonename = 'Asia/Tehran'
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005918
Paul Ganssle04af5b12018-01-24 17:29:30 -05005919
5920class CapiTest(unittest.TestCase):
5921 def setUp(self):
5922 # Since the C API is not present in the _Pure tests, skip all tests
5923 if self.__class__.__name__.endswith('Pure'):
5924 self.skipTest('Not relevant in pure Python')
5925
5926 # This *must* be called, and it must be called first, so until either
5927 # restriction is loosened, we'll call it as part of test setup
5928 _testcapi.test_datetime_capi()
5929
5930 def test_utc_capi(self):
5931 for use_macro in (True, False):
5932 capi_utc = _testcapi.get_timezone_utc_capi(use_macro)
5933
5934 with self.subTest(use_macro=use_macro):
5935 self.assertIs(capi_utc, timezone.utc)
5936
5937 def test_timezones_capi(self):
5938 est_capi, est_macro, est_macro_nn = _testcapi.make_timezones_capi()
5939
5940 exp_named = timezone(timedelta(hours=-5), "EST")
5941 exp_unnamed = timezone(timedelta(hours=-5))
5942
5943 cases = [
5944 ('est_capi', est_capi, exp_named),
5945 ('est_macro', est_macro, exp_named),
5946 ('est_macro_nn', est_macro_nn, exp_unnamed)
5947 ]
5948
5949 for name, tz_act, tz_exp in cases:
5950 with self.subTest(name=name):
5951 self.assertEqual(tz_act, tz_exp)
5952
5953 dt1 = datetime(2000, 2, 4, tzinfo=tz_act)
5954 dt2 = datetime(2000, 2, 4, tzinfo=tz_exp)
5955
5956 self.assertEqual(dt1, dt2)
5957 self.assertEqual(dt1.tzname(), dt2.tzname())
5958
5959 dt_utc = datetime(2000, 2, 4, 5, tzinfo=timezone.utc)
5960
5961 self.assertEqual(dt1.astimezone(timezone.utc), dt_utc)
5962
Joannah Nanjekye2c5fb172019-08-29 09:54:46 -03005963 def test_PyDateTime_DELTA_GET(self):
5964 class TimeDeltaSubclass(timedelta):
5965 pass
5966
5967 for klass in [timedelta, TimeDeltaSubclass]:
5968 for args in [(26, 55, 99999), (26, 55, 99999)]:
5969 d = klass(*args)
5970 with self.subTest(cls=klass, date=args):
5971 days, seconds, microseconds = _testcapi.PyDateTime_DELTA_GET(d)
5972
5973 self.assertEqual(days, d.days)
5974 self.assertEqual(seconds, d.seconds)
5975 self.assertEqual(microseconds, d.microseconds)
5976
5977 def test_PyDateTime_GET(self):
5978 class DateSubclass(date):
5979 pass
5980
5981 for klass in [date, DateSubclass]:
5982 for args in [(2000, 1, 2), (2012, 2, 29)]:
5983 d = klass(*args)
5984 with self.subTest(cls=klass, date=args):
5985 year, month, day = _testcapi.PyDateTime_GET(d)
5986
5987 self.assertEqual(year, d.year)
5988 self.assertEqual(month, d.month)
5989 self.assertEqual(day, d.day)
5990
5991 def test_PyDateTime_DATE_GET(self):
5992 class DateTimeSubclass(datetime):
5993 pass
5994
5995 for klass in [datetime, DateTimeSubclass]:
5996 for args in [(1993, 8, 26, 22, 12, 55, 99999),
Zackery Spytz2e4dd332020-09-23 12:43:45 -06005997 (1993, 8, 26, 22, 12, 55, 99999,
5998 timezone.utc)]:
Joannah Nanjekye2c5fb172019-08-29 09:54:46 -03005999 d = klass(*args)
6000 with self.subTest(cls=klass, date=args):
Zackery Spytz2e4dd332020-09-23 12:43:45 -06006001 hour, minute, second, microsecond, tzinfo = \
6002 _testcapi.PyDateTime_DATE_GET(d)
Joannah Nanjekye2c5fb172019-08-29 09:54:46 -03006003
6004 self.assertEqual(hour, d.hour)
6005 self.assertEqual(minute, d.minute)
6006 self.assertEqual(second, d.second)
6007 self.assertEqual(microsecond, d.microsecond)
Zackery Spytz2e4dd332020-09-23 12:43:45 -06006008 self.assertIs(tzinfo, d.tzinfo)
Joannah Nanjekye2c5fb172019-08-29 09:54:46 -03006009
6010 def test_PyDateTime_TIME_GET(self):
6011 class TimeSubclass(time):
6012 pass
6013
6014 for klass in [time, TimeSubclass]:
Zackery Spytz2e4dd332020-09-23 12:43:45 -06006015 for args in [(12, 30, 20, 10),
6016 (12, 30, 20, 10, timezone.utc)]:
Joannah Nanjekye2c5fb172019-08-29 09:54:46 -03006017 d = klass(*args)
6018 with self.subTest(cls=klass, date=args):
Zackery Spytz2e4dd332020-09-23 12:43:45 -06006019 hour, minute, second, microsecond, tzinfo = \
6020 _testcapi.PyDateTime_TIME_GET(d)
Joannah Nanjekye2c5fb172019-08-29 09:54:46 -03006021
6022 self.assertEqual(hour, d.hour)
6023 self.assertEqual(minute, d.minute)
6024 self.assertEqual(second, d.second)
6025 self.assertEqual(microsecond, d.microsecond)
Zackery Spytz2e4dd332020-09-23 12:43:45 -06006026 self.assertIs(tzinfo, d.tzinfo)
Joannah Nanjekye2c5fb172019-08-29 09:54:46 -03006027
Paul Gansslea049f572018-02-22 15:15:32 -05006028 def test_timezones_offset_zero(self):
6029 utc0, utc1, non_utc = _testcapi.get_timezones_offset_zero()
6030
6031 with self.subTest(testname="utc0"):
6032 self.assertIs(utc0, timezone.utc)
6033
6034 with self.subTest(testname="utc1"):
6035 self.assertIs(utc1, timezone.utc)
6036
6037 with self.subTest(testname="non_utc"):
6038 self.assertIsNot(non_utc, timezone.utc)
6039
6040 non_utc_exp = timezone(timedelta(hours=0), "")
6041
6042 self.assertEqual(non_utc, non_utc_exp)
6043
6044 dt1 = datetime(2000, 2, 4, tzinfo=non_utc)
6045 dt2 = datetime(2000, 2, 4, tzinfo=non_utc_exp)
6046
6047 self.assertEqual(dt1, dt2)
6048 self.assertEqual(dt1.tzname(), dt2.tzname())
6049
Paul Ganssle04af5b12018-01-24 17:29:30 -05006050 def test_check_date(self):
6051 class DateSubclass(date):
6052 pass
6053
6054 d = date(2011, 1, 1)
6055 ds = DateSubclass(2011, 1, 1)
6056 dt = datetime(2011, 1, 1)
6057
6058 is_date = _testcapi.datetime_check_date
6059
6060 # Check the ones that should be valid
6061 self.assertTrue(is_date(d))
6062 self.assertTrue(is_date(dt))
6063 self.assertTrue(is_date(ds))
6064 self.assertTrue(is_date(d, True))
6065
6066 # Check that the subclasses do not match exactly
6067 self.assertFalse(is_date(dt, True))
6068 self.assertFalse(is_date(ds, True))
6069
6070 # Check that various other things are not dates at all
6071 args = [tuple(), list(), 1, '2011-01-01',
6072 timedelta(1), timezone.utc, time(12, 00)]
6073 for arg in args:
6074 for exact in (True, False):
6075 with self.subTest(arg=arg, exact=exact):
6076 self.assertFalse(is_date(arg, exact))
6077
6078 def test_check_time(self):
6079 class TimeSubclass(time):
6080 pass
6081
6082 t = time(12, 30)
6083 ts = TimeSubclass(12, 30)
6084
6085 is_time = _testcapi.datetime_check_time
6086
6087 # Check the ones that should be valid
6088 self.assertTrue(is_time(t))
6089 self.assertTrue(is_time(ts))
6090 self.assertTrue(is_time(t, True))
6091
6092 # Check that the subclass does not match exactly
6093 self.assertFalse(is_time(ts, True))
6094
6095 # Check that various other things are not times
6096 args = [tuple(), list(), 1, '2011-01-01',
6097 timedelta(1), timezone.utc, date(2011, 1, 1)]
6098
6099 for arg in args:
6100 for exact in (True, False):
6101 with self.subTest(arg=arg, exact=exact):
6102 self.assertFalse(is_time(arg, exact))
6103
6104 def test_check_datetime(self):
6105 class DateTimeSubclass(datetime):
6106 pass
6107
6108 dt = datetime(2011, 1, 1, 12, 30)
6109 dts = DateTimeSubclass(2011, 1, 1, 12, 30)
6110
6111 is_datetime = _testcapi.datetime_check_datetime
6112
6113 # Check the ones that should be valid
6114 self.assertTrue(is_datetime(dt))
6115 self.assertTrue(is_datetime(dts))
6116 self.assertTrue(is_datetime(dt, True))
6117
6118 # Check that the subclass does not match exactly
6119 self.assertFalse(is_datetime(dts, True))
6120
6121 # Check that various other things are not datetimes
6122 args = [tuple(), list(), 1, '2011-01-01',
6123 timedelta(1), timezone.utc, date(2011, 1, 1)]
6124
6125 for arg in args:
6126 for exact in (True, False):
6127 with self.subTest(arg=arg, exact=exact):
6128 self.assertFalse(is_datetime(arg, exact))
6129
6130 def test_check_delta(self):
6131 class TimeDeltaSubclass(timedelta):
6132 pass
6133
6134 td = timedelta(1)
6135 tds = TimeDeltaSubclass(1)
6136
6137 is_timedelta = _testcapi.datetime_check_delta
6138
6139 # Check the ones that should be valid
6140 self.assertTrue(is_timedelta(td))
6141 self.assertTrue(is_timedelta(tds))
6142 self.assertTrue(is_timedelta(td, True))
6143
6144 # Check that the subclass does not match exactly
6145 self.assertFalse(is_timedelta(tds, True))
6146
6147 # Check that various other things are not timedeltas
6148 args = [tuple(), list(), 1, '2011-01-01',
6149 timezone.utc, date(2011, 1, 1), datetime(2011, 1, 1)]
6150
6151 for arg in args:
6152 for exact in (True, False):
6153 with self.subTest(arg=arg, exact=exact):
6154 self.assertFalse(is_timedelta(arg, exact))
6155
6156 def test_check_tzinfo(self):
6157 class TZInfoSubclass(tzinfo):
6158 pass
6159
6160 tzi = tzinfo()
6161 tzis = TZInfoSubclass()
6162 tz = timezone(timedelta(hours=-5))
6163
6164 is_tzinfo = _testcapi.datetime_check_tzinfo
6165
6166 # Check the ones that should be valid
6167 self.assertTrue(is_tzinfo(tzi))
6168 self.assertTrue(is_tzinfo(tz))
6169 self.assertTrue(is_tzinfo(tzis))
6170 self.assertTrue(is_tzinfo(tzi, True))
6171
6172 # Check that the subclasses do not match exactly
6173 self.assertFalse(is_tzinfo(tz, True))
6174 self.assertFalse(is_tzinfo(tzis, True))
6175
6176 # Check that various other things are not tzinfos
6177 args = [tuple(), list(), 1, '2011-01-01',
6178 date(2011, 1, 1), datetime(2011, 1, 1)]
6179
6180 for arg in args:
6181 for exact in (True, False):
6182 with self.subTest(arg=arg, exact=exact):
6183 self.assertFalse(is_tzinfo(arg, exact))
6184
Edison A98ff4d52019-05-17 13:28:42 -07006185 def test_date_from_date(self):
6186 exp_date = date(1993, 8, 26)
6187
Serhiy Storchaka1f21eaa2019-09-01 12:16:51 +03006188 for macro in False, True:
Edison A98ff4d52019-05-17 13:28:42 -07006189 with self.subTest(macro=macro):
6190 c_api_date = _testcapi.get_date_fromdate(
6191 macro,
6192 exp_date.year,
6193 exp_date.month,
6194 exp_date.day)
6195
6196 self.assertEqual(c_api_date, exp_date)
6197
6198 def test_datetime_from_dateandtime(self):
6199 exp_date = datetime(1993, 8, 26, 22, 12, 55, 99999)
6200
Serhiy Storchaka1f21eaa2019-09-01 12:16:51 +03006201 for macro in False, True:
Edison A98ff4d52019-05-17 13:28:42 -07006202 with self.subTest(macro=macro):
6203 c_api_date = _testcapi.get_datetime_fromdateandtime(
6204 macro,
6205 exp_date.year,
6206 exp_date.month,
6207 exp_date.day,
6208 exp_date.hour,
6209 exp_date.minute,
6210 exp_date.second,
6211 exp_date.microsecond)
6212
6213 self.assertEqual(c_api_date, exp_date)
6214
6215 def test_datetime_from_dateandtimeandfold(self):
6216 exp_date = datetime(1993, 8, 26, 22, 12, 55, 99999)
6217
6218 for fold in [0, 1]:
Serhiy Storchaka1f21eaa2019-09-01 12:16:51 +03006219 for macro in False, True:
Edison A98ff4d52019-05-17 13:28:42 -07006220 with self.subTest(macro=macro, fold=fold):
6221 c_api_date = _testcapi.get_datetime_fromdateandtimeandfold(
6222 macro,
6223 exp_date.year,
6224 exp_date.month,
6225 exp_date.day,
6226 exp_date.hour,
6227 exp_date.minute,
6228 exp_date.second,
6229 exp_date.microsecond,
6230 exp_date.fold)
6231
6232 self.assertEqual(c_api_date, exp_date)
6233 self.assertEqual(c_api_date.fold, exp_date.fold)
6234
6235 def test_time_from_time(self):
6236 exp_time = time(22, 12, 55, 99999)
6237
Serhiy Storchaka1f21eaa2019-09-01 12:16:51 +03006238 for macro in False, True:
Edison A98ff4d52019-05-17 13:28:42 -07006239 with self.subTest(macro=macro):
6240 c_api_time = _testcapi.get_time_fromtime(
6241 macro,
6242 exp_time.hour,
6243 exp_time.minute,
6244 exp_time.second,
6245 exp_time.microsecond)
6246
6247 self.assertEqual(c_api_time, exp_time)
6248
6249 def test_time_from_timeandfold(self):
6250 exp_time = time(22, 12, 55, 99999)
6251
6252 for fold in [0, 1]:
Serhiy Storchaka1f21eaa2019-09-01 12:16:51 +03006253 for macro in False, True:
Edison A98ff4d52019-05-17 13:28:42 -07006254 with self.subTest(macro=macro, fold=fold):
6255 c_api_time = _testcapi.get_time_fromtimeandfold(
6256 macro,
6257 exp_time.hour,
6258 exp_time.minute,
6259 exp_time.second,
6260 exp_time.microsecond,
6261 exp_time.fold)
6262
6263 self.assertEqual(c_api_time, exp_time)
6264 self.assertEqual(c_api_time.fold, exp_time.fold)
6265
6266 def test_delta_from_dsu(self):
6267 exp_delta = timedelta(26, 55, 99999)
6268
Serhiy Storchaka1f21eaa2019-09-01 12:16:51 +03006269 for macro in False, True:
Edison A98ff4d52019-05-17 13:28:42 -07006270 with self.subTest(macro=macro):
6271 c_api_delta = _testcapi.get_delta_fromdsu(
6272 macro,
6273 exp_delta.days,
6274 exp_delta.seconds,
6275 exp_delta.microseconds)
6276
6277 self.assertEqual(c_api_delta, exp_delta)
6278
Paul Ganssle4d8c8c02019-04-27 15:39:40 -04006279 def test_date_from_timestamp(self):
6280 ts = datetime(1995, 4, 12).timestamp()
6281
Serhiy Storchaka1f21eaa2019-09-01 12:16:51 +03006282 for macro in False, True:
Paul Ganssle4d8c8c02019-04-27 15:39:40 -04006283 with self.subTest(macro=macro):
6284 d = _testcapi.get_date_fromtimestamp(int(ts), macro)
6285
6286 self.assertEqual(d, date(1995, 4, 12))
6287
6288 def test_datetime_from_timestamp(self):
Paul Ganssle4d8c8c02019-04-27 15:39:40 -04006289 cases = [
6290 ((1995, 4, 12), None, False),
6291 ((1995, 4, 12), None, True),
6292 ((1995, 4, 12), timezone(timedelta(hours=1)), True),
6293 ((1995, 4, 12, 14, 30), None, False),
6294 ((1995, 4, 12, 14, 30), None, True),
6295 ((1995, 4, 12, 14, 30), timezone(timedelta(hours=1)), True),
6296 ]
6297
6298 from_timestamp = _testcapi.get_datetime_fromtimestamp
6299 for case in cases:
Serhiy Storchaka1f21eaa2019-09-01 12:16:51 +03006300 for macro in False, True:
Paul Ganssle4d8c8c02019-04-27 15:39:40 -04006301 with self.subTest(case=case, macro=macro):
6302 dtup, tzinfo, usetz = case
6303 dt_orig = datetime(*dtup, tzinfo=tzinfo)
6304 ts = int(dt_orig.timestamp())
6305
6306 dt_rt = from_timestamp(ts, tzinfo, usetz, macro)
6307
6308 self.assertEqual(dt_orig, dt_rt)
6309
6310
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04006311def load_tests(loader, standard_tests, pattern):
6312 standard_tests.addTest(ZoneInfoCompleteTest())
6313 return standard_tests
6314
6315
Alexander Belopolskycf86e362010-07-23 19:25:47 +00006316if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -05006317 unittest.main()