blob: 520a51df87999764d9be631c48ff13a376fc3480 [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)
1784
1785 def test_backdoor_resistance(self):
1786 # For fast unpickling, the constructor accepts a pickle byte string.
1787 # This is a low-overhead backdoor. A user can (by intent or
1788 # mistake) pass a string directly, which (if it's the right length)
1789 # will get treated like a pickle, and bypass the normal sanity
1790 # checks in the constructor. This can create insane objects.
1791 # The constructor doesn't want to burn the time to validate all
1792 # fields, but does check the month field. This stops, e.g.,
1793 # datetime.datetime('1995-03-25') from yielding an insane object.
1794 base = b'1995-03-25'
1795 if not issubclass(self.theclass, datetime):
1796 base = base[:4]
1797 for month_byte in b'9', b'\0', b'\r', b'\xff':
1798 self.assertRaises(TypeError, self.theclass,
1799 base[:2] + month_byte + base[3:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001800 if issubclass(self.theclass, datetime):
1801 # Good bytes, but bad tzinfo:
1802 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
1803 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001804
1805 for ord_byte in range(1, 13):
1806 # This shouldn't blow up because of the month byte alone. If
1807 # the implementation changes to do more-careful checking, it may
1808 # blow up because other fields are insane.
1809 self.theclass(base[:2] + bytes([ord_byte]) + base[3:])
1810
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001811 def test_fromisoformat(self):
1812 # Test that isoformat() is reversible
1813 base_dates = [
1814 (1, 1, 1),
1815 (1000, 2, 14),
1816 (1900, 1, 1),
1817 (2000, 2, 29),
1818 (2004, 11, 12),
1819 (2004, 4, 3),
1820 (2017, 5, 30)
1821 ]
1822
1823 for dt_tuple in base_dates:
1824 dt = self.theclass(*dt_tuple)
1825 dt_str = dt.isoformat()
1826 with self.subTest(dt_str=dt_str):
1827 dt_rt = self.theclass.fromisoformat(dt.isoformat())
1828
1829 self.assertEqual(dt, dt_rt)
1830
1831 def test_fromisoformat_subclass(self):
1832 class DateSubclass(self.theclass):
1833 pass
1834
1835 dt = DateSubclass(2014, 12, 14)
1836
1837 dt_rt = DateSubclass.fromisoformat(dt.isoformat())
1838
1839 self.assertIsInstance(dt_rt, DateSubclass)
1840
1841 def test_fromisoformat_fails(self):
1842 # Test that fromisoformat() fails on invalid values
1843 bad_strs = [
1844 '', # Empty string
Paul Ganssle096329f2018-08-23 11:06:20 -04001845 '\ud800', # bpo-34454: Surrogate code point
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001846 '009-03-04', # Not 10 characters
1847 '123456789', # Not a date
1848 '200a-12-04', # Invalid character in year
1849 '2009-1a-04', # Invalid character in month
1850 '2009-12-0a', # Invalid character in day
1851 '2009-01-32', # Invalid day
1852 '2009-02-29', # Invalid leap day
1853 '20090228', # Valid ISO8601 output not from isoformat()
Paul Ganssle096329f2018-08-23 11:06:20 -04001854 '2009\ud80002\ud80028', # Separators are surrogate codepoints
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001855 ]
1856
1857 for bad_str in bad_strs:
1858 with self.assertRaises(ValueError):
1859 self.theclass.fromisoformat(bad_str)
1860
1861 def test_fromisoformat_fails_typeerror(self):
1862 # Test that fromisoformat fails when passed the wrong type
1863 import io
1864
1865 bad_types = [b'2009-03-01', None, io.StringIO('2009-03-01')]
1866 for bad_type in bad_types:
1867 with self.assertRaises(TypeError):
1868 self.theclass.fromisoformat(bad_type)
1869
Paul Ganssle88c09372019-04-29 09:22:03 -04001870 def test_fromisocalendar(self):
1871 # For each test case, assert that fromisocalendar is the
1872 # inverse of the isocalendar function
1873 dates = [
1874 (2016, 4, 3),
1875 (2005, 1, 2), # (2004, 53, 7)
1876 (2008, 12, 30), # (2009, 1, 2)
1877 (2010, 1, 2), # (2009, 53, 6)
1878 (2009, 12, 31), # (2009, 53, 4)
1879 (1900, 1, 1), # Unusual non-leap year (year % 100 == 0)
1880 (1900, 12, 31),
1881 (2000, 1, 1), # Unusual leap year (year % 400 == 0)
1882 (2000, 12, 31),
1883 (2004, 1, 1), # Leap year
1884 (2004, 12, 31),
1885 (1, 1, 1),
1886 (9999, 12, 31),
1887 (MINYEAR, 1, 1),
1888 (MAXYEAR, 12, 31),
1889 ]
1890
1891 for datecomps in dates:
1892 with self.subTest(datecomps=datecomps):
1893 dobj = self.theclass(*datecomps)
1894 isocal = dobj.isocalendar()
1895
1896 d_roundtrip = self.theclass.fromisocalendar(*isocal)
1897
1898 self.assertEqual(dobj, d_roundtrip)
1899
1900 def test_fromisocalendar_value_errors(self):
1901 isocals = [
1902 (2019, 0, 1),
1903 (2019, -1, 1),
1904 (2019, 54, 1),
1905 (2019, 1, 0),
1906 (2019, 1, -1),
1907 (2019, 1, 8),
1908 (2019, 53, 1),
1909 (10000, 1, 1),
1910 (0, 1, 1),
1911 (9999999, 1, 1),
1912 (2<<32, 1, 1),
1913 (2019, 2<<32, 1),
1914 (2019, 1, 2<<32),
1915 ]
1916
1917 for isocal in isocals:
1918 with self.subTest(isocal=isocal):
1919 with self.assertRaises(ValueError):
1920 self.theclass.fromisocalendar(*isocal)
1921
1922 def test_fromisocalendar_type_errors(self):
1923 err_txformers = [
1924 str,
1925 float,
1926 lambda x: None,
1927 ]
1928
1929 # Take a valid base tuple and transform it to contain one argument
1930 # with the wrong type. Repeat this for each argument, e.g.
1931 # [("2019", 1, 1), (2019, "1", 1), (2019, 1, "1"), ...]
1932 isocals = []
1933 base = (2019, 1, 1)
1934 for i in range(3):
1935 for txformer in err_txformers:
1936 err_val = list(base)
1937 err_val[i] = txformer(err_val[i])
1938 isocals.append(tuple(err_val))
1939
1940 for isocal in isocals:
1941 with self.subTest(isocal=isocal):
1942 with self.assertRaises(TypeError):
1943 self.theclass.fromisocalendar(*isocal)
1944
1945
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001946#############################################################################
1947# datetime tests
1948
1949class SubclassDatetime(datetime):
1950 sub_var = 1
1951
1952class TestDateTime(TestDate):
1953
1954 theclass = datetime
1955
1956 def test_basic_attributes(self):
1957 dt = self.theclass(2002, 3, 1, 12, 0)
1958 self.assertEqual(dt.year, 2002)
1959 self.assertEqual(dt.month, 3)
1960 self.assertEqual(dt.day, 1)
1961 self.assertEqual(dt.hour, 12)
1962 self.assertEqual(dt.minute, 0)
1963 self.assertEqual(dt.second, 0)
1964 self.assertEqual(dt.microsecond, 0)
1965
1966 def test_basic_attributes_nonzero(self):
1967 # Make sure all attributes are non-zero so bugs in
1968 # bit-shifting access show up.
1969 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1970 self.assertEqual(dt.year, 2002)
1971 self.assertEqual(dt.month, 3)
1972 self.assertEqual(dt.day, 1)
1973 self.assertEqual(dt.hour, 12)
1974 self.assertEqual(dt.minute, 59)
1975 self.assertEqual(dt.second, 59)
1976 self.assertEqual(dt.microsecond, 8000)
1977
1978 def test_roundtrip(self):
1979 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1980 self.theclass.now()):
1981 # Verify dt -> string -> datetime identity.
1982 s = repr(dt)
1983 self.assertTrue(s.startswith('datetime.'))
1984 s = s[9:]
1985 dt2 = eval(s)
1986 self.assertEqual(dt, dt2)
1987
1988 # Verify identity via reconstructing from pieces.
1989 dt2 = self.theclass(dt.year, dt.month, dt.day,
1990 dt.hour, dt.minute, dt.second,
1991 dt.microsecond)
1992 self.assertEqual(dt, dt2)
1993
1994 def test_isoformat(self):
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001995 t = self.theclass(1, 2, 3, 4, 5, 1, 123)
1996 self.assertEqual(t.isoformat(), "0001-02-03T04:05:01.000123")
1997 self.assertEqual(t.isoformat('T'), "0001-02-03T04:05:01.000123")
1998 self.assertEqual(t.isoformat(' '), "0001-02-03 04:05:01.000123")
1999 self.assertEqual(t.isoformat('\x00'), "0001-02-03\x0004:05:01.000123")
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03002000 # bpo-34482: Check that surrogates are handled properly.
2001 self.assertEqual(t.isoformat('\ud800'),
2002 "0001-02-03\ud80004:05:01.000123")
Alexander Belopolskya2998a62016-03-06 14:58:43 -05002003 self.assertEqual(t.isoformat(timespec='hours'), "0001-02-03T04")
2004 self.assertEqual(t.isoformat(timespec='minutes'), "0001-02-03T04:05")
2005 self.assertEqual(t.isoformat(timespec='seconds'), "0001-02-03T04:05:01")
2006 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
2007 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000123")
2008 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01.000123")
2009 self.assertEqual(t.isoformat(sep=' ', timespec='minutes'), "0001-02-03 04:05")
2010 self.assertRaises(ValueError, t.isoformat, timespec='foo')
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03002011 # bpo-34482: Check that surrogates are handled properly.
2012 self.assertRaises(ValueError, t.isoformat, timespec='\ud800')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002013 # str is ISO format with the separator forced to a blank.
Alexander Belopolskya2998a62016-03-06 14:58:43 -05002014 self.assertEqual(str(t), "0001-02-03 04:05:01.000123")
2015
2016 t = self.theclass(1, 2, 3, 4, 5, 1, 999500, tzinfo=timezone.utc)
2017 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999+00:00")
2018
2019 t = self.theclass(1, 2, 3, 4, 5, 1, 999500)
2020 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999")
2021
2022 t = self.theclass(1, 2, 3, 4, 5, 1)
2023 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01")
2024 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
2025 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000000")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002026
2027 t = self.theclass(2, 3, 2)
2028 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
2029 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
2030 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
2031 # str is ISO format with the separator forced to a blank.
2032 self.assertEqual(str(t), "0002-03-02 00:00:00")
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002033 # ISO format with timezone
2034 tz = FixedOffset(timedelta(seconds=16), 'XXX')
2035 t = self.theclass(2, 3, 2, tzinfo=tz)
2036 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00+00:00:16")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002037
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002038 def test_isoformat_timezone(self):
2039 tzoffsets = [
2040 ('05:00', timedelta(hours=5)),
2041 ('02:00', timedelta(hours=2)),
2042 ('06:27', timedelta(hours=6, minutes=27)),
2043 ('12:32:30', timedelta(hours=12, minutes=32, seconds=30)),
2044 ('02:04:09.123456', timedelta(hours=2, minutes=4, seconds=9, microseconds=123456))
2045 ]
2046
2047 tzinfos = [
2048 ('', None),
2049 ('+00:00', timezone.utc),
2050 ('+00:00', timezone(timedelta(0))),
2051 ]
2052
2053 tzinfos += [
2054 (prefix + expected, timezone(sign * td))
2055 for expected, td in tzoffsets
2056 for prefix, sign in [('-', -1), ('+', 1)]
2057 ]
2058
2059 dt_base = self.theclass(2016, 4, 1, 12, 37, 9)
2060 exp_base = '2016-04-01T12:37:09'
2061
2062 for exp_tz, tzi in tzinfos:
2063 dt = dt_base.replace(tzinfo=tzi)
2064 exp = exp_base + exp_tz
2065 with self.subTest(tzi=tzi):
2066 assert dt.isoformat() == exp
2067
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002068 def test_format(self):
2069 dt = self.theclass(2007, 9, 10, 4, 5, 1, 123)
2070 self.assertEqual(dt.__format__(''), str(dt))
2071
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02002072 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002073 dt.__format__(123)
2074
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002075 # check that a derived class's __str__() gets called
2076 class A(self.theclass):
2077 def __str__(self):
2078 return 'A'
2079 a = A(2007, 9, 10, 4, 5, 1, 123)
2080 self.assertEqual(a.__format__(''), 'A')
2081
2082 # check that a derived class's strftime gets called
2083 class B(self.theclass):
2084 def strftime(self, format_spec):
2085 return 'B'
2086 b = B(2007, 9, 10, 4, 5, 1, 123)
2087 self.assertEqual(b.__format__(''), str(dt))
2088
2089 for fmt in ["m:%m d:%d y:%y",
2090 "m:%m d:%d y:%y H:%H M:%M S:%S",
2091 "%z %Z",
2092 ]:
2093 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
2094 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
2095 self.assertEqual(b.__format__(fmt), 'B')
2096
2097 def test_more_ctime(self):
2098 # Test fields that TestDate doesn't touch.
2099 import time
2100
2101 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
2102 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
2103 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
2104 # out. The difference is that t.ctime() produces " 2" for the day,
2105 # but platform ctime() produces "02" for the day. According to
2106 # C99, t.ctime() is correct here.
2107 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
2108
2109 # So test a case where that difference doesn't matter.
2110 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
2111 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
2112
2113 def test_tz_independent_comparing(self):
2114 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
2115 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
2116 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
2117 self.assertEqual(dt1, dt3)
2118 self.assertTrue(dt2 > dt3)
2119
2120 # Make sure comparison doesn't forget microseconds, and isn't done
2121 # via comparing a float timestamp (an IEEE double doesn't have enough
Leo Ariasc3d95082018-02-03 18:36:10 -06002122 # precision to span microsecond resolution across years 1 through 9999,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002123 # so comparing via timestamp necessarily calls some distinct values
2124 # equal).
2125 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
2126 us = timedelta(microseconds=1)
2127 dt2 = dt1 + us
2128 self.assertEqual(dt2 - dt1, us)
2129 self.assertTrue(dt1 < dt2)
2130
2131 def test_strftime_with_bad_tzname_replace(self):
2132 # verify ok if tzinfo.tzname().replace() returns a non-string
2133 class MyTzInfo(FixedOffset):
2134 def tzname(self, dt):
2135 class MyStr(str):
2136 def replace(self, *args):
2137 return None
2138 return MyStr('name')
2139 t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
2140 self.assertRaises(TypeError, t.strftime, '%Z')
2141
2142 def test_bad_constructor_arguments(self):
2143 # bad years
2144 self.theclass(MINYEAR, 1, 1) # no exception
2145 self.theclass(MAXYEAR, 1, 1) # no exception
2146 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
2147 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
2148 # bad months
2149 self.theclass(2000, 1, 1) # no exception
2150 self.theclass(2000, 12, 1) # no exception
2151 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
2152 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
2153 # bad days
2154 self.theclass(2000, 2, 29) # no exception
2155 self.theclass(2004, 2, 29) # no exception
2156 self.theclass(2400, 2, 29) # no exception
2157 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
2158 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
2159 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
2160 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
2161 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
2162 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
2163 # bad hours
2164 self.theclass(2000, 1, 31, 0) # no exception
2165 self.theclass(2000, 1, 31, 23) # no exception
2166 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
2167 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
2168 # bad minutes
2169 self.theclass(2000, 1, 31, 23, 0) # no exception
2170 self.theclass(2000, 1, 31, 23, 59) # no exception
2171 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
2172 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
2173 # bad seconds
2174 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
2175 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
2176 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
2177 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
2178 # bad microseconds
2179 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
2180 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
2181 self.assertRaises(ValueError, self.theclass,
2182 2000, 1, 31, 23, 59, 59, -1)
2183 self.assertRaises(ValueError, self.theclass,
2184 2000, 1, 31, 23, 59, 59,
2185 1000000)
Alexander Belopolsky47649ab2016-08-08 17:05:40 -04002186 # bad fold
2187 self.assertRaises(ValueError, self.theclass,
2188 2000, 1, 31, fold=-1)
2189 self.assertRaises(ValueError, self.theclass,
2190 2000, 1, 31, fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002191 # Positional fold:
2192 self.assertRaises(TypeError, self.theclass,
2193 2000, 1, 31, 23, 59, 59, 0, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04002194
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002195 def test_hash_equality(self):
2196 d = self.theclass(2000, 12, 31, 23, 30, 17)
2197 e = self.theclass(2000, 12, 31, 23, 30, 17)
2198 self.assertEqual(d, e)
2199 self.assertEqual(hash(d), hash(e))
2200
2201 dic = {d: 1}
2202 dic[e] = 2
2203 self.assertEqual(len(dic), 1)
2204 self.assertEqual(dic[d], 2)
2205 self.assertEqual(dic[e], 2)
2206
2207 d = self.theclass(2001, 1, 1, 0, 5, 17)
2208 e = self.theclass(2001, 1, 1, 0, 5, 17)
2209 self.assertEqual(d, e)
2210 self.assertEqual(hash(d), hash(e))
2211
2212 dic = {d: 1}
2213 dic[e] = 2
2214 self.assertEqual(len(dic), 1)
2215 self.assertEqual(dic[d], 2)
2216 self.assertEqual(dic[e], 2)
2217
2218 def test_computations(self):
2219 a = self.theclass(2002, 1, 31)
2220 b = self.theclass(1956, 1, 31)
2221 diff = a-b
2222 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
2223 self.assertEqual(diff.seconds, 0)
2224 self.assertEqual(diff.microseconds, 0)
2225 a = self.theclass(2002, 3, 2, 17, 6)
2226 millisec = timedelta(0, 0, 1000)
2227 hour = timedelta(0, 3600)
2228 day = timedelta(1)
2229 week = timedelta(7)
2230 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
2231 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
2232 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
2233 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
2234 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
2235 self.assertEqual(a - hour, a + -hour)
2236 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
2237 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
2238 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
2239 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
2240 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
2241 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
2242 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
2243 self.assertEqual((a + week) - a, week)
2244 self.assertEqual((a + day) - a, day)
2245 self.assertEqual((a + hour) - a, hour)
2246 self.assertEqual((a + millisec) - a, millisec)
2247 self.assertEqual((a - week) - a, -week)
2248 self.assertEqual((a - day) - a, -day)
2249 self.assertEqual((a - hour) - a, -hour)
2250 self.assertEqual((a - millisec) - a, -millisec)
2251 self.assertEqual(a - (a + week), -week)
2252 self.assertEqual(a - (a + day), -day)
2253 self.assertEqual(a - (a + hour), -hour)
2254 self.assertEqual(a - (a + millisec), -millisec)
2255 self.assertEqual(a - (a - week), week)
2256 self.assertEqual(a - (a - day), day)
2257 self.assertEqual(a - (a - hour), hour)
2258 self.assertEqual(a - (a - millisec), millisec)
2259 self.assertEqual(a + (week + day + hour + millisec),
2260 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
2261 self.assertEqual(a + (week + day + hour + millisec),
2262 (((a + week) + day) + hour) + millisec)
2263 self.assertEqual(a - (week + day + hour + millisec),
2264 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
2265 self.assertEqual(a - (week + day + hour + millisec),
2266 (((a - week) - day) - hour) - millisec)
2267 # Add/sub ints or floats should be illegal
2268 for i in 1, 1.0:
2269 self.assertRaises(TypeError, lambda: a+i)
2270 self.assertRaises(TypeError, lambda: a-i)
2271 self.assertRaises(TypeError, lambda: i+a)
2272 self.assertRaises(TypeError, lambda: i-a)
2273
2274 # delta - datetime is senseless.
2275 self.assertRaises(TypeError, lambda: day - a)
2276 # mixing datetime and (delta or datetime) via * or // is senseless
2277 self.assertRaises(TypeError, lambda: day * a)
2278 self.assertRaises(TypeError, lambda: a * day)
2279 self.assertRaises(TypeError, lambda: day // a)
2280 self.assertRaises(TypeError, lambda: a // day)
2281 self.assertRaises(TypeError, lambda: a * a)
2282 self.assertRaises(TypeError, lambda: a // a)
2283 # datetime + datetime is senseless
2284 self.assertRaises(TypeError, lambda: a + a)
2285
2286 def test_pickling(self):
2287 args = 6, 7, 23, 20, 59, 1, 64**2
2288 orig = self.theclass(*args)
2289 for pickler, unpickler, proto in pickle_choices:
2290 green = pickler.dumps(orig, proto)
2291 derived = unpickler.loads(green)
2292 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02002293 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002294
2295 def test_more_pickling(self):
2296 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
Serhiy Storchakabad12572014-12-15 14:03:42 +02002297 for proto in range(pickle.HIGHEST_PROTOCOL + 1):
2298 s = pickle.dumps(a, proto)
2299 b = pickle.loads(s)
2300 self.assertEqual(b.year, 2003)
2301 self.assertEqual(b.month, 2)
2302 self.assertEqual(b.day, 7)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002303
2304 def test_pickling_subclass_datetime(self):
2305 args = 6, 7, 23, 20, 59, 1, 64**2
2306 orig = SubclassDatetime(*args)
2307 for pickler, unpickler, proto in pickle_choices:
2308 green = pickler.dumps(orig, proto)
2309 derived = unpickler.loads(green)
2310 self.assertEqual(orig, derived)
2311
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02002312 def test_compat_unpickle(self):
2313 tests = [
2314 b'cdatetime\ndatetime\n('
2315 b"S'\\x07\\xdf\\x0b\\x1b\\x14;\\x01\\x00\\x10\\x00'\ntR.",
2316
2317 b'cdatetime\ndatetime\n('
2318 b'U\n\x07\xdf\x0b\x1b\x14;\x01\x00\x10\x00tR.',
2319
2320 b'\x80\x02cdatetime\ndatetime\n'
2321 b'U\n\x07\xdf\x0b\x1b\x14;\x01\x00\x10\x00\x85R.',
2322 ]
2323 args = 2015, 11, 27, 20, 59, 1, 64**2
2324 expected = self.theclass(*args)
2325 for data in tests:
2326 for loads in pickle_loads:
2327 derived = loads(data, encoding='latin1')
2328 self.assertEqual(derived, expected)
2329
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002330 def test_more_compare(self):
2331 # The test_compare() inherited from TestDate covers the error cases.
2332 # We just want to test lexicographic ordering on the members datetime
2333 # has that date lacks.
2334 args = [2000, 11, 29, 20, 58, 16, 999998]
2335 t1 = self.theclass(*args)
2336 t2 = self.theclass(*args)
2337 self.assertEqual(t1, t2)
2338 self.assertTrue(t1 <= t2)
2339 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002340 self.assertFalse(t1 != t2)
2341 self.assertFalse(t1 < t2)
2342 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002343
2344 for i in range(len(args)):
2345 newargs = args[:]
2346 newargs[i] = args[i] + 1
2347 t2 = self.theclass(*newargs) # this is larger than t1
2348 self.assertTrue(t1 < t2)
2349 self.assertTrue(t2 > t1)
2350 self.assertTrue(t1 <= t2)
2351 self.assertTrue(t2 >= t1)
2352 self.assertTrue(t1 != t2)
2353 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002354 self.assertFalse(t1 == t2)
2355 self.assertFalse(t2 == t1)
2356 self.assertFalse(t1 > t2)
2357 self.assertFalse(t2 < t1)
2358 self.assertFalse(t1 >= t2)
2359 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002360
2361
2362 # A helper for timestamp constructor tests.
2363 def verify_field_equality(self, expected, got):
2364 self.assertEqual(expected.tm_year, got.year)
2365 self.assertEqual(expected.tm_mon, got.month)
2366 self.assertEqual(expected.tm_mday, got.day)
2367 self.assertEqual(expected.tm_hour, got.hour)
2368 self.assertEqual(expected.tm_min, got.minute)
2369 self.assertEqual(expected.tm_sec, got.second)
2370
2371 def test_fromtimestamp(self):
2372 import time
2373
2374 ts = time.time()
2375 expected = time.localtime(ts)
2376 got = self.theclass.fromtimestamp(ts)
2377 self.verify_field_equality(expected, got)
2378
2379 def test_utcfromtimestamp(self):
2380 import time
2381
2382 ts = time.time()
2383 expected = time.gmtime(ts)
2384 got = self.theclass.utcfromtimestamp(ts)
2385 self.verify_field_equality(expected, got)
2386
Alexander Belopolskya4415142012-06-08 12:33:09 -04002387 # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in
2388 # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0).
2389 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
2390 def test_timestamp_naive(self):
2391 t = self.theclass(1970, 1, 1)
2392 self.assertEqual(t.timestamp(), 18000.0)
2393 t = self.theclass(1970, 1, 1, 1, 2, 3, 4)
2394 self.assertEqual(t.timestamp(),
2395 18000.0 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002396 # Missing hour
2397 t0 = self.theclass(2012, 3, 11, 2, 30)
2398 t1 = t0.replace(fold=1)
2399 self.assertEqual(self.theclass.fromtimestamp(t1.timestamp()),
2400 t0 - timedelta(hours=1))
2401 self.assertEqual(self.theclass.fromtimestamp(t0.timestamp()),
2402 t1 + timedelta(hours=1))
Alexander Belopolskya4415142012-06-08 12:33:09 -04002403 # Ambiguous hour defaults to DST
2404 t = self.theclass(2012, 11, 4, 1, 30)
2405 self.assertEqual(self.theclass.fromtimestamp(t.timestamp()), t)
2406
2407 # Timestamp may raise an overflow error on some platforms
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002408 # XXX: Do we care to support the first and last year?
2409 for t in [self.theclass(2,1,1), self.theclass(9998,12,12)]:
Alexander Belopolskya4415142012-06-08 12:33:09 -04002410 try:
2411 s = t.timestamp()
2412 except OverflowError:
2413 pass
2414 else:
2415 self.assertEqual(self.theclass.fromtimestamp(s), t)
2416
2417 def test_timestamp_aware(self):
2418 t = self.theclass(1970, 1, 1, tzinfo=timezone.utc)
2419 self.assertEqual(t.timestamp(), 0.0)
2420 t = self.theclass(1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone.utc)
2421 self.assertEqual(t.timestamp(),
2422 3600 + 2*60 + 3 + 4*1e-6)
2423 t = self.theclass(1970, 1, 1, 1, 2, 3, 4,
2424 tzinfo=timezone(timedelta(hours=-5), 'EST'))
2425 self.assertEqual(t.timestamp(),
2426 18000 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002427
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002428 @support.run_with_tz('MSK-03') # Something east of Greenwich
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002429 def test_microsecond_rounding(self):
Victor Stinner5ebfe422015-09-18 14:52:15 +02002430 for fts in [self.theclass.fromtimestamp,
2431 self.theclass.utcfromtimestamp]:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002432 zero = fts(0)
2433 self.assertEqual(zero.second, 0)
2434 self.assertEqual(zero.microsecond, 0)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002435 one = fts(1e-6)
Victor Stinner8050ca92012-03-14 00:17:05 +01002436 try:
2437 minus_one = fts(-1e-6)
2438 except OSError:
2439 # localtime(-1) and gmtime(-1) is not supported on Windows
2440 pass
2441 else:
2442 self.assertEqual(minus_one.second, 59)
2443 self.assertEqual(minus_one.microsecond, 999999)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002444
Victor Stinner8050ca92012-03-14 00:17:05 +01002445 t = fts(-1e-8)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002446 self.assertEqual(t, zero)
Victor Stinner8050ca92012-03-14 00:17:05 +01002447 t = fts(-9e-7)
2448 self.assertEqual(t, minus_one)
2449 t = fts(-1e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002450 self.assertEqual(t, zero)
Victor Stinner8820a352015-09-05 10:50:20 +02002451 t = fts(-1/2**7)
2452 self.assertEqual(t.second, 59)
Victor Stinner7667f582015-09-09 01:02:23 +02002453 self.assertEqual(t.microsecond, 992188)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002454
2455 t = fts(1e-7)
2456 self.assertEqual(t, zero)
2457 t = fts(9e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002458 self.assertEqual(t, one)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002459 t = fts(0.99999949)
2460 self.assertEqual(t.second, 0)
2461 self.assertEqual(t.microsecond, 999999)
2462 t = fts(0.9999999)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002463 self.assertEqual(t.second, 1)
2464 self.assertEqual(t.microsecond, 0)
Victor Stinneradfefa52015-09-04 23:57:25 +02002465 t = fts(1/2**7)
2466 self.assertEqual(t.second, 0)
Victor Stinner7667f582015-09-09 01:02:23 +02002467 self.assertEqual(t.microsecond, 7812)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002468
Victor Stinnerb67f0962017-02-10 10:34:02 +01002469 def test_timestamp_limits(self):
2470 # minimum timestamp
2471 min_dt = self.theclass.min.replace(tzinfo=timezone.utc)
2472 min_ts = min_dt.timestamp()
Victor Stinner6f37e362017-02-10 11:45:14 +01002473 try:
2474 # date 0001-01-01 00:00:00+00:00: timestamp=-62135596800
2475 self.assertEqual(self.theclass.fromtimestamp(min_ts, tz=timezone.utc),
2476 min_dt)
Victor Stinner2a35c932017-02-10 12:37:21 +01002477 except (OverflowError, OSError) as exc:
2478 # the date 0001-01-01 doesn't fit into 32-bit time_t,
2479 # or platform doesn't support such very old date
Victor Stinner6f37e362017-02-10 11:45:14 +01002480 self.skipTest(str(exc))
Victor Stinnerb67f0962017-02-10 10:34:02 +01002481
2482 # maximum timestamp: set seconds to zero to avoid rounding issues
2483 max_dt = self.theclass.max.replace(tzinfo=timezone.utc,
2484 second=0, microsecond=0)
2485 max_ts = max_dt.timestamp()
2486 # date 9999-12-31 23:59:00+00:00: timestamp 253402300740
2487 self.assertEqual(self.theclass.fromtimestamp(max_ts, tz=timezone.utc),
2488 max_dt)
2489
2490 # number of seconds greater than 1 year: make sure that the new date
2491 # is not valid in datetime.datetime limits
2492 delta = 3600 * 24 * 400
2493
2494 # too small
2495 ts = min_ts - delta
2496 # converting a Python int to C time_t can raise a OverflowError,
2497 # especially on 32-bit platforms.
2498 with self.assertRaises((ValueError, OverflowError)):
2499 self.theclass.fromtimestamp(ts)
2500 with self.assertRaises((ValueError, OverflowError)):
2501 self.theclass.utcfromtimestamp(ts)
2502
2503 # too big
2504 ts = max_dt.timestamp() + delta
2505 with self.assertRaises((ValueError, OverflowError)):
2506 self.theclass.fromtimestamp(ts)
2507 with self.assertRaises((ValueError, OverflowError)):
2508 self.theclass.utcfromtimestamp(ts)
2509
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002510 def test_insane_fromtimestamp(self):
2511 # It's possible that some platform maps time_t to double,
2512 # and that this test will fail there. This test should
2513 # exempt such platforms (provided they return reasonable
2514 # results!).
2515 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002516 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002517 insane)
2518
2519 def test_insane_utcfromtimestamp(self):
2520 # It's possible that some platform maps time_t to double,
2521 # and that this test will fail there. This test should
2522 # exempt such platforms (provided they return reasonable
2523 # results!).
2524 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002525 self.assertRaises(OverflowError, self.theclass.utcfromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002526 insane)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002527
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002528 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2529 def test_negative_float_fromtimestamp(self):
2530 # The result is tz-dependent; at least test that this doesn't
2531 # fail (like it did before bug 1646728 was fixed).
2532 self.theclass.fromtimestamp(-1.05)
2533
2534 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2535 def test_negative_float_utcfromtimestamp(self):
2536 d = self.theclass.utcfromtimestamp(-1.05)
2537 self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
2538
2539 def test_utcnow(self):
2540 import time
2541
2542 # Call it a success if utcnow() and utcfromtimestamp() are within
2543 # a second of each other.
2544 tolerance = timedelta(seconds=1)
2545 for dummy in range(3):
2546 from_now = self.theclass.utcnow()
2547 from_timestamp = self.theclass.utcfromtimestamp(time.time())
2548 if abs(from_timestamp - from_now) <= tolerance:
2549 break
2550 # Else try again a few times.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002551 self.assertLessEqual(abs(from_timestamp - from_now), tolerance)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002552
2553 def test_strptime(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002554 string = '2004-12-01 13:02:47.197'
2555 format = '%Y-%m-%d %H:%M:%S.%f'
2556 expected = _strptime._strptime_datetime(self.theclass, string, format)
2557 got = self.theclass.strptime(string, format)
2558 self.assertEqual(expected, got)
2559 self.assertIs(type(expected), self.theclass)
2560 self.assertIs(type(got), self.theclass)
2561
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03002562 # bpo-34482: Check that surrogates are handled properly.
2563 inputs = [
2564 ('2004-12-01\ud80013:02:47.197', '%Y-%m-%d\ud800%H:%M:%S.%f'),
2565 ('2004\ud80012-01 13:02:47.197', '%Y\ud800%m-%d %H:%M:%S.%f'),
2566 ('2004-12-01 13:02\ud80047.197', '%Y-%m-%d %H:%M\ud800%S.%f'),
2567 ]
2568 for string, format in inputs:
2569 with self.subTest(string=string, format=format):
2570 expected = _strptime._strptime_datetime(self.theclass, string,
2571 format)
2572 got = self.theclass.strptime(string, format)
2573 self.assertEqual(expected, got)
2574
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002575 strptime = self.theclass.strptime
Mike Gleen6b9c2042019-06-18 19:14:57 +01002576
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002577 self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
2578 self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
Mario Corchero32318932017-10-26 01:35:41 +01002579 self.assertEqual(
2580 strptime("-00:02:01.000003", "%z").utcoffset(),
2581 -timedelta(minutes=2, seconds=1, microseconds=3)
2582 )
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002583 # Only local timezone and UTC are supported
2584 for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
2585 (-_time.timezone, _time.tzname[0])):
2586 if tzseconds < 0:
2587 sign = '-'
2588 seconds = -tzseconds
2589 else:
2590 sign ='+'
2591 seconds = tzseconds
2592 hours, minutes = divmod(seconds//60, 60)
2593 dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
Martin Panterfca22322015-11-16 09:22:19 +00002594 dt = strptime(dtstr, "%z %Z")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002595 self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
2596 self.assertEqual(dt.tzname(), tzname)
2597 # Can produce inconsistent datetime
2598 dtstr, fmt = "+1234 UTC", "%z %Z"
2599 dt = strptime(dtstr, fmt)
2600 self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE)
2601 self.assertEqual(dt.tzname(), 'UTC')
2602 # yet will roundtrip
2603 self.assertEqual(dt.strftime(fmt), dtstr)
2604
2605 # Produce naive datetime if no %z is provided
2606 self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
2607
2608 with self.assertRaises(ValueError): strptime("-2400", "%z")
2609 with self.assertRaises(ValueError): strptime("-000", "%z")
2610
Mike Gleen6b9c2042019-06-18 19:14:57 +01002611 def test_strptime_single_digit(self):
2612 # bpo-34903: Check that single digit dates and times are allowed.
2613
2614 strptime = self.theclass.strptime
2615
2616 with self.assertRaises(ValueError):
2617 # %y does require two digits.
2618 newdate = strptime('01/02/3 04:05:06', '%d/%m/%y %H:%M:%S')
2619 dt1 = self.theclass(2003, 2, 1, 4, 5, 6)
2620 dt2 = self.theclass(2003, 1, 2, 4, 5, 6)
2621 dt3 = self.theclass(2003, 2, 1, 0, 0, 0)
2622 dt4 = self.theclass(2003, 1, 25, 0, 0, 0)
2623 inputs = [
2624 ('%d', '1/02/03 4:5:6', '%d/%m/%y %H:%M:%S', dt1),
2625 ('%m', '01/2/03 4:5:6', '%d/%m/%y %H:%M:%S', dt1),
2626 ('%H', '01/02/03 4:05:06', '%d/%m/%y %H:%M:%S', dt1),
2627 ('%M', '01/02/03 04:5:06', '%d/%m/%y %H:%M:%S', dt1),
2628 ('%S', '01/02/03 04:05:6', '%d/%m/%y %H:%M:%S', dt1),
2629 ('%j', '2/03 04am:05:06', '%j/%y %I%p:%M:%S',dt2),
2630 ('%I', '02/03 4am:05:06', '%j/%y %I%p:%M:%S',dt2),
2631 ('%w', '6/04/03', '%w/%U/%y', dt3),
2632 # %u requires a single digit.
2633 ('%W', '6/4/2003', '%u/%W/%Y', dt3),
2634 ('%V', '6/4/2003', '%u/%V/%G', dt4),
2635 ]
2636 for reason, string, format, target in inputs:
2637 reason = 'test single digit ' + reason
2638 with self.subTest(reason=reason,
2639 string=string,
2640 format=format,
2641 target=target):
2642 newdate = strptime(string, format)
2643 self.assertEqual(newdate, target, msg=reason)
2644
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002645 def test_more_timetuple(self):
2646 # This tests fields beyond those tested by the TestDate.test_timetuple.
2647 t = self.theclass(2004, 12, 31, 6, 22, 33)
2648 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
2649 self.assertEqual(t.timetuple(),
2650 (t.year, t.month, t.day,
2651 t.hour, t.minute, t.second,
2652 t.weekday(),
2653 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
2654 -1))
2655 tt = t.timetuple()
2656 self.assertEqual(tt.tm_year, t.year)
2657 self.assertEqual(tt.tm_mon, t.month)
2658 self.assertEqual(tt.tm_mday, t.day)
2659 self.assertEqual(tt.tm_hour, t.hour)
2660 self.assertEqual(tt.tm_min, t.minute)
2661 self.assertEqual(tt.tm_sec, t.second)
2662 self.assertEqual(tt.tm_wday, t.weekday())
2663 self.assertEqual(tt.tm_yday, t.toordinal() -
2664 date(t.year, 1, 1).toordinal() + 1)
2665 self.assertEqual(tt.tm_isdst, -1)
2666
2667 def test_more_strftime(self):
2668 # This tests fields beyond those tested by the TestDate.test_strftime.
2669 t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
2670 self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
2671 "12 31 04 000047 33 22 06 366")
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04002672 for (s, us), z in [((33, 123), "33.000123"), ((33, 0), "33"),]:
2673 tz = timezone(-timedelta(hours=2, seconds=s, microseconds=us))
2674 t = t.replace(tzinfo=tz)
2675 self.assertEqual(t.strftime("%z"), "-0200" + z)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002676
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03002677 # bpo-34482: Check that surrogates don't cause a crash.
2678 try:
2679 t.strftime('%y\ud800%m %H\ud800%M')
2680 except UnicodeEncodeError:
2681 pass
2682
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002683 def test_extract(self):
2684 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2685 self.assertEqual(dt.date(), date(2002, 3, 4))
2686 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2687
2688 def test_combine(self):
2689 d = date(2002, 3, 4)
2690 t = time(18, 45, 3, 1234)
2691 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2692 combine = self.theclass.combine
2693 dt = combine(d, t)
2694 self.assertEqual(dt, expected)
2695
2696 dt = combine(time=t, date=d)
2697 self.assertEqual(dt, expected)
2698
2699 self.assertEqual(d, dt.date())
2700 self.assertEqual(t, dt.time())
2701 self.assertEqual(dt, combine(dt.date(), dt.time()))
2702
2703 self.assertRaises(TypeError, combine) # need an arg
2704 self.assertRaises(TypeError, combine, d) # need two args
2705 self.assertRaises(TypeError, combine, t, d) # args reversed
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002706 self.assertRaises(TypeError, combine, d, t, 1) # wrong tzinfo type
2707 self.assertRaises(TypeError, combine, d, t, 1, 2) # too many args
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002708 self.assertRaises(TypeError, combine, "date", "time") # wrong types
2709 self.assertRaises(TypeError, combine, d, "time") # wrong type
2710 self.assertRaises(TypeError, combine, "date", t) # wrong type
2711
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002712 # tzinfo= argument
2713 dt = combine(d, t, timezone.utc)
2714 self.assertIs(dt.tzinfo, timezone.utc)
2715 dt = combine(d, t, tzinfo=timezone.utc)
2716 self.assertIs(dt.tzinfo, timezone.utc)
2717 t = time()
2718 dt = combine(dt, t)
2719 self.assertEqual(dt.date(), d)
2720 self.assertEqual(dt.time(), t)
2721
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002722 def test_replace(self):
2723 cls = self.theclass
2724 args = [1, 2, 3, 4, 5, 6, 7]
2725 base = cls(*args)
2726 self.assertEqual(base, base.replace())
2727
2728 i = 0
2729 for name, newval in (("year", 2),
2730 ("month", 3),
2731 ("day", 4),
2732 ("hour", 5),
2733 ("minute", 6),
2734 ("second", 7),
2735 ("microsecond", 8)):
2736 newargs = args[:]
2737 newargs[i] = newval
2738 expected = cls(*newargs)
2739 got = base.replace(**{name: newval})
2740 self.assertEqual(expected, got)
2741 i += 1
2742
2743 # Out of bounds.
2744 base = cls(2000, 2, 29)
2745 self.assertRaises(ValueError, base.replace, year=2001)
2746
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002747 @support.run_with_tz('EDT4')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002748 def test_astimezone(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002749 dt = self.theclass.now()
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002750 f = FixedOffset(44, "0044")
2751 dt_utc = dt.replace(tzinfo=timezone(timedelta(hours=-4), 'EDT'))
2752 self.assertEqual(dt.astimezone(), dt_utc) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002753 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
2754 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002755 dt_f = dt.replace(tzinfo=f) + timedelta(hours=4, minutes=44)
2756 self.assertEqual(dt.astimezone(f), dt_f) # naive
2757 self.assertEqual(dt.astimezone(tz=f), dt_f) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002758
2759 class Bogus(tzinfo):
2760 def utcoffset(self, dt): return None
2761 def dst(self, dt): return timedelta(0)
2762 bog = Bogus()
2763 self.assertRaises(ValueError, dt.astimezone, bog) # naive
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002764 self.assertEqual(dt.replace(tzinfo=bog).astimezone(f), dt_f)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002765
2766 class AlsoBogus(tzinfo):
2767 def utcoffset(self, dt): return timedelta(0)
2768 def dst(self, dt): return None
2769 alsobog = AlsoBogus()
2770 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
2771
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002772 class Broken(tzinfo):
2773 def utcoffset(self, dt): return 1
2774 def dst(self, dt): return 1
2775 broken = Broken()
2776 dt_broken = dt.replace(tzinfo=broken)
2777 with self.assertRaises(TypeError):
2778 dt_broken.astimezone()
2779
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002780 def test_subclass_datetime(self):
2781
2782 class C(self.theclass):
2783 theAnswer = 42
2784
2785 def __new__(cls, *args, **kws):
2786 temp = kws.copy()
2787 extra = temp.pop('extra')
2788 result = self.theclass.__new__(cls, *args, **temp)
2789 result.extra = extra
2790 return result
2791
2792 def newmeth(self, start):
2793 return start + self.year + self.month + self.second
2794
2795 args = 2003, 4, 14, 12, 13, 41
2796
2797 dt1 = self.theclass(*args)
2798 dt2 = C(*args, **{'extra': 7})
2799
2800 self.assertEqual(dt2.__class__, C)
2801 self.assertEqual(dt2.theAnswer, 42)
2802 self.assertEqual(dt2.extra, 7)
2803 self.assertEqual(dt1.toordinal(), dt2.toordinal())
2804 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
2805 dt1.second - 7)
2806
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002807 def test_subclass_alternate_constructors_datetime(self):
2808 # Test that alternate constructors call the constructor
2809 class DateTimeSubclass(self.theclass):
2810 def __new__(cls, *args, **kwargs):
2811 result = self.theclass.__new__(cls, *args, **kwargs)
2812 result.extra = 7
2813
2814 return result
2815
2816 args = (2003, 4, 14, 12, 30, 15, 123456)
2817 d_isoformat = '2003-04-14T12:30:15.123456' # Equivalent isoformat()
2818 utc_ts = 1050323415.123456 # UTC timestamp
2819
2820 base_d = DateTimeSubclass(*args)
2821 self.assertIsInstance(base_d, DateTimeSubclass)
2822 self.assertEqual(base_d.extra, 7)
2823
2824 # Timestamp depends on time zone, so we'll calculate the equivalent here
2825 ts = base_d.timestamp()
2826
2827 test_cases = [
Paul Ganssle89427cd2019-02-04 14:42:04 -05002828 ('fromtimestamp', (ts,), base_d),
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002829 # See https://bugs.python.org/issue32417
Paul Ganssle89427cd2019-02-04 14:42:04 -05002830 ('fromtimestamp', (ts, timezone.utc),
2831 base_d.astimezone(timezone.utc)),
2832 ('utcfromtimestamp', (utc_ts,), base_d),
2833 ('fromisoformat', (d_isoformat,), base_d),
2834 ('strptime', (d_isoformat, '%Y-%m-%dT%H:%M:%S.%f'), base_d),
2835 ('combine', (date(*args[0:3]), time(*args[3:])), base_d),
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002836 ]
2837
Paul Ganssle89427cd2019-02-04 14:42:04 -05002838 for constr_name, constr_args, expected in test_cases:
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002839 for base_obj in (DateTimeSubclass, base_d):
2840 # Test both the classmethod and method
2841 with self.subTest(base_obj_type=type(base_obj),
2842 constr_name=constr_name):
Paul Ganssle89427cd2019-02-04 14:42:04 -05002843 constructor = getattr(base_obj, constr_name)
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002844
Paul Ganssle89427cd2019-02-04 14:42:04 -05002845 dt = constructor(*constr_args)
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002846
2847 # Test that it creates the right subclass
2848 self.assertIsInstance(dt, DateTimeSubclass)
2849
2850 # Test that it's equal to the base object
Paul Ganssle89427cd2019-02-04 14:42:04 -05002851 self.assertEqual(dt, expected)
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002852
2853 # Test that it called the constructor
2854 self.assertEqual(dt.extra, 7)
2855
Paul Ganssle89427cd2019-02-04 14:42:04 -05002856 def test_subclass_now(self):
2857 # Test that alternate constructors call the constructor
2858 class DateTimeSubclass(self.theclass):
2859 def __new__(cls, *args, **kwargs):
2860 result = self.theclass.__new__(cls, *args, **kwargs)
2861 result.extra = 7
2862
2863 return result
2864
2865 test_cases = [
2866 ('now', 'now', {}),
2867 ('utcnow', 'utcnow', {}),
2868 ('now_utc', 'now', {'tz': timezone.utc}),
2869 ('now_fixed', 'now', {'tz': timezone(timedelta(hours=-5), "EST")}),
2870 ]
2871
2872 for name, meth_name, kwargs in test_cases:
2873 with self.subTest(name):
2874 constr = getattr(DateTimeSubclass, meth_name)
2875 dt = constr(**kwargs)
2876
2877 self.assertIsInstance(dt, DateTimeSubclass)
2878 self.assertEqual(dt.extra, 7)
2879
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002880 def test_fromisoformat_datetime(self):
2881 # Test that isoformat() is reversible
2882 base_dates = [
2883 (1, 1, 1),
2884 (1900, 1, 1),
2885 (2004, 11, 12),
2886 (2017, 5, 30)
2887 ]
2888
2889 base_times = [
2890 (0, 0, 0, 0),
2891 (0, 0, 0, 241000),
2892 (0, 0, 0, 234567),
2893 (12, 30, 45, 234567)
2894 ]
2895
2896 separators = [' ', 'T']
2897
2898 tzinfos = [None, timezone.utc,
2899 timezone(timedelta(hours=-5)),
2900 timezone(timedelta(hours=2))]
2901
2902 dts = [self.theclass(*date_tuple, *time_tuple, tzinfo=tzi)
2903 for date_tuple in base_dates
2904 for time_tuple in base_times
2905 for tzi in tzinfos]
2906
2907 for dt in dts:
2908 for sep in separators:
2909 dtstr = dt.isoformat(sep=sep)
2910
2911 with self.subTest(dtstr=dtstr):
2912 dt_rt = self.theclass.fromisoformat(dtstr)
2913 self.assertEqual(dt, dt_rt)
2914
2915 def test_fromisoformat_timezone(self):
2916 base_dt = self.theclass(2014, 12, 30, 12, 30, 45, 217456)
2917
2918 tzoffsets = [
2919 timedelta(hours=5), timedelta(hours=2),
2920 timedelta(hours=6, minutes=27),
2921 timedelta(hours=12, minutes=32, seconds=30),
2922 timedelta(hours=2, minutes=4, seconds=9, microseconds=123456)
2923 ]
2924
2925 tzoffsets += [-1 * td for td in tzoffsets]
2926
2927 tzinfos = [None, timezone.utc,
2928 timezone(timedelta(hours=0))]
2929
2930 tzinfos += [timezone(td) for td in tzoffsets]
2931
2932 for tzi in tzinfos:
2933 dt = base_dt.replace(tzinfo=tzi)
2934 dtstr = dt.isoformat()
2935
2936 with self.subTest(tstr=dtstr):
2937 dt_rt = self.theclass.fromisoformat(dtstr)
2938 assert dt == dt_rt, dt_rt
2939
2940 def test_fromisoformat_separators(self):
2941 separators = [
2942 ' ', 'T', '\u007f', # 1-bit widths
2943 '\u0080', 'ʁ', # 2-bit widths
2944 'ᛇ', '時', # 3-bit widths
Paul Ganssle096329f2018-08-23 11:06:20 -04002945 '🐍', # 4-bit widths
2946 '\ud800', # bpo-34454: Surrogate code point
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002947 ]
2948
2949 for sep in separators:
2950 dt = self.theclass(2018, 1, 31, 23, 59, 47, 124789)
2951 dtstr = dt.isoformat(sep=sep)
2952
2953 with self.subTest(dtstr=dtstr):
2954 dt_rt = self.theclass.fromisoformat(dtstr)
2955 self.assertEqual(dt, dt_rt)
2956
2957 def test_fromisoformat_ambiguous(self):
2958 # Test strings like 2018-01-31+12:15 (where +12:15 is not a time zone)
2959 separators = ['+', '-']
2960 for sep in separators:
2961 dt = self.theclass(2018, 1, 31, 12, 15)
2962 dtstr = dt.isoformat(sep=sep)
2963
2964 with self.subTest(dtstr=dtstr):
2965 dt_rt = self.theclass.fromisoformat(dtstr)
2966 self.assertEqual(dt, dt_rt)
2967
2968 def test_fromisoformat_timespecs(self):
2969 datetime_bases = [
2970 (2009, 12, 4, 8, 17, 45, 123456),
2971 (2009, 12, 4, 8, 17, 45, 0)]
2972
2973 tzinfos = [None, timezone.utc,
2974 timezone(timedelta(hours=-5)),
2975 timezone(timedelta(hours=2)),
2976 timezone(timedelta(hours=6, minutes=27))]
2977
2978 timespecs = ['hours', 'minutes', 'seconds',
2979 'milliseconds', 'microseconds']
2980
2981 for ip, ts in enumerate(timespecs):
2982 for tzi in tzinfos:
2983 for dt_tuple in datetime_bases:
2984 if ts == 'milliseconds':
2985 new_microseconds = 1000 * (dt_tuple[6] // 1000)
2986 dt_tuple = dt_tuple[0:6] + (new_microseconds,)
2987
2988 dt = self.theclass(*(dt_tuple[0:(4 + ip)]), tzinfo=tzi)
2989 dtstr = dt.isoformat(timespec=ts)
2990 with self.subTest(dtstr=dtstr):
2991 dt_rt = self.theclass.fromisoformat(dtstr)
2992 self.assertEqual(dt, dt_rt)
2993
2994 def test_fromisoformat_fails_datetime(self):
2995 # Test that fromisoformat() fails on invalid values
2996 bad_strs = [
2997 '', # Empty string
Paul Ganssle096329f2018-08-23 11:06:20 -04002998 '\ud800', # bpo-34454: Surrogate code point
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002999 '2009.04-19T03', # Wrong first separator
3000 '2009-04.19T03', # Wrong second separator
3001 '2009-04-19T0a', # Invalid hours
3002 '2009-04-19T03:1a:45', # Invalid minutes
3003 '2009-04-19T03:15:4a', # Invalid seconds
3004 '2009-04-19T03;15:45', # Bad first time separator
3005 '2009-04-19T03:15;45', # Bad second time separator
3006 '2009-04-19T03:15:4500:00', # Bad time zone separator
3007 '2009-04-19T03:15:45.2345', # Too many digits for milliseconds
3008 '2009-04-19T03:15:45.1234567', # Too many digits for microseconds
3009 '2009-04-19T03:15:45.123456+24:30', # Invalid time zone offset
3010 '2009-04-19T03:15:45.123456-24:30', # Invalid negative offset
3011 '2009-04-10ᛇᛇᛇᛇᛇ12:15', # Too many unicode separators
Paul Ganssle096329f2018-08-23 11:06:20 -04003012 '2009-04\ud80010T12:15', # Surrogate char in date
3013 '2009-04-10T12\ud80015', # Surrogate char in time
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003014 '2009-04-19T1', # Incomplete hours
3015 '2009-04-19T12:3', # Incomplete minutes
3016 '2009-04-19T12:30:4', # Incomplete seconds
3017 '2009-04-19T12:', # Ends with time separator
3018 '2009-04-19T12:30:', # Ends with time separator
3019 '2009-04-19T12:30:45.', # Ends with time separator
3020 '2009-04-19T12:30:45.123456+', # Ends with timzone separator
3021 '2009-04-19T12:30:45.123456-', # Ends with timzone separator
3022 '2009-04-19T12:30:45.123456-05:00a', # Extra text
3023 '2009-04-19T12:30:45.123-05:00a', # Extra text
3024 '2009-04-19T12:30:45-05:00a', # Extra text
3025 ]
3026
3027 for bad_str in bad_strs:
3028 with self.subTest(bad_str=bad_str):
3029 with self.assertRaises(ValueError):
3030 self.theclass.fromisoformat(bad_str)
3031
Paul Ganssle3df85402018-10-22 12:32:52 -04003032 def test_fromisoformat_fails_surrogate(self):
3033 # Test that when fromisoformat() fails with a surrogate character as
3034 # the separator, the error message contains the original string
3035 dtstr = "2018-01-03\ud80001:0113"
3036
3037 with self.assertRaisesRegex(ValueError, re.escape(repr(dtstr))):
3038 self.theclass.fromisoformat(dtstr)
3039
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003040 def test_fromisoformat_utc(self):
3041 dt_str = '2014-04-19T13:21:13+00:00'
3042 dt = self.theclass.fromisoformat(dt_str)
3043
3044 self.assertIs(dt.tzinfo, timezone.utc)
3045
3046 def test_fromisoformat_subclass(self):
3047 class DateTimeSubclass(self.theclass):
3048 pass
3049
3050 dt = DateTimeSubclass(2014, 12, 14, 9, 30, 45, 457390,
3051 tzinfo=timezone(timedelta(hours=10, minutes=45)))
3052
3053 dt_rt = DateTimeSubclass.fromisoformat(dt.isoformat())
3054
3055 self.assertEqual(dt, dt_rt)
3056 self.assertIsInstance(dt_rt, DateTimeSubclass)
3057
3058
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003059class TestSubclassDateTime(TestDateTime):
3060 theclass = SubclassDatetime
3061 # Override tests not designed for subclass
Zachary Ware9fe6d862013-12-08 00:20:35 -06003062 @unittest.skip('not appropriate for subclasses')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003063 def test_roundtrip(self):
3064 pass
3065
3066class SubclassTime(time):
3067 sub_var = 1
3068
3069class TestTime(HarmlessMixedComparison, unittest.TestCase):
3070
3071 theclass = time
3072
3073 def test_basic_attributes(self):
3074 t = self.theclass(12, 0)
3075 self.assertEqual(t.hour, 12)
3076 self.assertEqual(t.minute, 0)
3077 self.assertEqual(t.second, 0)
3078 self.assertEqual(t.microsecond, 0)
3079
3080 def test_basic_attributes_nonzero(self):
3081 # Make sure all attributes are non-zero so bugs in
3082 # bit-shifting access show up.
3083 t = self.theclass(12, 59, 59, 8000)
3084 self.assertEqual(t.hour, 12)
3085 self.assertEqual(t.minute, 59)
3086 self.assertEqual(t.second, 59)
3087 self.assertEqual(t.microsecond, 8000)
3088
3089 def test_roundtrip(self):
3090 t = self.theclass(1, 2, 3, 4)
3091
3092 # Verify t -> string -> time identity.
3093 s = repr(t)
3094 self.assertTrue(s.startswith('datetime.'))
3095 s = s[9:]
3096 t2 = eval(s)
3097 self.assertEqual(t, t2)
3098
3099 # Verify identity via reconstructing from pieces.
3100 t2 = self.theclass(t.hour, t.minute, t.second,
3101 t.microsecond)
3102 self.assertEqual(t, t2)
3103
3104 def test_comparing(self):
3105 args = [1, 2, 3, 4]
3106 t1 = self.theclass(*args)
3107 t2 = self.theclass(*args)
3108 self.assertEqual(t1, t2)
3109 self.assertTrue(t1 <= t2)
3110 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003111 self.assertFalse(t1 != t2)
3112 self.assertFalse(t1 < t2)
3113 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003114
3115 for i in range(len(args)):
3116 newargs = args[:]
3117 newargs[i] = args[i] + 1
3118 t2 = self.theclass(*newargs) # this is larger than t1
3119 self.assertTrue(t1 < t2)
3120 self.assertTrue(t2 > t1)
3121 self.assertTrue(t1 <= t2)
3122 self.assertTrue(t2 >= t1)
3123 self.assertTrue(t1 != t2)
3124 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003125 self.assertFalse(t1 == t2)
3126 self.assertFalse(t2 == t1)
3127 self.assertFalse(t1 > t2)
3128 self.assertFalse(t2 < t1)
3129 self.assertFalse(t1 >= t2)
3130 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003131
3132 for badarg in OTHERSTUFF:
3133 self.assertEqual(t1 == badarg, False)
3134 self.assertEqual(t1 != badarg, True)
3135 self.assertEqual(badarg == t1, False)
3136 self.assertEqual(badarg != t1, True)
3137
3138 self.assertRaises(TypeError, lambda: t1 <= badarg)
3139 self.assertRaises(TypeError, lambda: t1 < badarg)
3140 self.assertRaises(TypeError, lambda: t1 > badarg)
3141 self.assertRaises(TypeError, lambda: t1 >= badarg)
3142 self.assertRaises(TypeError, lambda: badarg <= t1)
3143 self.assertRaises(TypeError, lambda: badarg < t1)
3144 self.assertRaises(TypeError, lambda: badarg > t1)
3145 self.assertRaises(TypeError, lambda: badarg >= t1)
3146
3147 def test_bad_constructor_arguments(self):
3148 # bad hours
3149 self.theclass(0, 0) # no exception
3150 self.theclass(23, 0) # no exception
3151 self.assertRaises(ValueError, self.theclass, -1, 0)
3152 self.assertRaises(ValueError, self.theclass, 24, 0)
3153 # bad minutes
3154 self.theclass(23, 0) # no exception
3155 self.theclass(23, 59) # no exception
3156 self.assertRaises(ValueError, self.theclass, 23, -1)
3157 self.assertRaises(ValueError, self.theclass, 23, 60)
3158 # bad seconds
3159 self.theclass(23, 59, 0) # no exception
3160 self.theclass(23, 59, 59) # no exception
3161 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
3162 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
3163 # bad microseconds
3164 self.theclass(23, 59, 59, 0) # no exception
3165 self.theclass(23, 59, 59, 999999) # no exception
3166 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
3167 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
3168
3169 def test_hash_equality(self):
3170 d = self.theclass(23, 30, 17)
3171 e = self.theclass(23, 30, 17)
3172 self.assertEqual(d, e)
3173 self.assertEqual(hash(d), hash(e))
3174
3175 dic = {d: 1}
3176 dic[e] = 2
3177 self.assertEqual(len(dic), 1)
3178 self.assertEqual(dic[d], 2)
3179 self.assertEqual(dic[e], 2)
3180
3181 d = self.theclass(0, 5, 17)
3182 e = self.theclass(0, 5, 17)
3183 self.assertEqual(d, e)
3184 self.assertEqual(hash(d), hash(e))
3185
3186 dic = {d: 1}
3187 dic[e] = 2
3188 self.assertEqual(len(dic), 1)
3189 self.assertEqual(dic[d], 2)
3190 self.assertEqual(dic[e], 2)
3191
3192 def test_isoformat(self):
3193 t = self.theclass(4, 5, 1, 123)
3194 self.assertEqual(t.isoformat(), "04:05:01.000123")
3195 self.assertEqual(t.isoformat(), str(t))
3196
3197 t = self.theclass()
3198 self.assertEqual(t.isoformat(), "00:00:00")
3199 self.assertEqual(t.isoformat(), str(t))
3200
3201 t = self.theclass(microsecond=1)
3202 self.assertEqual(t.isoformat(), "00:00:00.000001")
3203 self.assertEqual(t.isoformat(), str(t))
3204
3205 t = self.theclass(microsecond=10)
3206 self.assertEqual(t.isoformat(), "00:00:00.000010")
3207 self.assertEqual(t.isoformat(), str(t))
3208
3209 t = self.theclass(microsecond=100)
3210 self.assertEqual(t.isoformat(), "00:00:00.000100")
3211 self.assertEqual(t.isoformat(), str(t))
3212
3213 t = self.theclass(microsecond=1000)
3214 self.assertEqual(t.isoformat(), "00:00:00.001000")
3215 self.assertEqual(t.isoformat(), str(t))
3216
3217 t = self.theclass(microsecond=10000)
3218 self.assertEqual(t.isoformat(), "00:00:00.010000")
3219 self.assertEqual(t.isoformat(), str(t))
3220
3221 t = self.theclass(microsecond=100000)
3222 self.assertEqual(t.isoformat(), "00:00:00.100000")
3223 self.assertEqual(t.isoformat(), str(t))
3224
Alexander Belopolskya2998a62016-03-06 14:58:43 -05003225 t = self.theclass(hour=12, minute=34, second=56, microsecond=123456)
3226 self.assertEqual(t.isoformat(timespec='hours'), "12")
3227 self.assertEqual(t.isoformat(timespec='minutes'), "12:34")
3228 self.assertEqual(t.isoformat(timespec='seconds'), "12:34:56")
3229 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.123")
3230 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.123456")
3231 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56.123456")
3232 self.assertRaises(ValueError, t.isoformat, timespec='monkey')
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03003233 # bpo-34482: Check that surrogates are handled properly.
3234 self.assertRaises(ValueError, t.isoformat, timespec='\ud800')
Alexander Belopolskya2998a62016-03-06 14:58:43 -05003235
3236 t = self.theclass(hour=12, minute=34, second=56, microsecond=999500)
3237 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.999")
3238
3239 t = self.theclass(hour=12, minute=34, second=56, microsecond=0)
3240 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.000")
3241 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.000000")
3242 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56")
3243
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003244 def test_isoformat_timezone(self):
3245 tzoffsets = [
3246 ('05:00', timedelta(hours=5)),
3247 ('02:00', timedelta(hours=2)),
3248 ('06:27', timedelta(hours=6, minutes=27)),
3249 ('12:32:30', timedelta(hours=12, minutes=32, seconds=30)),
3250 ('02:04:09.123456', timedelta(hours=2, minutes=4, seconds=9, microseconds=123456))
3251 ]
3252
3253 tzinfos = [
3254 ('', None),
3255 ('+00:00', timezone.utc),
3256 ('+00:00', timezone(timedelta(0))),
3257 ]
3258
3259 tzinfos += [
3260 (prefix + expected, timezone(sign * td))
3261 for expected, td in tzoffsets
3262 for prefix, sign in [('-', -1), ('+', 1)]
3263 ]
3264
3265 t_base = self.theclass(12, 37, 9)
3266 exp_base = '12:37:09'
3267
3268 for exp_tz, tzi in tzinfos:
3269 t = t_base.replace(tzinfo=tzi)
3270 exp = exp_base + exp_tz
3271 with self.subTest(tzi=tzi):
3272 assert t.isoformat() == exp
3273
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003274 def test_1653736(self):
3275 # verify it doesn't accept extra keyword arguments
3276 t = self.theclass(second=1)
3277 self.assertRaises(TypeError, t.isoformat, foo=3)
3278
3279 def test_strftime(self):
3280 t = self.theclass(1, 2, 3, 4)
3281 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
3282 # A naive object replaces %z and %Z with empty strings.
3283 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
3284
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03003285 # bpo-34482: Check that surrogates don't cause a crash.
3286 try:
3287 t.strftime('%H\ud800%M')
3288 except UnicodeEncodeError:
3289 pass
3290
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003291 def test_format(self):
3292 t = self.theclass(1, 2, 3, 4)
3293 self.assertEqual(t.__format__(''), str(t))
3294
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02003295 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04003296 t.__format__(123)
3297
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003298 # check that a derived class's __str__() gets called
3299 class A(self.theclass):
3300 def __str__(self):
3301 return 'A'
3302 a = A(1, 2, 3, 4)
3303 self.assertEqual(a.__format__(''), 'A')
3304
3305 # check that a derived class's strftime gets called
3306 class B(self.theclass):
3307 def strftime(self, format_spec):
3308 return 'B'
3309 b = B(1, 2, 3, 4)
3310 self.assertEqual(b.__format__(''), str(t))
3311
3312 for fmt in ['%H %M %S',
3313 ]:
3314 self.assertEqual(t.__format__(fmt), t.strftime(fmt))
3315 self.assertEqual(a.__format__(fmt), t.strftime(fmt))
3316 self.assertEqual(b.__format__(fmt), 'B')
3317
3318 def test_str(self):
3319 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
3320 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
3321 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
3322 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
3323 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
3324
3325 def test_repr(self):
3326 name = 'datetime.' + self.theclass.__name__
3327 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
3328 "%s(1, 2, 3, 4)" % name)
3329 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
3330 "%s(10, 2, 3, 4000)" % name)
3331 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
3332 "%s(0, 2, 3, 400000)" % name)
3333 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
3334 "%s(12, 2, 3)" % name)
3335 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
3336 "%s(23, 15)" % name)
3337
3338 def test_resolution_info(self):
3339 self.assertIsInstance(self.theclass.min, self.theclass)
3340 self.assertIsInstance(self.theclass.max, self.theclass)
3341 self.assertIsInstance(self.theclass.resolution, timedelta)
3342 self.assertTrue(self.theclass.max > self.theclass.min)
3343
3344 def test_pickling(self):
3345 args = 20, 59, 16, 64**2
3346 orig = self.theclass(*args)
3347 for pickler, unpickler, proto in pickle_choices:
3348 green = pickler.dumps(orig, proto)
3349 derived = unpickler.loads(green)
3350 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003351 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003352
3353 def test_pickling_subclass_time(self):
3354 args = 20, 59, 16, 64**2
3355 orig = SubclassTime(*args)
3356 for pickler, unpickler, proto in pickle_choices:
3357 green = pickler.dumps(orig, proto)
3358 derived = unpickler.loads(green)
3359 self.assertEqual(orig, derived)
3360
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02003361 def test_compat_unpickle(self):
3362 tests = [
Justin Blanchard122376d2019-08-29 03:36:15 -04003363 (b"cdatetime\ntime\n(S'\\x14;\\x10\\x00\\x10\\x00'\ntR.",
3364 (20, 59, 16, 64**2)),
3365 (b'cdatetime\ntime\n(U\x06\x14;\x10\x00\x10\x00tR.',
3366 (20, 59, 16, 64**2)),
3367 (b'\x80\x02cdatetime\ntime\nU\x06\x14;\x10\x00\x10\x00\x85R.',
3368 (20, 59, 16, 64**2)),
3369 (b"cdatetime\ntime\n(S'\\x14;\\x19\\x00\\x10\\x00'\ntR.",
3370 (20, 59, 25, 64**2)),
3371 (b'cdatetime\ntime\n(U\x06\x14;\x19\x00\x10\x00tR.',
3372 (20, 59, 25, 64**2)),
3373 (b'\x80\x02cdatetime\ntime\nU\x06\x14;\x19\x00\x10\x00\x85R.',
3374 (20, 59, 25, 64**2)),
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02003375 ]
Justin Blanchard122376d2019-08-29 03:36:15 -04003376 for i, (data, args) in enumerate(tests):
3377 with self.subTest(i=i):
3378 expected = self.theclass(*args)
3379 for loads in pickle_loads:
3380 derived = loads(data, encoding='latin1')
3381 self.assertEqual(derived, expected)
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02003382
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003383 def test_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003384 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003385 cls = self.theclass
3386 self.assertTrue(cls(1))
3387 self.assertTrue(cls(0, 1))
3388 self.assertTrue(cls(0, 0, 1))
3389 self.assertTrue(cls(0, 0, 0, 1))
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003390 self.assertTrue(cls(0))
3391 self.assertTrue(cls())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003392
3393 def test_replace(self):
3394 cls = self.theclass
3395 args = [1, 2, 3, 4]
3396 base = cls(*args)
3397 self.assertEqual(base, base.replace())
3398
3399 i = 0
3400 for name, newval in (("hour", 5),
3401 ("minute", 6),
3402 ("second", 7),
3403 ("microsecond", 8)):
3404 newargs = args[:]
3405 newargs[i] = newval
3406 expected = cls(*newargs)
3407 got = base.replace(**{name: newval})
3408 self.assertEqual(expected, got)
3409 i += 1
3410
3411 # Out of bounds.
3412 base = cls(1)
3413 self.assertRaises(ValueError, base.replace, hour=24)
3414 self.assertRaises(ValueError, base.replace, minute=-1)
3415 self.assertRaises(ValueError, base.replace, second=100)
3416 self.assertRaises(ValueError, base.replace, microsecond=1000000)
3417
Paul Ganssle191e9932017-11-09 16:34:29 -05003418 def test_subclass_replace(self):
3419 class TimeSubclass(self.theclass):
3420 pass
3421
3422 ctime = TimeSubclass(12, 30)
3423 self.assertIs(type(ctime.replace(hour=10)), TimeSubclass)
3424
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003425 def test_subclass_time(self):
3426
3427 class C(self.theclass):
3428 theAnswer = 42
3429
3430 def __new__(cls, *args, **kws):
3431 temp = kws.copy()
3432 extra = temp.pop('extra')
3433 result = self.theclass.__new__(cls, *args, **temp)
3434 result.extra = extra
3435 return result
3436
3437 def newmeth(self, start):
3438 return start + self.hour + self.second
3439
3440 args = 4, 5, 6
3441
3442 dt1 = self.theclass(*args)
3443 dt2 = C(*args, **{'extra': 7})
3444
3445 self.assertEqual(dt2.__class__, C)
3446 self.assertEqual(dt2.theAnswer, 42)
3447 self.assertEqual(dt2.extra, 7)
3448 self.assertEqual(dt1.isoformat(), dt2.isoformat())
3449 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
3450
3451 def test_backdoor_resistance(self):
3452 # see TestDate.test_backdoor_resistance().
3453 base = '2:59.0'
3454 for hour_byte in ' ', '9', chr(24), '\xff':
3455 self.assertRaises(TypeError, self.theclass,
3456 hour_byte + base[1:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04003457 # Good bytes, but bad tzinfo:
3458 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
3459 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003460
3461# A mixin for classes with a tzinfo= argument. Subclasses must define
Martin Panter46f50722016-05-26 05:35:26 +00003462# theclass as a class attribute, and theclass(1, 1, 1, tzinfo=whatever)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003463# must be legit (which is true for time and datetime).
3464class TZInfoBase:
3465
3466 def test_argument_passing(self):
3467 cls = self.theclass
3468 # A datetime passes itself on, a time passes None.
3469 class introspective(tzinfo):
3470 def tzname(self, dt): return dt and "real" or "none"
3471 def utcoffset(self, dt):
3472 return timedelta(minutes = dt and 42 or -42)
3473 dst = utcoffset
3474
3475 obj = cls(1, 2, 3, tzinfo=introspective())
3476
3477 expected = cls is time and "none" or "real"
3478 self.assertEqual(obj.tzname(), expected)
3479
3480 expected = timedelta(minutes=(cls is time and -42 or 42))
3481 self.assertEqual(obj.utcoffset(), expected)
3482 self.assertEqual(obj.dst(), expected)
3483
3484 def test_bad_tzinfo_classes(self):
3485 cls = self.theclass
3486 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
3487
3488 class NiceTry(object):
3489 def __init__(self): pass
3490 def utcoffset(self, dt): pass
3491 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
3492
3493 class BetterTry(tzinfo):
3494 def __init__(self): pass
3495 def utcoffset(self, dt): pass
3496 b = BetterTry()
3497 t = cls(1, 1, 1, tzinfo=b)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003498 self.assertIs(t.tzinfo, b)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003499
3500 def test_utc_offset_out_of_bounds(self):
3501 class Edgy(tzinfo):
3502 def __init__(self, offset):
3503 self.offset = timedelta(minutes=offset)
3504 def utcoffset(self, dt):
3505 return self.offset
3506
3507 cls = self.theclass
3508 for offset, legit in ((-1440, False),
3509 (-1439, True),
3510 (1439, True),
3511 (1440, False)):
3512 if cls is time:
3513 t = cls(1, 2, 3, tzinfo=Edgy(offset))
3514 elif cls is datetime:
3515 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
3516 else:
3517 assert 0, "impossible"
3518 if legit:
3519 aofs = abs(offset)
3520 h, m = divmod(aofs, 60)
3521 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
3522 if isinstance(t, datetime):
3523 t = t.timetz()
3524 self.assertEqual(str(t), "01:02:03" + tag)
3525 else:
3526 self.assertRaises(ValueError, str, t)
3527
3528 def test_tzinfo_classes(self):
3529 cls = self.theclass
3530 class C1(tzinfo):
3531 def utcoffset(self, dt): return None
3532 def dst(self, dt): return None
3533 def tzname(self, dt): return None
3534 for t in (cls(1, 1, 1),
3535 cls(1, 1, 1, tzinfo=None),
3536 cls(1, 1, 1, tzinfo=C1())):
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003537 self.assertIsNone(t.utcoffset())
3538 self.assertIsNone(t.dst())
3539 self.assertIsNone(t.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003540
3541 class C3(tzinfo):
3542 def utcoffset(self, dt): return timedelta(minutes=-1439)
3543 def dst(self, dt): return timedelta(minutes=1439)
3544 def tzname(self, dt): return "aname"
3545 t = cls(1, 1, 1, tzinfo=C3())
3546 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
3547 self.assertEqual(t.dst(), timedelta(minutes=1439))
3548 self.assertEqual(t.tzname(), "aname")
3549
3550 # Wrong types.
3551 class C4(tzinfo):
3552 def utcoffset(self, dt): return "aname"
3553 def dst(self, dt): return 7
3554 def tzname(self, dt): return 0
3555 t = cls(1, 1, 1, tzinfo=C4())
3556 self.assertRaises(TypeError, t.utcoffset)
3557 self.assertRaises(TypeError, t.dst)
3558 self.assertRaises(TypeError, t.tzname)
3559
3560 # Offset out of range.
3561 class C6(tzinfo):
3562 def utcoffset(self, dt): return timedelta(hours=-24)
3563 def dst(self, dt): return timedelta(hours=24)
3564 t = cls(1, 1, 1, tzinfo=C6())
3565 self.assertRaises(ValueError, t.utcoffset)
3566 self.assertRaises(ValueError, t.dst)
3567
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04003568 # Not a whole number of seconds.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003569 class C7(tzinfo):
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04003570 def utcoffset(self, dt): return timedelta(microseconds=61)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003571 def dst(self, dt): return timedelta(microseconds=-81)
3572 t = cls(1, 1, 1, tzinfo=C7())
Alexander Belopolsky018d3532017-07-31 10:26:50 -04003573 self.assertEqual(t.utcoffset(), timedelta(microseconds=61))
3574 self.assertEqual(t.dst(), timedelta(microseconds=-81))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003575
3576 def test_aware_compare(self):
3577 cls = self.theclass
3578
3579 # Ensure that utcoffset() gets ignored if the comparands have
3580 # the same tzinfo member.
3581 class OperandDependentOffset(tzinfo):
3582 def utcoffset(self, t):
3583 if t.minute < 10:
3584 # d0 and d1 equal after adjustment
3585 return timedelta(minutes=t.minute)
3586 else:
3587 # d2 off in the weeds
3588 return timedelta(minutes=59)
3589
3590 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
3591 d0 = base.replace(minute=3)
3592 d1 = base.replace(minute=9)
3593 d2 = base.replace(minute=11)
3594 for x in d0, d1, d2:
3595 for y in d0, d1, d2:
3596 for op in lt, le, gt, ge, eq, ne:
3597 got = op(x, y)
3598 expected = op(x.minute, y.minute)
3599 self.assertEqual(got, expected)
3600
3601 # However, if they're different members, uctoffset is not ignored.
penguindustin96466302019-05-06 14:57:17 -04003602 # Note that a time can't actually have an operand-dependent offset,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003603 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
3604 # so skip this test for time.
3605 if cls is not time:
3606 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
3607 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
3608 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
3609 for x in d0, d1, d2:
3610 for y in d0, d1, d2:
3611 got = (x > y) - (x < y)
3612 if (x is d0 or x is d1) and (y is d0 or y is d1):
3613 expected = 0
3614 elif x is y is d2:
3615 expected = 0
3616 elif x is d2:
3617 expected = -1
3618 else:
3619 assert y is d2
3620 expected = 1
3621 self.assertEqual(got, expected)
3622
3623
3624# Testing time objects with a non-None tzinfo.
3625class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
3626 theclass = time
3627
3628 def test_empty(self):
3629 t = self.theclass()
3630 self.assertEqual(t.hour, 0)
3631 self.assertEqual(t.minute, 0)
3632 self.assertEqual(t.second, 0)
3633 self.assertEqual(t.microsecond, 0)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003634 self.assertIsNone(t.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003635
3636 def test_zones(self):
3637 est = FixedOffset(-300, "EST", 1)
3638 utc = FixedOffset(0, "UTC", -2)
3639 met = FixedOffset(60, "MET", 3)
3640 t1 = time( 7, 47, tzinfo=est)
3641 t2 = time(12, 47, tzinfo=utc)
3642 t3 = time(13, 47, tzinfo=met)
3643 t4 = time(microsecond=40)
3644 t5 = time(microsecond=40, tzinfo=utc)
3645
3646 self.assertEqual(t1.tzinfo, est)
3647 self.assertEqual(t2.tzinfo, utc)
3648 self.assertEqual(t3.tzinfo, met)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003649 self.assertIsNone(t4.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003650 self.assertEqual(t5.tzinfo, utc)
3651
3652 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
3653 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
3654 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003655 self.assertIsNone(t4.utcoffset())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003656 self.assertRaises(TypeError, t1.utcoffset, "no args")
3657
3658 self.assertEqual(t1.tzname(), "EST")
3659 self.assertEqual(t2.tzname(), "UTC")
3660 self.assertEqual(t3.tzname(), "MET")
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003661 self.assertIsNone(t4.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003662 self.assertRaises(TypeError, t1.tzname, "no args")
3663
3664 self.assertEqual(t1.dst(), timedelta(minutes=1))
3665 self.assertEqual(t2.dst(), timedelta(minutes=-2))
3666 self.assertEqual(t3.dst(), timedelta(minutes=3))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003667 self.assertIsNone(t4.dst())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003668 self.assertRaises(TypeError, t1.dst, "no args")
3669
3670 self.assertEqual(hash(t1), hash(t2))
3671 self.assertEqual(hash(t1), hash(t3))
3672 self.assertEqual(hash(t2), hash(t3))
3673
3674 self.assertEqual(t1, t2)
3675 self.assertEqual(t1, t3)
3676 self.assertEqual(t2, t3)
Alexander Belopolsky08313822012-06-15 20:19:47 -04003677 self.assertNotEqual(t4, t5) # mixed tz-aware & naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003678 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
3679 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
3680
3681 self.assertEqual(str(t1), "07:47:00-05:00")
3682 self.assertEqual(str(t2), "12:47:00+00:00")
3683 self.assertEqual(str(t3), "13:47:00+01:00")
3684 self.assertEqual(str(t4), "00:00:00.000040")
3685 self.assertEqual(str(t5), "00:00:00.000040+00:00")
3686
3687 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
3688 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
3689 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
3690 self.assertEqual(t4.isoformat(), "00:00:00.000040")
3691 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
3692
3693 d = 'datetime.time'
3694 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
3695 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
3696 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
3697 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
3698 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
3699
3700 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
3701 "07:47:00 %Z=EST %z=-0500")
3702 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
3703 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
3704
3705 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
3706 t1 = time(23, 59, tzinfo=yuck)
3707 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
3708 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
3709
3710 # Check that an invalid tzname result raises an exception.
3711 class Badtzname(tzinfo):
Alexander Belopolskye239d232010-12-08 23:31:48 +00003712 tz = 42
3713 def tzname(self, dt): return self.tz
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003714 t = time(2, 3, 4, tzinfo=Badtzname())
3715 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
3716 self.assertRaises(TypeError, t.strftime, "%Z")
3717
Alexander Belopolskye239d232010-12-08 23:31:48 +00003718 # Issue #6697:
Utkarsh Upadhyay287c5592017-07-21 02:14:54 +02003719 if '_Fast' in self.__class__.__name__:
Alexander Belopolskye239d232010-12-08 23:31:48 +00003720 Badtzname.tz = '\ud800'
3721 self.assertRaises(ValueError, t.strftime, "%Z")
3722
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003723 def test_hash_edge_cases(self):
3724 # Offsets that overflow a basic time.
3725 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
3726 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
3727 self.assertEqual(hash(t1), hash(t2))
3728
3729 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
3730 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
3731 self.assertEqual(hash(t1), hash(t2))
3732
3733 def test_pickling(self):
3734 # Try one without a tzinfo.
3735 args = 20, 59, 16, 64**2
3736 orig = self.theclass(*args)
3737 for pickler, unpickler, proto in pickle_choices:
3738 green = pickler.dumps(orig, proto)
3739 derived = unpickler.loads(green)
3740 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003741 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003742
3743 # Try one with a tzinfo.
3744 tinfo = PicklableFixedOffset(-300, 'cookie')
3745 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
3746 for pickler, unpickler, proto in pickle_choices:
3747 green = pickler.dumps(orig, proto)
3748 derived = unpickler.loads(green)
3749 self.assertEqual(orig, derived)
3750 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3751 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3752 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003753 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003754
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02003755 def test_compat_unpickle(self):
3756 tests = [
3757 b"cdatetime\ntime\n(S'\\x05\\x06\\x07\\x01\\xe2@'\n"
3758 b"ctest.datetimetester\nPicklableFixedOffset\n(tR"
3759 b"(dS'_FixedOffset__offset'\ncdatetime\ntimedelta\n"
3760 b"(I-1\nI68400\nI0\ntRs"
3761 b"S'_FixedOffset__dstoffset'\nNs"
3762 b"S'_FixedOffset__name'\nS'cookie'\nsbtR.",
3763
3764 b'cdatetime\ntime\n(U\x06\x05\x06\x07\x01\xe2@'
3765 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
3766 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
3767 b'(J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00tR'
3768 b'U\x17_FixedOffset__dstoffsetN'
3769 b'U\x12_FixedOffset__nameU\x06cookieubtR.',
3770
3771 b'\x80\x02cdatetime\ntime\nU\x06\x05\x06\x07\x01\xe2@'
3772 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
3773 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
3774 b'J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00\x87R'
3775 b'U\x17_FixedOffset__dstoffsetN'
3776 b'U\x12_FixedOffset__nameU\x06cookieub\x86R.',
3777 ]
3778
3779 tinfo = PicklableFixedOffset(-300, 'cookie')
3780 expected = self.theclass(5, 6, 7, 123456, tzinfo=tinfo)
3781 for data in tests:
3782 for loads in pickle_loads:
3783 derived = loads(data, encoding='latin1')
3784 self.assertEqual(derived, expected, repr(data))
3785 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3786 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3787 self.assertEqual(derived.tzname(), 'cookie')
3788
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003789 def test_more_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003790 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003791 cls = self.theclass
3792
3793 t = cls(0, tzinfo=FixedOffset(-300, ""))
3794 self.assertTrue(t)
3795
3796 t = cls(5, tzinfo=FixedOffset(-300, ""))
3797 self.assertTrue(t)
3798
3799 t = cls(5, tzinfo=FixedOffset(300, ""))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003800 self.assertTrue(t)
3801
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003802 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
3803 self.assertTrue(t)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003804
3805 def test_replace(self):
3806 cls = self.theclass
3807 z100 = FixedOffset(100, "+100")
3808 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3809 args = [1, 2, 3, 4, z100]
3810 base = cls(*args)
3811 self.assertEqual(base, base.replace())
3812
3813 i = 0
3814 for name, newval in (("hour", 5),
3815 ("minute", 6),
3816 ("second", 7),
3817 ("microsecond", 8),
3818 ("tzinfo", zm200)):
3819 newargs = args[:]
3820 newargs[i] = newval
3821 expected = cls(*newargs)
3822 got = base.replace(**{name: newval})
3823 self.assertEqual(expected, got)
3824 i += 1
3825
3826 # Ensure we can get rid of a tzinfo.
3827 self.assertEqual(base.tzname(), "+100")
3828 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003829 self.assertIsNone(base2.tzinfo)
3830 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003831
3832 # Ensure we can add one.
3833 base3 = base2.replace(tzinfo=z100)
3834 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003835 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003836
3837 # Out of bounds.
3838 base = cls(1)
3839 self.assertRaises(ValueError, base.replace, hour=24)
3840 self.assertRaises(ValueError, base.replace, minute=-1)
3841 self.assertRaises(ValueError, base.replace, second=100)
3842 self.assertRaises(ValueError, base.replace, microsecond=1000000)
3843
3844 def test_mixed_compare(self):
Serhiy Storchaka17e52642019-08-04 12:38:46 +03003845 t1 = self.theclass(1, 2, 3)
3846 t2 = self.theclass(1, 2, 3)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003847 self.assertEqual(t1, t2)
3848 t2 = t2.replace(tzinfo=None)
3849 self.assertEqual(t1, t2)
3850 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3851 self.assertEqual(t1, t2)
3852 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04003853 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003854
3855 # In time w/ identical tzinfo objects, utcoffset is ignored.
3856 class Varies(tzinfo):
3857 def __init__(self):
3858 self.offset = timedelta(minutes=22)
3859 def utcoffset(self, t):
3860 self.offset += timedelta(minutes=1)
3861 return self.offset
3862
3863 v = Varies()
3864 t1 = t2.replace(tzinfo=v)
3865 t2 = t2.replace(tzinfo=v)
3866 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3867 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3868 self.assertEqual(t1, t2)
3869
3870 # But if they're not identical, it isn't ignored.
3871 t2 = t2.replace(tzinfo=Varies())
3872 self.assertTrue(t1 < t2) # t1's offset counter still going up
3873
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003874 def test_fromisoformat(self):
3875 time_examples = [
3876 (0, 0, 0, 0),
3877 (23, 59, 59, 999999),
3878 ]
3879
3880 hh = (9, 12, 20)
3881 mm = (5, 30)
3882 ss = (4, 45)
3883 usec = (0, 245000, 678901)
3884
3885 time_examples += list(itertools.product(hh, mm, ss, usec))
3886
3887 tzinfos = [None, timezone.utc,
3888 timezone(timedelta(hours=2)),
3889 timezone(timedelta(hours=6, minutes=27))]
3890
3891 for ttup in time_examples:
3892 for tzi in tzinfos:
3893 t = self.theclass(*ttup, tzinfo=tzi)
3894 tstr = t.isoformat()
3895
3896 with self.subTest(tstr=tstr):
3897 t_rt = self.theclass.fromisoformat(tstr)
3898 self.assertEqual(t, t_rt)
3899
3900 def test_fromisoformat_timezone(self):
3901 base_time = self.theclass(12, 30, 45, 217456)
3902
3903 tzoffsets = [
3904 timedelta(hours=5), timedelta(hours=2),
3905 timedelta(hours=6, minutes=27),
3906 timedelta(hours=12, minutes=32, seconds=30),
3907 timedelta(hours=2, minutes=4, seconds=9, microseconds=123456)
3908 ]
3909
3910 tzoffsets += [-1 * td for td in tzoffsets]
3911
3912 tzinfos = [None, timezone.utc,
3913 timezone(timedelta(hours=0))]
3914
3915 tzinfos += [timezone(td) for td in tzoffsets]
3916
3917 for tzi in tzinfos:
3918 t = base_time.replace(tzinfo=tzi)
3919 tstr = t.isoformat()
3920
3921 with self.subTest(tstr=tstr):
3922 t_rt = self.theclass.fromisoformat(tstr)
3923 assert t == t_rt, t_rt
3924
3925 def test_fromisoformat_timespecs(self):
3926 time_bases = [
3927 (8, 17, 45, 123456),
3928 (8, 17, 45, 0)
3929 ]
3930
3931 tzinfos = [None, timezone.utc,
3932 timezone(timedelta(hours=-5)),
3933 timezone(timedelta(hours=2)),
3934 timezone(timedelta(hours=6, minutes=27))]
3935
3936 timespecs = ['hours', 'minutes', 'seconds',
3937 'milliseconds', 'microseconds']
3938
3939 for ip, ts in enumerate(timespecs):
3940 for tzi in tzinfos:
3941 for t_tuple in time_bases:
3942 if ts == 'milliseconds':
3943 new_microseconds = 1000 * (t_tuple[-1] // 1000)
3944 t_tuple = t_tuple[0:-1] + (new_microseconds,)
3945
3946 t = self.theclass(*(t_tuple[0:(1 + ip)]), tzinfo=tzi)
3947 tstr = t.isoformat(timespec=ts)
3948 with self.subTest(tstr=tstr):
3949 t_rt = self.theclass.fromisoformat(tstr)
3950 self.assertEqual(t, t_rt)
3951
3952 def test_fromisoformat_fails(self):
3953 bad_strs = [
3954 '', # Empty string
Paul Ganssle096329f2018-08-23 11:06:20 -04003955 '12\ud80000', # Invalid separator - surrogate char
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003956 '12:', # Ends on a separator
3957 '12:30:', # Ends on a separator
3958 '12:30:15.', # Ends on a separator
3959 '1', # Incomplete hours
3960 '12:3', # Incomplete minutes
3961 '12:30:1', # Incomplete seconds
3962 '1a:30:45.334034', # Invalid character in hours
3963 '12:a0:45.334034', # Invalid character in minutes
3964 '12:30:a5.334034', # Invalid character in seconds
3965 '12:30:45.1234', # Too many digits for milliseconds
3966 '12:30:45.1234567', # Too many digits for microseconds
3967 '12:30:45.123456+24:30', # Invalid time zone offset
3968 '12:30:45.123456-24:30', # Invalid negative offset
3969 '12:30:45', # Uses full-width unicode colons
3970 '12:30:45․123456', # Uses \u2024 in place of decimal point
3971 '12:30:45a', # Extra at tend of basic time
3972 '12:30:45.123a', # Extra at end of millisecond time
3973 '12:30:45.123456a', # Extra at end of microsecond time
3974 '12:30:45.123456+12:00:30a', # Extra at end of full time
3975 ]
3976
3977 for bad_str in bad_strs:
3978 with self.subTest(bad_str=bad_str):
3979 with self.assertRaises(ValueError):
3980 self.theclass.fromisoformat(bad_str)
3981
3982 def test_fromisoformat_fails_typeerror(self):
3983 # Test the fromisoformat fails when passed the wrong type
3984 import io
3985
3986 bad_types = [b'12:30:45', None, io.StringIO('12:30:45')]
3987
3988 for bad_type in bad_types:
3989 with self.assertRaises(TypeError):
3990 self.theclass.fromisoformat(bad_type)
3991
3992 def test_fromisoformat_subclass(self):
3993 class TimeSubclass(self.theclass):
3994 pass
3995
3996 tsc = TimeSubclass(12, 14, 45, 203745, tzinfo=timezone.utc)
3997 tsc_rt = TimeSubclass.fromisoformat(tsc.isoformat())
3998
3999 self.assertEqual(tsc, tsc_rt)
4000 self.assertIsInstance(tsc_rt, TimeSubclass)
4001
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004002 def test_subclass_timetz(self):
4003
4004 class C(self.theclass):
4005 theAnswer = 42
4006
4007 def __new__(cls, *args, **kws):
4008 temp = kws.copy()
4009 extra = temp.pop('extra')
4010 result = self.theclass.__new__(cls, *args, **temp)
4011 result.extra = extra
4012 return result
4013
4014 def newmeth(self, start):
4015 return start + self.hour + self.second
4016
4017 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
4018
4019 dt1 = self.theclass(*args)
4020 dt2 = C(*args, **{'extra': 7})
4021
4022 self.assertEqual(dt2.__class__, C)
4023 self.assertEqual(dt2.theAnswer, 42)
4024 self.assertEqual(dt2.extra, 7)
4025 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
4026 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
4027
4028
4029# Testing datetime objects with a non-None tzinfo.
4030
4031class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
4032 theclass = datetime
4033
4034 def test_trivial(self):
4035 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
4036 self.assertEqual(dt.year, 1)
4037 self.assertEqual(dt.month, 2)
4038 self.assertEqual(dt.day, 3)
4039 self.assertEqual(dt.hour, 4)
4040 self.assertEqual(dt.minute, 5)
4041 self.assertEqual(dt.second, 6)
4042 self.assertEqual(dt.microsecond, 7)
4043 self.assertEqual(dt.tzinfo, None)
4044
4045 def test_even_more_compare(self):
4046 # The test_compare() and test_more_compare() inherited from TestDate
4047 # and TestDateTime covered non-tzinfo cases.
4048
4049 # Smallest possible after UTC adjustment.
4050 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
4051 # Largest possible after UTC adjustment.
4052 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
4053 tzinfo=FixedOffset(-1439, ""))
4054
4055 # Make sure those compare correctly, and w/o overflow.
4056 self.assertTrue(t1 < t2)
4057 self.assertTrue(t1 != t2)
4058 self.assertTrue(t2 > t1)
4059
4060 self.assertEqual(t1, t1)
4061 self.assertEqual(t2, t2)
4062
4063 # Equal afer adjustment.
4064 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
4065 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
4066 self.assertEqual(t1, t2)
4067
4068 # Change t1 not to subtract a minute, and t1 should be larger.
4069 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
4070 self.assertTrue(t1 > t2)
4071
4072 # Change t1 to subtract 2 minutes, and t1 should be smaller.
4073 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
4074 self.assertTrue(t1 < t2)
4075
4076 # Back to the original t1, but make seconds resolve it.
4077 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
4078 second=1)
4079 self.assertTrue(t1 > t2)
4080
4081 # Likewise, but make microseconds resolve it.
4082 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
4083 microsecond=1)
4084 self.assertTrue(t1 > t2)
4085
Alexander Belopolsky08313822012-06-15 20:19:47 -04004086 # Make t2 naive and it should differ.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004087 t2 = self.theclass.min
Alexander Belopolsky08313822012-06-15 20:19:47 -04004088 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004089 self.assertEqual(t2, t2)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04004090 # and > comparison should fail
4091 with self.assertRaises(TypeError):
4092 t1 > t2
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004093
4094 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
4095 class Naive(tzinfo):
4096 def utcoffset(self, dt): return None
4097 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
Alexander Belopolsky08313822012-06-15 20:19:47 -04004098 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004099 self.assertEqual(t2, t2)
4100
4101 # OTOH, it's OK to compare two of these mixing the two ways of being
4102 # naive.
4103 t1 = self.theclass(5, 6, 7)
4104 self.assertEqual(t1, t2)
4105
4106 # Try a bogus uctoffset.
4107 class Bogus(tzinfo):
4108 def utcoffset(self, dt):
4109 return timedelta(minutes=1440) # out of bounds
4110 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
4111 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
4112 self.assertRaises(ValueError, lambda: t1 == t2)
4113
4114 def test_pickling(self):
4115 # Try one without a tzinfo.
4116 args = 6, 7, 23, 20, 59, 1, 64**2
4117 orig = self.theclass(*args)
4118 for pickler, unpickler, proto in pickle_choices:
4119 green = pickler.dumps(orig, proto)
4120 derived = unpickler.loads(green)
4121 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02004122 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004123
4124 # Try one with a tzinfo.
4125 tinfo = PicklableFixedOffset(-300, 'cookie')
4126 orig = self.theclass(*args, **{'tzinfo': tinfo})
4127 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
4128 for pickler, unpickler, proto in pickle_choices:
4129 green = pickler.dumps(orig, proto)
4130 derived = unpickler.loads(green)
4131 self.assertEqual(orig, derived)
4132 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
4133 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
4134 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02004135 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004136
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02004137 def test_compat_unpickle(self):
4138 tests = [
4139 b'cdatetime\ndatetime\n'
4140 b"(S'\\x07\\xdf\\x0b\\x1b\\x14;\\x01\\x01\\xe2@'\n"
4141 b'ctest.datetimetester\nPicklableFixedOffset\n(tR'
4142 b"(dS'_FixedOffset__offset'\ncdatetime\ntimedelta\n"
4143 b'(I-1\nI68400\nI0\ntRs'
4144 b"S'_FixedOffset__dstoffset'\nNs"
4145 b"S'_FixedOffset__name'\nS'cookie'\nsbtR.",
4146
4147 b'cdatetime\ndatetime\n'
4148 b'(U\n\x07\xdf\x0b\x1b\x14;\x01\x01\xe2@'
4149 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
4150 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
4151 b'(J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00tR'
4152 b'U\x17_FixedOffset__dstoffsetN'
4153 b'U\x12_FixedOffset__nameU\x06cookieubtR.',
4154
4155 b'\x80\x02cdatetime\ndatetime\n'
4156 b'U\n\x07\xdf\x0b\x1b\x14;\x01\x01\xe2@'
4157 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
4158 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
4159 b'J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00\x87R'
4160 b'U\x17_FixedOffset__dstoffsetN'
4161 b'U\x12_FixedOffset__nameU\x06cookieub\x86R.',
4162 ]
4163 args = 2015, 11, 27, 20, 59, 1, 123456
4164 tinfo = PicklableFixedOffset(-300, 'cookie')
4165 expected = self.theclass(*args, **{'tzinfo': tinfo})
4166 for data in tests:
4167 for loads in pickle_loads:
4168 derived = loads(data, encoding='latin1')
4169 self.assertEqual(derived, expected)
4170 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
4171 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
4172 self.assertEqual(derived.tzname(), 'cookie')
4173
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004174 def test_extreme_hashes(self):
4175 # If an attempt is made to hash these via subtracting the offset
4176 # then hashing a datetime object, OverflowError results. The
4177 # Python implementation used to blow up here.
4178 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
4179 hash(t)
4180 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
4181 tzinfo=FixedOffset(-1439, ""))
4182 hash(t)
4183
4184 # OTOH, an OOB offset should blow up.
4185 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
4186 self.assertRaises(ValueError, hash, t)
4187
4188 def test_zones(self):
4189 est = FixedOffset(-300, "EST")
4190 utc = FixedOffset(0, "UTC")
4191 met = FixedOffset(60, "MET")
4192 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
4193 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
4194 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
4195 self.assertEqual(t1.tzinfo, est)
4196 self.assertEqual(t2.tzinfo, utc)
4197 self.assertEqual(t3.tzinfo, met)
4198 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
4199 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
4200 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
4201 self.assertEqual(t1.tzname(), "EST")
4202 self.assertEqual(t2.tzname(), "UTC")
4203 self.assertEqual(t3.tzname(), "MET")
4204 self.assertEqual(hash(t1), hash(t2))
4205 self.assertEqual(hash(t1), hash(t3))
4206 self.assertEqual(hash(t2), hash(t3))
4207 self.assertEqual(t1, t2)
4208 self.assertEqual(t1, t3)
4209 self.assertEqual(t2, t3)
4210 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
4211 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
4212 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
4213 d = 'datetime.datetime(2002, 3, 19, '
4214 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
4215 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
4216 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
4217
4218 def test_combine(self):
4219 met = FixedOffset(60, "MET")
4220 d = date(2002, 3, 4)
4221 tz = time(18, 45, 3, 1234, tzinfo=met)
4222 dt = datetime.combine(d, tz)
4223 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
4224 tzinfo=met))
4225
4226 def test_extract(self):
4227 met = FixedOffset(60, "MET")
4228 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
4229 self.assertEqual(dt.date(), date(2002, 3, 4))
4230 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
4231 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
4232
4233 def test_tz_aware_arithmetic(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004234 now = self.theclass.now()
4235 tz55 = FixedOffset(-330, "west 5:30")
4236 timeaware = now.time().replace(tzinfo=tz55)
4237 nowaware = self.theclass.combine(now.date(), timeaware)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004238 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004239 self.assertEqual(nowaware.timetz(), timeaware)
4240
4241 # Can't mix aware and non-aware.
4242 self.assertRaises(TypeError, lambda: now - nowaware)
4243 self.assertRaises(TypeError, lambda: nowaware - now)
4244
4245 # And adding datetime's doesn't make sense, aware or not.
4246 self.assertRaises(TypeError, lambda: now + nowaware)
4247 self.assertRaises(TypeError, lambda: nowaware + now)
4248 self.assertRaises(TypeError, lambda: nowaware + nowaware)
4249
4250 # Subtracting should yield 0.
4251 self.assertEqual(now - now, timedelta(0))
4252 self.assertEqual(nowaware - nowaware, timedelta(0))
4253
4254 # Adding a delta should preserve tzinfo.
4255 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
4256 nowawareplus = nowaware + delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004257 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004258 nowawareplus2 = delta + nowaware
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004259 self.assertIs(nowawareplus2.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004260 self.assertEqual(nowawareplus, nowawareplus2)
4261
4262 # that - delta should be what we started with, and that - what we
4263 # started with should be delta.
4264 diff = nowawareplus - delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004265 self.assertIs(diff.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004266 self.assertEqual(nowaware, diff)
4267 self.assertRaises(TypeError, lambda: delta - nowawareplus)
4268 self.assertEqual(nowawareplus - nowaware, delta)
4269
4270 # Make up a random timezone.
4271 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
4272 # Attach it to nowawareplus.
4273 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004274 self.assertIs(nowawareplus.tzinfo, tzr)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004275 # Make sure the difference takes the timezone adjustments into account.
4276 got = nowaware - nowawareplus
4277 # Expected: (nowaware base - nowaware offset) -
4278 # (nowawareplus base - nowawareplus offset) =
4279 # (nowaware base - nowawareplus base) +
4280 # (nowawareplus offset - nowaware offset) =
4281 # -delta + nowawareplus offset - nowaware offset
4282 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
4283 self.assertEqual(got, expected)
4284
4285 # Try max possible difference.
4286 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
4287 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
4288 tzinfo=FixedOffset(-1439, "max"))
4289 maxdiff = max - min
4290 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
4291 timedelta(minutes=2*1439))
4292 # Different tzinfo, but the same offset
4293 tza = timezone(HOUR, 'A')
4294 tzb = timezone(HOUR, 'B')
4295 delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
4296 self.assertEqual(delta, self.theclass.min - self.theclass.max)
4297
4298 def test_tzinfo_now(self):
4299 meth = self.theclass.now
4300 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4301 base = meth()
4302 # Try with and without naming the keyword.
4303 off42 = FixedOffset(42, "42")
4304 another = meth(off42)
4305 again = meth(tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004306 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004307 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
4308 # Bad argument with and w/o naming the keyword.
4309 self.assertRaises(TypeError, meth, 16)
4310 self.assertRaises(TypeError, meth, tzinfo=16)
4311 # Bad keyword name.
4312 self.assertRaises(TypeError, meth, tinfo=off42)
4313 # Too many args.
4314 self.assertRaises(TypeError, meth, off42, off42)
4315
4316 # We don't know which time zone we're in, and don't have a tzinfo
4317 # class to represent it, so seeing whether a tz argument actually
4318 # does a conversion is tricky.
4319 utc = FixedOffset(0, "utc", 0)
4320 for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
4321 timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
4322 for dummy in range(3):
4323 now = datetime.now(weirdtz)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004324 self.assertIs(now.tzinfo, weirdtz)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004325 utcnow = datetime.utcnow().replace(tzinfo=utc)
4326 now2 = utcnow.astimezone(weirdtz)
4327 if abs(now - now2) < timedelta(seconds=30):
4328 break
4329 # Else the code is broken, or more than 30 seconds passed between
4330 # calls; assuming the latter, just try again.
4331 else:
4332 # Three strikes and we're out.
4333 self.fail("utcnow(), now(tz), or astimezone() may be broken")
4334
4335 def test_tzinfo_fromtimestamp(self):
4336 import time
4337 meth = self.theclass.fromtimestamp
4338 ts = time.time()
4339 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4340 base = meth(ts)
4341 # Try with and without naming the keyword.
4342 off42 = FixedOffset(42, "42")
4343 another = meth(ts, off42)
4344 again = meth(ts, tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004345 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004346 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
4347 # Bad argument with and w/o naming the keyword.
4348 self.assertRaises(TypeError, meth, ts, 16)
4349 self.assertRaises(TypeError, meth, ts, tzinfo=16)
4350 # Bad keyword name.
4351 self.assertRaises(TypeError, meth, ts, tinfo=off42)
4352 # Too many args.
4353 self.assertRaises(TypeError, meth, ts, off42, off42)
4354 # Too few args.
4355 self.assertRaises(TypeError, meth)
4356
4357 # Try to make sure tz= actually does some conversion.
4358 timestamp = 1000000000
4359 utcdatetime = datetime.utcfromtimestamp(timestamp)
4360 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
4361 # But on some flavor of Mac, it's nowhere near that. So we can't have
4362 # any idea here what time that actually is, we can only test that
4363 # relative changes match.
4364 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
4365 tz = FixedOffset(utcoffset, "tz", 0)
4366 expected = utcdatetime + utcoffset
4367 got = datetime.fromtimestamp(timestamp, tz)
4368 self.assertEqual(expected, got.replace(tzinfo=None))
4369
4370 def test_tzinfo_utcnow(self):
4371 meth = self.theclass.utcnow
4372 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4373 base = meth()
4374 # Try with and without naming the keyword; for whatever reason,
4375 # utcnow() doesn't accept a tzinfo argument.
4376 off42 = FixedOffset(42, "42")
4377 self.assertRaises(TypeError, meth, off42)
4378 self.assertRaises(TypeError, meth, tzinfo=off42)
4379
4380 def test_tzinfo_utcfromtimestamp(self):
4381 import time
4382 meth = self.theclass.utcfromtimestamp
4383 ts = time.time()
4384 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4385 base = meth(ts)
4386 # Try with and without naming the keyword; for whatever reason,
4387 # utcfromtimestamp() doesn't accept a tzinfo argument.
4388 off42 = FixedOffset(42, "42")
4389 self.assertRaises(TypeError, meth, ts, off42)
4390 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
4391
4392 def test_tzinfo_timetuple(self):
4393 # TestDateTime tested most of this. datetime adds a twist to the
4394 # DST flag.
4395 class DST(tzinfo):
4396 def __init__(self, dstvalue):
4397 if isinstance(dstvalue, int):
4398 dstvalue = timedelta(minutes=dstvalue)
4399 self.dstvalue = dstvalue
4400 def dst(self, dt):
4401 return self.dstvalue
4402
4403 cls = self.theclass
4404 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
4405 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
4406 t = d.timetuple()
4407 self.assertEqual(1, t.tm_year)
4408 self.assertEqual(1, t.tm_mon)
4409 self.assertEqual(1, t.tm_mday)
4410 self.assertEqual(10, t.tm_hour)
4411 self.assertEqual(20, t.tm_min)
4412 self.assertEqual(30, t.tm_sec)
4413 self.assertEqual(0, t.tm_wday)
4414 self.assertEqual(1, t.tm_yday)
4415 self.assertEqual(flag, t.tm_isdst)
4416
4417 # dst() returns wrong type.
4418 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
4419
4420 # dst() at the edge.
4421 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
4422 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
4423
4424 # dst() out of range.
4425 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
4426 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
4427
4428 def test_utctimetuple(self):
4429 class DST(tzinfo):
4430 def __init__(self, dstvalue=0):
4431 if isinstance(dstvalue, int):
4432 dstvalue = timedelta(minutes=dstvalue)
4433 self.dstvalue = dstvalue
4434 def dst(self, dt):
4435 return self.dstvalue
4436
4437 cls = self.theclass
4438 # This can't work: DST didn't implement utcoffset.
4439 self.assertRaises(NotImplementedError,
4440 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
4441
4442 class UOFS(DST):
4443 def __init__(self, uofs, dofs=None):
4444 DST.__init__(self, dofs)
4445 self.uofs = timedelta(minutes=uofs)
4446 def utcoffset(self, dt):
4447 return self.uofs
4448
4449 for dstvalue in -33, 33, 0, None:
4450 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
4451 t = d.utctimetuple()
4452 self.assertEqual(d.year, t.tm_year)
4453 self.assertEqual(d.month, t.tm_mon)
4454 self.assertEqual(d.day, t.tm_mday)
4455 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
4456 self.assertEqual(13, t.tm_min)
4457 self.assertEqual(d.second, t.tm_sec)
4458 self.assertEqual(d.weekday(), t.tm_wday)
4459 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
4460 t.tm_yday)
4461 # Ensure tm_isdst is 0 regardless of what dst() says: DST
4462 # is never in effect for a UTC time.
4463 self.assertEqual(0, t.tm_isdst)
4464
4465 # For naive datetime, utctimetuple == timetuple except for isdst
4466 d = cls(1, 2, 3, 10, 20, 30, 40)
4467 t = d.utctimetuple()
4468 self.assertEqual(t[:-1], d.timetuple()[:-1])
4469 self.assertEqual(0, t.tm_isdst)
4470 # Same if utcoffset is None
4471 class NOFS(DST):
4472 def utcoffset(self, dt):
4473 return None
4474 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
4475 t = d.utctimetuple()
4476 self.assertEqual(t[:-1], d.timetuple()[:-1])
4477 self.assertEqual(0, t.tm_isdst)
4478 # Check that bad tzinfo is detected
4479 class BOFS(DST):
4480 def utcoffset(self, dt):
4481 return "EST"
4482 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
4483 self.assertRaises(TypeError, d.utctimetuple)
4484
4485 # Check that utctimetuple() is the same as
4486 # astimezone(utc).timetuple()
4487 d = cls(2010, 11, 13, 14, 15, 16, 171819)
4488 for tz in [timezone.min, timezone.utc, timezone.max]:
4489 dtz = d.replace(tzinfo=tz)
4490 self.assertEqual(dtz.utctimetuple()[:-1],
4491 dtz.astimezone(timezone.utc).timetuple()[:-1])
4492 # At the edges, UTC adjustment can produce years out-of-range
4493 # for a datetime object. Ensure that an OverflowError is
4494 # raised.
4495 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
4496 # That goes back 1 minute less than a full day.
4497 self.assertRaises(OverflowError, tiny.utctimetuple)
4498
4499 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
4500 # That goes forward 1 minute less than a full day.
4501 self.assertRaises(OverflowError, huge.utctimetuple)
4502 # More overflow cases
4503 tiny = cls.min.replace(tzinfo=timezone(MINUTE))
4504 self.assertRaises(OverflowError, tiny.utctimetuple)
4505 huge = cls.max.replace(tzinfo=timezone(-MINUTE))
4506 self.assertRaises(OverflowError, huge.utctimetuple)
4507
4508 def test_tzinfo_isoformat(self):
4509 zero = FixedOffset(0, "+00:00")
4510 plus = FixedOffset(220, "+03:40")
4511 minus = FixedOffset(-231, "-03:51")
4512 unknown = FixedOffset(None, "")
4513
4514 cls = self.theclass
4515 datestr = '0001-02-03'
4516 for ofs in None, zero, plus, minus, unknown:
4517 for us in 0, 987001:
4518 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
4519 timestr = '04:05:59' + (us and '.987001' or '')
4520 ofsstr = ofs is not None and d.tzname() or ''
4521 tailstr = timestr + ofsstr
4522 iso = d.isoformat()
4523 self.assertEqual(iso, datestr + 'T' + tailstr)
4524 self.assertEqual(iso, d.isoformat('T'))
4525 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
4526 self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
4527 self.assertEqual(str(d), datestr + ' ' + tailstr)
4528
4529 def test_replace(self):
4530 cls = self.theclass
4531 z100 = FixedOffset(100, "+100")
4532 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
4533 args = [1, 2, 3, 4, 5, 6, 7, z100]
4534 base = cls(*args)
4535 self.assertEqual(base, base.replace())
4536
4537 i = 0
4538 for name, newval in (("year", 2),
4539 ("month", 3),
4540 ("day", 4),
4541 ("hour", 5),
4542 ("minute", 6),
4543 ("second", 7),
4544 ("microsecond", 8),
4545 ("tzinfo", zm200)):
4546 newargs = args[:]
4547 newargs[i] = newval
4548 expected = cls(*newargs)
4549 got = base.replace(**{name: newval})
4550 self.assertEqual(expected, got)
4551 i += 1
4552
4553 # Ensure we can get rid of a tzinfo.
4554 self.assertEqual(base.tzname(), "+100")
4555 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004556 self.assertIsNone(base2.tzinfo)
4557 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004558
4559 # Ensure we can add one.
4560 base3 = base2.replace(tzinfo=z100)
4561 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004562 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004563
4564 # Out of bounds.
4565 base = cls(2000, 2, 29)
4566 self.assertRaises(ValueError, base.replace, year=2001)
4567
4568 def test_more_astimezone(self):
4569 # The inherited test_astimezone covered some trivial and error cases.
4570 fnone = FixedOffset(None, "None")
4571 f44m = FixedOffset(44, "44")
4572 fm5h = FixedOffset(-timedelta(hours=5), "m300")
4573
4574 dt = self.theclass.now(tz=f44m)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004575 self.assertIs(dt.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004576 # Replacing with degenerate tzinfo raises an exception.
4577 self.assertRaises(ValueError, dt.astimezone, fnone)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004578 # Replacing with same tzinfo makes no change.
4579 x = dt.astimezone(dt.tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004580 self.assertIs(x.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004581 self.assertEqual(x.date(), dt.date())
4582 self.assertEqual(x.time(), dt.time())
4583
4584 # Replacing with different tzinfo does adjust.
4585 got = dt.astimezone(fm5h)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004586 self.assertIs(got.tzinfo, fm5h)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004587 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
4588 expected = dt - dt.utcoffset() # in effect, convert to UTC
4589 expected += fm5h.utcoffset(dt) # and from there to local time
4590 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
4591 self.assertEqual(got.date(), expected.date())
4592 self.assertEqual(got.time(), expected.time())
4593 self.assertEqual(got.timetz(), expected.timetz())
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004594 self.assertIs(got.tzinfo, expected.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004595 self.assertEqual(got, expected)
4596
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004597 @support.run_with_tz('UTC')
4598 def test_astimezone_default_utc(self):
4599 dt = self.theclass.now(timezone.utc)
4600 self.assertEqual(dt.astimezone(None), dt)
4601 self.assertEqual(dt.astimezone(), dt)
4602
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004603 # Note that offset in TZ variable has the opposite sign to that
4604 # produced by %z directive.
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004605 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4606 def test_astimezone_default_eastern(self):
4607 dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
4608 local = dt.astimezone()
4609 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004610 self.assertEqual(local.strftime("%z %Z"), "-0500 EST")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004611 dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc)
4612 local = dt.astimezone()
4613 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004614 self.assertEqual(local.strftime("%z %Z"), "-0400 EDT")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004615
Alexander Belopolsky1dcf4f92016-03-25 15:42:59 -04004616 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4617 def test_astimezone_default_near_fold(self):
4618 # Issue #26616.
4619 u = datetime(2015, 11, 1, 5, tzinfo=timezone.utc)
4620 t = u.astimezone()
4621 s = t.astimezone()
4622 self.assertEqual(t.tzinfo, s.tzinfo)
4623
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004624 def test_aware_subtract(self):
4625 cls = self.theclass
4626
4627 # Ensure that utcoffset() is ignored when the operands have the
4628 # same tzinfo member.
4629 class OperandDependentOffset(tzinfo):
4630 def utcoffset(self, t):
4631 if t.minute < 10:
4632 # d0 and d1 equal after adjustment
4633 return timedelta(minutes=t.minute)
4634 else:
4635 # d2 off in the weeds
4636 return timedelta(minutes=59)
4637
4638 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
4639 d0 = base.replace(minute=3)
4640 d1 = base.replace(minute=9)
4641 d2 = base.replace(minute=11)
4642 for x in d0, d1, d2:
4643 for y in d0, d1, d2:
4644 got = x - y
4645 expected = timedelta(minutes=x.minute - y.minute)
4646 self.assertEqual(got, expected)
4647
4648 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
4649 # ignored.
4650 base = cls(8, 9, 10, 11, 12, 13, 14)
4651 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
4652 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
4653 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
4654 for x in d0, d1, d2:
4655 for y in d0, d1, d2:
4656 got = x - y
4657 if (x is d0 or x is d1) and (y is d0 or y is d1):
4658 expected = timedelta(0)
4659 elif x is y is d2:
4660 expected = timedelta(0)
4661 elif x is d2:
4662 expected = timedelta(minutes=(11-59)-0)
4663 else:
4664 assert y is d2
4665 expected = timedelta(minutes=0-(11-59))
4666 self.assertEqual(got, expected)
4667
4668 def test_mixed_compare(self):
4669 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
4670 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
4671 self.assertEqual(t1, t2)
4672 t2 = t2.replace(tzinfo=None)
4673 self.assertEqual(t1, t2)
4674 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
4675 self.assertEqual(t1, t2)
4676 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04004677 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004678
4679 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
4680 class Varies(tzinfo):
4681 def __init__(self):
4682 self.offset = timedelta(minutes=22)
4683 def utcoffset(self, t):
4684 self.offset += timedelta(minutes=1)
4685 return self.offset
4686
4687 v = Varies()
4688 t1 = t2.replace(tzinfo=v)
4689 t2 = t2.replace(tzinfo=v)
4690 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
4691 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
4692 self.assertEqual(t1, t2)
4693
4694 # But if they're not identical, it isn't ignored.
4695 t2 = t2.replace(tzinfo=Varies())
4696 self.assertTrue(t1 < t2) # t1's offset counter still going up
4697
4698 def test_subclass_datetimetz(self):
4699
4700 class C(self.theclass):
4701 theAnswer = 42
4702
4703 def __new__(cls, *args, **kws):
4704 temp = kws.copy()
4705 extra = temp.pop('extra')
4706 result = self.theclass.__new__(cls, *args, **temp)
4707 result.extra = extra
4708 return result
4709
4710 def newmeth(self, start):
4711 return start + self.hour + self.year
4712
4713 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
4714
4715 dt1 = self.theclass(*args)
4716 dt2 = C(*args, **{'extra': 7})
4717
4718 self.assertEqual(dt2.__class__, C)
4719 self.assertEqual(dt2.theAnswer, 42)
4720 self.assertEqual(dt2.extra, 7)
4721 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
4722 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
4723
4724# Pain to set up DST-aware tzinfo classes.
4725
4726def first_sunday_on_or_after(dt):
4727 days_to_go = 6 - dt.weekday()
4728 if days_to_go:
4729 dt += timedelta(days_to_go)
4730 return dt
4731
4732ZERO = timedelta(0)
4733MINUTE = timedelta(minutes=1)
4734HOUR = timedelta(hours=1)
4735DAY = timedelta(days=1)
4736# In the US, DST starts at 2am (standard time) on the first Sunday in April.
4737DSTSTART = datetime(1, 4, 1, 2)
4738# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
4739# which is the first Sunday on or after Oct 25. Because we view 1:MM as
4740# being standard time on that day, there is no spelling in local time of
4741# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
4742DSTEND = datetime(1, 10, 25, 1)
4743
4744class USTimeZone(tzinfo):
4745
4746 def __init__(self, hours, reprname, stdname, dstname):
4747 self.stdoffset = timedelta(hours=hours)
4748 self.reprname = reprname
4749 self.stdname = stdname
4750 self.dstname = dstname
4751
4752 def __repr__(self):
4753 return self.reprname
4754
4755 def tzname(self, dt):
4756 if self.dst(dt):
4757 return self.dstname
4758 else:
4759 return self.stdname
4760
4761 def utcoffset(self, dt):
4762 return self.stdoffset + self.dst(dt)
4763
4764 def dst(self, dt):
4765 if dt is None or dt.tzinfo is None:
4766 # An exception instead may be sensible here, in one or more of
4767 # the cases.
4768 return ZERO
4769 assert dt.tzinfo is self
4770
4771 # Find first Sunday in April.
4772 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
4773 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
4774
4775 # Find last Sunday in October.
4776 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
4777 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
4778
4779 # Can't compare naive to aware objects, so strip the timezone from
4780 # dt first.
4781 if start <= dt.replace(tzinfo=None) < end:
4782 return HOUR
4783 else:
4784 return ZERO
4785
4786Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
4787Central = USTimeZone(-6, "Central", "CST", "CDT")
4788Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
4789Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
4790utc_real = FixedOffset(0, "UTC", 0)
4791# For better test coverage, we want another flavor of UTC that's west of
4792# the Eastern and Pacific timezones.
4793utc_fake = FixedOffset(-12*60, "UTCfake", 0)
4794
4795class TestTimezoneConversions(unittest.TestCase):
4796 # The DST switch times for 2002, in std time.
4797 dston = datetime(2002, 4, 7, 2)
4798 dstoff = datetime(2002, 10, 27, 1)
4799
4800 theclass = datetime
4801
4802 # Check a time that's inside DST.
4803 def checkinside(self, dt, tz, utc, dston, dstoff):
4804 self.assertEqual(dt.dst(), HOUR)
4805
4806 # Conversion to our own timezone is always an identity.
4807 self.assertEqual(dt.astimezone(tz), dt)
4808
4809 asutc = dt.astimezone(utc)
4810 there_and_back = asutc.astimezone(tz)
4811
4812 # Conversion to UTC and back isn't always an identity here,
4813 # because there are redundant spellings (in local time) of
4814 # UTC time when DST begins: the clock jumps from 1:59:59
4815 # to 3:00:00, and a local time of 2:MM:SS doesn't really
4816 # make sense then. The classes above treat 2:MM:SS as
4817 # daylight time then (it's "after 2am"), really an alias
4818 # for 1:MM:SS standard time. The latter form is what
4819 # conversion back from UTC produces.
4820 if dt.date() == dston.date() and dt.hour == 2:
4821 # We're in the redundant hour, and coming back from
4822 # UTC gives the 1:MM:SS standard-time spelling.
4823 self.assertEqual(there_and_back + HOUR, dt)
4824 # Although during was considered to be in daylight
4825 # time, there_and_back is not.
4826 self.assertEqual(there_and_back.dst(), ZERO)
4827 # They're the same times in UTC.
4828 self.assertEqual(there_and_back.astimezone(utc),
4829 dt.astimezone(utc))
4830 else:
4831 # We're not in the redundant hour.
4832 self.assertEqual(dt, there_and_back)
4833
4834 # Because we have a redundant spelling when DST begins, there is
Ezio Melotti3b3499b2011-03-16 11:35:38 +02004835 # (unfortunately) an hour when DST ends that can't be spelled at all in
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004836 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
4837 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
4838 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
4839 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
4840 # expressed in local time. Nevertheless, we want conversion back
4841 # from UTC to mimic the local clock's "repeat an hour" behavior.
4842 nexthour_utc = asutc + HOUR
4843 nexthour_tz = nexthour_utc.astimezone(tz)
4844 if dt.date() == dstoff.date() and dt.hour == 0:
4845 # We're in the hour before the last DST hour. The last DST hour
4846 # is ineffable. We want the conversion back to repeat 1:MM.
4847 self.assertEqual(nexthour_tz, dt.replace(hour=1))
4848 nexthour_utc += HOUR
4849 nexthour_tz = nexthour_utc.astimezone(tz)
4850 self.assertEqual(nexthour_tz, dt.replace(hour=1))
4851 else:
4852 self.assertEqual(nexthour_tz - dt, HOUR)
4853
4854 # Check a time that's outside DST.
4855 def checkoutside(self, dt, tz, utc):
4856 self.assertEqual(dt.dst(), ZERO)
4857
4858 # Conversion to our own timezone is always an identity.
4859 self.assertEqual(dt.astimezone(tz), dt)
4860
4861 # Converting to UTC and back is an identity too.
4862 asutc = dt.astimezone(utc)
4863 there_and_back = asutc.astimezone(tz)
4864 self.assertEqual(dt, there_and_back)
4865
4866 def convert_between_tz_and_utc(self, tz, utc):
4867 dston = self.dston.replace(tzinfo=tz)
4868 # Because 1:MM on the day DST ends is taken as being standard time,
4869 # there is no spelling in tz for the last hour of daylight time.
4870 # For purposes of the test, the last hour of DST is 0:MM, which is
4871 # taken as being daylight time (and 1:MM is taken as being standard
4872 # time).
4873 dstoff = self.dstoff.replace(tzinfo=tz)
4874 for delta in (timedelta(weeks=13),
4875 DAY,
4876 HOUR,
4877 timedelta(minutes=1),
4878 timedelta(microseconds=1)):
4879
4880 self.checkinside(dston, tz, utc, dston, dstoff)
4881 for during in dston + delta, dstoff - delta:
4882 self.checkinside(during, tz, utc, dston, dstoff)
4883
4884 self.checkoutside(dstoff, tz, utc)
4885 for outside in dston - delta, dstoff + delta:
4886 self.checkoutside(outside, tz, utc)
4887
4888 def test_easy(self):
4889 # Despite the name of this test, the endcases are excruciating.
4890 self.convert_between_tz_and_utc(Eastern, utc_real)
4891 self.convert_between_tz_and_utc(Pacific, utc_real)
4892 self.convert_between_tz_and_utc(Eastern, utc_fake)
4893 self.convert_between_tz_and_utc(Pacific, utc_fake)
4894 # The next is really dancing near the edge. It works because
4895 # Pacific and Eastern are far enough apart that their "problem
4896 # hours" don't overlap.
4897 self.convert_between_tz_and_utc(Eastern, Pacific)
4898 self.convert_between_tz_and_utc(Pacific, Eastern)
4899 # OTOH, these fail! Don't enable them. The difficulty is that
4900 # the edge case tests assume that every hour is representable in
4901 # the "utc" class. This is always true for a fixed-offset tzinfo
4902 # class (lke utc_real and utc_fake), but not for Eastern or Central.
4903 # For these adjacent DST-aware time zones, the range of time offsets
4904 # tested ends up creating hours in the one that aren't representable
4905 # in the other. For the same reason, we would see failures in the
4906 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
4907 # offset deltas in convert_between_tz_and_utc().
4908 #
4909 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
4910 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
4911
4912 def test_tricky(self):
4913 # 22:00 on day before daylight starts.
4914 fourback = self.dston - timedelta(hours=4)
4915 ninewest = FixedOffset(-9*60, "-0900", 0)
4916 fourback = fourback.replace(tzinfo=ninewest)
4917 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
4918 # 2", we should get the 3 spelling.
4919 # If we plug 22:00 the day before into Eastern, it "looks like std
4920 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
4921 # to 22:00 lands on 2:00, which makes no sense in local time (the
4922 # local clock jumps from 1 to 3). The point here is to make sure we
4923 # get the 3 spelling.
4924 expected = self.dston.replace(hour=3)
4925 got = fourback.astimezone(Eastern).replace(tzinfo=None)
4926 self.assertEqual(expected, got)
4927
4928 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
4929 # case we want the 1:00 spelling.
4930 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
4931 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
4932 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
4933 # spelling.
4934 expected = self.dston.replace(hour=1)
4935 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
4936 self.assertEqual(expected, got)
4937
4938 # Now on the day DST ends, we want "repeat an hour" behavior.
4939 # UTC 4:MM 5:MM 6:MM 7:MM checking these
4940 # EST 23:MM 0:MM 1:MM 2:MM
4941 # EDT 0:MM 1:MM 2:MM 3:MM
4942 # wall 0:MM 1:MM 1:MM 2:MM against these
4943 for utc in utc_real, utc_fake:
4944 for tz in Eastern, Pacific:
4945 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
4946 # Convert that to UTC.
4947 first_std_hour -= tz.utcoffset(None)
4948 # Adjust for possibly fake UTC.
4949 asutc = first_std_hour + utc.utcoffset(None)
4950 # First UTC hour to convert; this is 4:00 when utc=utc_real &
4951 # tz=Eastern.
4952 asutcbase = asutc.replace(tzinfo=utc)
4953 for tzhour in (0, 1, 1, 2):
4954 expectedbase = self.dstoff.replace(hour=tzhour)
4955 for minute in 0, 30, 59:
4956 expected = expectedbase.replace(minute=minute)
4957 asutc = asutcbase.replace(minute=minute)
4958 astz = asutc.astimezone(tz)
4959 self.assertEqual(astz.replace(tzinfo=None), expected)
4960 asutcbase += HOUR
4961
4962
4963 def test_bogus_dst(self):
4964 class ok(tzinfo):
4965 def utcoffset(self, dt): return HOUR
4966 def dst(self, dt): return HOUR
4967
4968 now = self.theclass.now().replace(tzinfo=utc_real)
4969 # Doesn't blow up.
4970 now.astimezone(ok())
4971
4972 # Does blow up.
4973 class notok(ok):
4974 def dst(self, dt): return None
4975 self.assertRaises(ValueError, now.astimezone, notok())
4976
4977 # Sometimes blow up. In the following, tzinfo.dst()
4978 # implementation may return None or not None depending on
4979 # whether DST is assumed to be in effect. In this situation,
4980 # a ValueError should be raised by astimezone().
4981 class tricky_notok(ok):
4982 def dst(self, dt):
4983 if dt.year == 2000:
4984 return None
4985 else:
4986 return 10*HOUR
4987 dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
4988 self.assertRaises(ValueError, dt.astimezone, tricky_notok())
4989
4990 def test_fromutc(self):
4991 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
4992 now = datetime.utcnow().replace(tzinfo=utc_real)
4993 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
4994 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
4995 enow = Eastern.fromutc(now) # doesn't blow up
4996 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
4997 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
4998 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
4999
5000 # Always converts UTC to standard time.
5001 class FauxUSTimeZone(USTimeZone):
5002 def fromutc(self, dt):
5003 return dt + self.stdoffset
5004 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
5005
5006 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
5007 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
5008 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
5009
5010 # Check around DST start.
5011 start = self.dston.replace(hour=4, tzinfo=Eastern)
5012 fstart = start.replace(tzinfo=FEastern)
5013 for wall in 23, 0, 1, 3, 4, 5:
5014 expected = start.replace(hour=wall)
5015 if wall == 23:
5016 expected -= timedelta(days=1)
5017 got = Eastern.fromutc(start)
5018 self.assertEqual(expected, got)
5019
5020 expected = fstart + FEastern.stdoffset
5021 got = FEastern.fromutc(fstart)
5022 self.assertEqual(expected, got)
5023
5024 # Ensure astimezone() calls fromutc() too.
5025 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
5026 self.assertEqual(expected, got)
5027
5028 start += HOUR
5029 fstart += HOUR
5030
5031 # Check around DST end.
5032 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
5033 fstart = start.replace(tzinfo=FEastern)
5034 for wall in 0, 1, 1, 2, 3, 4:
5035 expected = start.replace(hour=wall)
5036 got = Eastern.fromutc(start)
5037 self.assertEqual(expected, got)
5038
5039 expected = fstart + FEastern.stdoffset
5040 got = FEastern.fromutc(fstart)
5041 self.assertEqual(expected, got)
5042
5043 # Ensure astimezone() calls fromutc() too.
5044 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
5045 self.assertEqual(expected, got)
5046
5047 start += HOUR
5048 fstart += HOUR
5049
5050
5051#############################################################################
5052# oddballs
5053
5054class Oddballs(unittest.TestCase):
5055
5056 def test_bug_1028306(self):
5057 # Trying to compare a date to a datetime should act like a mixed-
5058 # type comparison, despite that datetime is a subclass of date.
5059 as_date = date.today()
5060 as_datetime = datetime.combine(as_date, time())
5061 self.assertTrue(as_date != as_datetime)
5062 self.assertTrue(as_datetime != as_date)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02005063 self.assertFalse(as_date == as_datetime)
5064 self.assertFalse(as_datetime == as_date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00005065 self.assertRaises(TypeError, lambda: as_date < as_datetime)
5066 self.assertRaises(TypeError, lambda: as_datetime < as_date)
5067 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
5068 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
5069 self.assertRaises(TypeError, lambda: as_date > as_datetime)
5070 self.assertRaises(TypeError, lambda: as_datetime > as_date)
5071 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
5072 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
5073
Raymond Hettinger15f44ab2016-08-30 10:47:49 -07005074 # Nevertheless, comparison should work with the base-class (date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00005075 # projection if use of a date method is forced.
5076 self.assertEqual(as_date.__eq__(as_datetime), True)
5077 different_day = (as_date.day + 1) % 20 + 1
5078 as_different = as_datetime.replace(day= different_day)
5079 self.assertEqual(as_date.__eq__(as_different), False)
5080
5081 # And date should compare with other subclasses of date. If a
5082 # subclass wants to stop this, it's up to the subclass to do so.
5083 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
5084 self.assertEqual(as_date, date_sc)
5085 self.assertEqual(date_sc, as_date)
5086
5087 # Ditto for datetimes.
5088 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
5089 as_date.day, 0, 0, 0)
5090 self.assertEqual(as_datetime, datetime_sc)
5091 self.assertEqual(datetime_sc, as_datetime)
5092
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04005093 def test_extra_attributes(self):
5094 for x in [date.today(),
5095 time(),
5096 datetime.utcnow(),
5097 timedelta(),
5098 tzinfo(),
5099 timezone(timedelta())]:
5100 with self.assertRaises(AttributeError):
5101 x.abc = 1
5102
5103 def test_check_arg_types(self):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04005104 class Number:
5105 def __init__(self, value):
5106 self.value = value
5107 def __int__(self):
5108 return self.value
5109
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04005110 class Float(float):
5111 pass
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04005112
Serhiy Storchaka578c3952020-05-26 18:43:38 +03005113 for xx in [10.0, Float(10.9),
5114 decimal.Decimal(10), decimal.Decimal('10.9'),
5115 Number(10), Number(10.9),
5116 '10']:
5117 self.assertRaises(TypeError, datetime, xx, 10, 10, 10, 10, 10, 10)
5118 self.assertRaises(TypeError, datetime, 10, xx, 10, 10, 10, 10, 10)
5119 self.assertRaises(TypeError, datetime, 10, 10, xx, 10, 10, 10, 10)
5120 self.assertRaises(TypeError, datetime, 10, 10, 10, xx, 10, 10, 10)
5121 self.assertRaises(TypeError, datetime, 10, 10, 10, 10, xx, 10, 10)
5122 self.assertRaises(TypeError, datetime, 10, 10, 10, 10, 10, xx, 10)
5123 self.assertRaises(TypeError, datetime, 10, 10, 10, 10, 10, 10, xx)
5124
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04005125
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005126#############################################################################
5127# Local Time Disambiguation
5128
5129# An experimental reimplementation of fromutc that respects the "fold" flag.
5130
5131class tzinfo2(tzinfo):
5132
5133 def fromutc(self, dt):
5134 "datetime in UTC -> datetime in local time."
5135
5136 if not isinstance(dt, datetime):
5137 raise TypeError("fromutc() requires a datetime argument")
5138 if dt.tzinfo is not self:
5139 raise ValueError("dt.tzinfo is not self")
5140 # Returned value satisfies
5141 # dt + ldt.utcoffset() = ldt
5142 off0 = dt.replace(fold=0).utcoffset()
5143 off1 = dt.replace(fold=1).utcoffset()
5144 if off0 is None or off1 is None or dt.dst() is None:
5145 raise ValueError
5146 if off0 == off1:
5147 ldt = dt + off0
5148 off1 = ldt.utcoffset()
5149 if off0 == off1:
5150 return ldt
5151 # Now, we discovered both possible offsets, so
5152 # we can just try four possible solutions:
5153 for off in [off0, off1]:
5154 ldt = dt + off
5155 if ldt.utcoffset() == off:
5156 return ldt
5157 ldt = ldt.replace(fold=1)
5158 if ldt.utcoffset() == off:
5159 return ldt
5160
5161 raise ValueError("No suitable local time found")
5162
5163# Reimplementing simplified US timezones to respect the "fold" flag:
5164
5165class USTimeZone2(tzinfo2):
5166
5167 def __init__(self, hours, reprname, stdname, dstname):
5168 self.stdoffset = timedelta(hours=hours)
5169 self.reprname = reprname
5170 self.stdname = stdname
5171 self.dstname = dstname
5172
5173 def __repr__(self):
5174 return self.reprname
5175
5176 def tzname(self, dt):
5177 if self.dst(dt):
5178 return self.dstname
5179 else:
5180 return self.stdname
5181
5182 def utcoffset(self, dt):
5183 return self.stdoffset + self.dst(dt)
5184
5185 def dst(self, dt):
5186 if dt is None or dt.tzinfo is None:
5187 # An exception instead may be sensible here, in one or more of
5188 # the cases.
5189 return ZERO
5190 assert dt.tzinfo is self
5191
5192 # Find first Sunday in April.
5193 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
5194 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
5195
5196 # Find last Sunday in October.
5197 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
5198 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
5199
5200 # Can't compare naive to aware objects, so strip the timezone from
5201 # dt first.
5202 dt = dt.replace(tzinfo=None)
5203 if start + HOUR <= dt < end:
5204 # DST is in effect.
5205 return HOUR
5206 elif end <= dt < end + HOUR:
5207 # Fold (an ambiguous hour): use dt.fold to disambiguate.
5208 return ZERO if dt.fold else HOUR
5209 elif start <= dt < start + HOUR:
5210 # Gap (a non-existent hour): reverse the fold rule.
5211 return HOUR if dt.fold else ZERO
5212 else:
5213 # DST is off.
5214 return ZERO
5215
5216Eastern2 = USTimeZone2(-5, "Eastern2", "EST", "EDT")
5217Central2 = USTimeZone2(-6, "Central2", "CST", "CDT")
5218Mountain2 = USTimeZone2(-7, "Mountain2", "MST", "MDT")
5219Pacific2 = USTimeZone2(-8, "Pacific2", "PST", "PDT")
5220
5221# Europe_Vilnius_1941 tzinfo implementation reproduces the following
5222# 1941 transition from Olson's tzdist:
5223#
5224# Zone NAME GMTOFF RULES FORMAT [UNTIL]
5225# ZoneEurope/Vilnius 1:00 - CET 1940 Aug 3
5226# 3:00 - MSK 1941 Jun 24
5227# 1:00 C-Eur CE%sT 1944 Aug
5228#
5229# $ zdump -v Europe/Vilnius | grep 1941
5230# Europe/Vilnius Mon Jun 23 20:59:59 1941 UTC = Mon Jun 23 23:59:59 1941 MSK isdst=0 gmtoff=10800
5231# Europe/Vilnius Mon Jun 23 21:00:00 1941 UTC = Mon Jun 23 23:00:00 1941 CEST isdst=1 gmtoff=7200
5232
5233class Europe_Vilnius_1941(tzinfo):
5234 def _utc_fold(self):
5235 return [datetime(1941, 6, 23, 21, tzinfo=self), # Mon Jun 23 21:00:00 1941 UTC
5236 datetime(1941, 6, 23, 22, tzinfo=self)] # Mon Jun 23 22:00:00 1941 UTC
5237
5238 def _loc_fold(self):
5239 return [datetime(1941, 6, 23, 23, tzinfo=self), # Mon Jun 23 23:00:00 1941 MSK / CEST
5240 datetime(1941, 6, 24, 0, tzinfo=self)] # Mon Jun 24 00:00:00 1941 CEST
5241
5242 def utcoffset(self, dt):
5243 fold_start, fold_stop = self._loc_fold()
5244 if dt < fold_start:
5245 return 3 * HOUR
5246 if dt < fold_stop:
5247 return (2 if dt.fold else 3) * HOUR
5248 # if dt >= fold_stop
5249 return 2 * HOUR
5250
5251 def dst(self, dt):
5252 fold_start, fold_stop = self._loc_fold()
5253 if dt < fold_start:
5254 return 0 * HOUR
5255 if dt < fold_stop:
5256 return (1 if dt.fold else 0) * HOUR
5257 # if dt >= fold_stop
5258 return 1 * HOUR
5259
5260 def tzname(self, dt):
5261 fold_start, fold_stop = self._loc_fold()
5262 if dt < fold_start:
5263 return 'MSK'
5264 if dt < fold_stop:
5265 return ('MSK', 'CEST')[dt.fold]
5266 # if dt >= fold_stop
5267 return 'CEST'
5268
5269 def fromutc(self, dt):
5270 assert dt.fold == 0
5271 assert dt.tzinfo is self
5272 if dt.year != 1941:
5273 raise NotImplementedError
5274 fold_start, fold_stop = self._utc_fold()
5275 if dt < fold_start:
5276 return dt + 3 * HOUR
5277 if dt < fold_stop:
5278 return (dt + 2 * HOUR).replace(fold=1)
5279 # if dt >= fold_stop
5280 return dt + 2 * HOUR
5281
5282
5283class TestLocalTimeDisambiguation(unittest.TestCase):
5284
5285 def test_vilnius_1941_fromutc(self):
5286 Vilnius = Europe_Vilnius_1941()
5287
5288 gdt = datetime(1941, 6, 23, 20, 59, 59, tzinfo=timezone.utc)
5289 ldt = gdt.astimezone(Vilnius)
5290 self.assertEqual(ldt.strftime("%c %Z%z"),
5291 'Mon Jun 23 23:59:59 1941 MSK+0300')
5292 self.assertEqual(ldt.fold, 0)
5293 self.assertFalse(ldt.dst())
5294
5295 gdt = datetime(1941, 6, 23, 21, tzinfo=timezone.utc)
5296 ldt = gdt.astimezone(Vilnius)
5297 self.assertEqual(ldt.strftime("%c %Z%z"),
5298 'Mon Jun 23 23:00:00 1941 CEST+0200')
5299 self.assertEqual(ldt.fold, 1)
5300 self.assertTrue(ldt.dst())
5301
5302 gdt = datetime(1941, 6, 23, 22, tzinfo=timezone.utc)
5303 ldt = gdt.astimezone(Vilnius)
5304 self.assertEqual(ldt.strftime("%c %Z%z"),
5305 'Tue Jun 24 00:00:00 1941 CEST+0200')
5306 self.assertEqual(ldt.fold, 0)
5307 self.assertTrue(ldt.dst())
5308
5309 def test_vilnius_1941_toutc(self):
5310 Vilnius = Europe_Vilnius_1941()
5311
5312 ldt = datetime(1941, 6, 23, 22, 59, 59, tzinfo=Vilnius)
5313 gdt = ldt.astimezone(timezone.utc)
5314 self.assertEqual(gdt.strftime("%c %Z"),
5315 'Mon Jun 23 19:59:59 1941 UTC')
5316
5317 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius)
5318 gdt = ldt.astimezone(timezone.utc)
5319 self.assertEqual(gdt.strftime("%c %Z"),
5320 'Mon Jun 23 20:59:59 1941 UTC')
5321
5322 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius, fold=1)
5323 gdt = ldt.astimezone(timezone.utc)
5324 self.assertEqual(gdt.strftime("%c %Z"),
5325 'Mon Jun 23 21:59:59 1941 UTC')
5326
5327 ldt = datetime(1941, 6, 24, 0, tzinfo=Vilnius)
5328 gdt = ldt.astimezone(timezone.utc)
5329 self.assertEqual(gdt.strftime("%c %Z"),
5330 'Mon Jun 23 22:00:00 1941 UTC')
5331
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005332 def test_constructors(self):
5333 t = time(0, fold=1)
5334 dt = datetime(1, 1, 1, fold=1)
5335 self.assertEqual(t.fold, 1)
5336 self.assertEqual(dt.fold, 1)
5337 with self.assertRaises(TypeError):
5338 time(0, 0, 0, 0, None, 0)
5339
5340 def test_member(self):
5341 dt = datetime(1, 1, 1, fold=1)
5342 t = dt.time()
5343 self.assertEqual(t.fold, 1)
5344 t = dt.timetz()
5345 self.assertEqual(t.fold, 1)
5346
5347 def test_replace(self):
5348 t = time(0)
5349 dt = datetime(1, 1, 1)
5350 self.assertEqual(t.replace(fold=1).fold, 1)
5351 self.assertEqual(dt.replace(fold=1).fold, 1)
5352 self.assertEqual(t.replace(fold=0).fold, 0)
5353 self.assertEqual(dt.replace(fold=0).fold, 0)
5354 # Check that replacement of other fields does not change "fold".
5355 t = t.replace(fold=1, tzinfo=Eastern)
5356 dt = dt.replace(fold=1, tzinfo=Eastern)
5357 self.assertEqual(t.replace(tzinfo=None).fold, 1)
5358 self.assertEqual(dt.replace(tzinfo=None).fold, 1)
Serhiy Storchaka314d6fc2017-03-31 22:48:16 +03005359 # Out of bounds.
5360 with self.assertRaises(ValueError):
5361 t.replace(fold=2)
5362 with self.assertRaises(ValueError):
5363 dt.replace(fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005364 # Check that fold is a keyword-only argument
5365 with self.assertRaises(TypeError):
5366 t.replace(1, 1, 1, None, 1)
5367 with self.assertRaises(TypeError):
5368 dt.replace(1, 1, 1, 1, 1, 1, 1, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04005369
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005370 def test_comparison(self):
5371 t = time(0)
5372 dt = datetime(1, 1, 1)
5373 self.assertEqual(t, t.replace(fold=1))
5374 self.assertEqual(dt, dt.replace(fold=1))
5375
5376 def test_hash(self):
5377 t = time(0)
5378 dt = datetime(1, 1, 1)
5379 self.assertEqual(hash(t), hash(t.replace(fold=1)))
5380 self.assertEqual(hash(dt), hash(dt.replace(fold=1)))
5381
5382 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5383 def test_fromtimestamp(self):
5384 s = 1414906200
5385 dt0 = datetime.fromtimestamp(s)
5386 dt1 = datetime.fromtimestamp(s + 3600)
5387 self.assertEqual(dt0.fold, 0)
5388 self.assertEqual(dt1.fold, 1)
5389
5390 @support.run_with_tz('Australia/Lord_Howe')
5391 def test_fromtimestamp_lord_howe(self):
5392 tm = _time.localtime(1.4e9)
5393 if _time.strftime('%Z%z', tm) != 'LHST+1030':
5394 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
5395 # $ TZ=Australia/Lord_Howe date -r 1428158700
5396 # Sun Apr 5 01:45:00 LHDT 2015
5397 # $ TZ=Australia/Lord_Howe date -r 1428160500
5398 # Sun Apr 5 01:45:00 LHST 2015
5399 s = 1428158700
5400 t0 = datetime.fromtimestamp(s)
5401 t1 = datetime.fromtimestamp(s + 1800)
5402 self.assertEqual(t0, t1)
5403 self.assertEqual(t0.fold, 0)
5404 self.assertEqual(t1.fold, 1)
5405
Ammar Askar96d1e692018-07-25 09:54:58 -07005406 def test_fromtimestamp_low_fold_detection(self):
5407 # Ensure that fold detection doesn't cause an
5408 # OSError for really low values, see bpo-29097
5409 self.assertEqual(datetime.fromtimestamp(0).fold, 0)
5410
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005411 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5412 def test_timestamp(self):
5413 dt0 = datetime(2014, 11, 2, 1, 30)
5414 dt1 = dt0.replace(fold=1)
5415 self.assertEqual(dt0.timestamp() + 3600,
5416 dt1.timestamp())
5417
5418 @support.run_with_tz('Australia/Lord_Howe')
5419 def test_timestamp_lord_howe(self):
5420 tm = _time.localtime(1.4e9)
5421 if _time.strftime('%Z%z', tm) != 'LHST+1030':
5422 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
5423 t = datetime(2015, 4, 5, 1, 45)
5424 s0 = t.replace(fold=0).timestamp()
5425 s1 = t.replace(fold=1).timestamp()
5426 self.assertEqual(s0 + 1800, s1)
5427
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005428 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5429 def test_astimezone(self):
5430 dt0 = datetime(2014, 11, 2, 1, 30)
5431 dt1 = dt0.replace(fold=1)
5432 # Convert both naive instances to aware.
5433 adt0 = dt0.astimezone()
5434 adt1 = dt1.astimezone()
5435 # Check that the first instance in DST zone and the second in STD
5436 self.assertEqual(adt0.tzname(), 'EDT')
5437 self.assertEqual(adt1.tzname(), 'EST')
5438 self.assertEqual(adt0 + HOUR, adt1)
5439 # Aware instances with fixed offset tzinfo's always have fold=0
5440 self.assertEqual(adt0.fold, 0)
5441 self.assertEqual(adt1.fold, 0)
5442
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005443 def test_pickle_fold(self):
5444 t = time(fold=1)
5445 dt = datetime(1, 1, 1, fold=1)
5446 for pickler, unpickler, proto in pickle_choices:
5447 for x in [t, dt]:
5448 s = pickler.dumps(x, proto)
5449 y = unpickler.loads(s)
5450 self.assertEqual(x, y)
5451 self.assertEqual((0 if proto < 4 else x.fold), y.fold)
5452
5453 def test_repr(self):
5454 t = time(fold=1)
5455 dt = datetime(1, 1, 1, fold=1)
5456 self.assertEqual(repr(t), 'datetime.time(0, 0, fold=1)')
5457 self.assertEqual(repr(dt),
5458 'datetime.datetime(1, 1, 1, 0, 0, fold=1)')
5459
5460 def test_dst(self):
5461 # Let's first establish that things work in regular times.
5462 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
5463 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
5464 self.assertEqual(dt_summer.dst(), HOUR)
5465 self.assertEqual(dt_winter.dst(), ZERO)
5466 # The disambiguation flag is ignored
5467 self.assertEqual(dt_summer.replace(fold=1).dst(), HOUR)
5468 self.assertEqual(dt_winter.replace(fold=1).dst(), ZERO)
5469
5470 # Pick local time in the fold.
5471 for minute in [0, 30, 59]:
5472 dt = datetime(2002, 10, 27, 1, minute, tzinfo=Eastern2)
5473 # With fold=0 (the default) it is in DST.
5474 self.assertEqual(dt.dst(), HOUR)
5475 # With fold=1 it is in STD.
5476 self.assertEqual(dt.replace(fold=1).dst(), ZERO)
5477
5478 # Pick local time in the gap.
5479 for minute in [0, 30, 59]:
5480 dt = datetime(2002, 4, 7, 2, minute, tzinfo=Eastern2)
5481 # With fold=0 (the default) it is in STD.
5482 self.assertEqual(dt.dst(), ZERO)
5483 # With fold=1 it is in DST.
5484 self.assertEqual(dt.replace(fold=1).dst(), HOUR)
5485
5486
5487 def test_utcoffset(self):
5488 # Let's first establish that things work in regular times.
5489 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
5490 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
5491 self.assertEqual(dt_summer.utcoffset(), -4 * HOUR)
5492 self.assertEqual(dt_winter.utcoffset(), -5 * HOUR)
5493 # The disambiguation flag is ignored
5494 self.assertEqual(dt_summer.replace(fold=1).utcoffset(), -4 * HOUR)
5495 self.assertEqual(dt_winter.replace(fold=1).utcoffset(), -5 * HOUR)
5496
5497 def test_fromutc(self):
5498 # Let's first establish that things work in regular times.
5499 u_summer = datetime(2002, 10, 27, 6, tzinfo=Eastern2) - timedelta.resolution
5500 u_winter = datetime(2002, 10, 27, 7, tzinfo=Eastern2)
5501 t_summer = Eastern2.fromutc(u_summer)
5502 t_winter = Eastern2.fromutc(u_winter)
5503 self.assertEqual(t_summer, u_summer - 4 * HOUR)
5504 self.assertEqual(t_winter, u_winter - 5 * HOUR)
5505 self.assertEqual(t_summer.fold, 0)
5506 self.assertEqual(t_winter.fold, 0)
5507
5508 # What happens in the fall-back fold?
5509 u = datetime(2002, 10, 27, 5, 30, tzinfo=Eastern2)
5510 t0 = Eastern2.fromutc(u)
5511 u += HOUR
5512 t1 = Eastern2.fromutc(u)
5513 self.assertEqual(t0, t1)
5514 self.assertEqual(t0.fold, 0)
5515 self.assertEqual(t1.fold, 1)
5516 # The tricky part is when u is in the local fold:
5517 u = datetime(2002, 10, 27, 1, 30, tzinfo=Eastern2)
5518 t = Eastern2.fromutc(u)
5519 self.assertEqual((t.day, t.hour), (26, 21))
5520 # .. or gets into the local fold after a standard time adjustment
5521 u = datetime(2002, 10, 27, 6, 30, tzinfo=Eastern2)
5522 t = Eastern2.fromutc(u)
5523 self.assertEqual((t.day, t.hour), (27, 1))
5524
5525 # What happens in the spring-forward gap?
5526 u = datetime(2002, 4, 7, 2, 0, tzinfo=Eastern2)
5527 t = Eastern2.fromutc(u)
5528 self.assertEqual((t.day, t.hour), (6, 21))
5529
5530 def test_mixed_compare_regular(self):
5531 t = datetime(2000, 1, 1, tzinfo=Eastern2)
5532 self.assertEqual(t, t.astimezone(timezone.utc))
5533 t = datetime(2000, 6, 1, tzinfo=Eastern2)
5534 self.assertEqual(t, t.astimezone(timezone.utc))
5535
5536 def test_mixed_compare_fold(self):
5537 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
5538 t_fold_utc = t_fold.astimezone(timezone.utc)
5539 self.assertNotEqual(t_fold, t_fold_utc)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04005540 self.assertNotEqual(t_fold_utc, t_fold)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005541
5542 def test_mixed_compare_gap(self):
5543 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
5544 t_gap_utc = t_gap.astimezone(timezone.utc)
5545 self.assertNotEqual(t_gap, t_gap_utc)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04005546 self.assertNotEqual(t_gap_utc, t_gap)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005547
5548 def test_hash_aware(self):
5549 t = datetime(2000, 1, 1, tzinfo=Eastern2)
5550 self.assertEqual(hash(t), hash(t.replace(fold=1)))
5551 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
5552 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
5553 self.assertEqual(hash(t_fold), hash(t_fold.replace(fold=1)))
5554 self.assertEqual(hash(t_gap), hash(t_gap.replace(fold=1)))
5555
5556SEC = timedelta(0, 1)
5557
5558def pairs(iterable):
5559 a, b = itertools.tee(iterable)
5560 next(b, None)
5561 return zip(a, b)
5562
5563class ZoneInfo(tzinfo):
5564 zoneroot = '/usr/share/zoneinfo'
5565 def __init__(self, ut, ti):
5566 """
5567
5568 :param ut: array
5569 Array of transition point timestamps
5570 :param ti: list
5571 A list of (offset, isdst, abbr) tuples
5572 :return: None
5573 """
5574 self.ut = ut
5575 self.ti = ti
5576 self.lt = self.invert(ut, ti)
5577
5578 @staticmethod
5579 def invert(ut, ti):
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04005580 lt = (array('q', ut), array('q', ut))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005581 if ut:
5582 offset = ti[0][0] // SEC
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04005583 lt[0][0] += offset
5584 lt[1][0] += offset
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005585 for i in range(1, len(ut)):
5586 lt[0][i] += ti[i-1][0] // SEC
5587 lt[1][i] += ti[i][0] // SEC
5588 return lt
5589
5590 @classmethod
5591 def fromfile(cls, fileobj):
5592 if fileobj.read(4).decode() != "TZif":
5593 raise ValueError("not a zoneinfo file")
5594 fileobj.seek(32)
5595 counts = array('i')
5596 counts.fromfile(fileobj, 3)
5597 if sys.byteorder != 'big':
5598 counts.byteswap()
5599
5600 ut = array('i')
5601 ut.fromfile(fileobj, counts[0])
5602 if sys.byteorder != 'big':
5603 ut.byteswap()
5604
5605 type_indices = array('B')
5606 type_indices.fromfile(fileobj, counts[0])
5607
5608 ttis = []
5609 for i in range(counts[1]):
5610 ttis.append(struct.unpack(">lbb", fileobj.read(6)))
5611
5612 abbrs = fileobj.read(counts[2])
5613
5614 # Convert ttis
5615 for i, (gmtoff, isdst, abbrind) in enumerate(ttis):
5616 abbr = abbrs[abbrind:abbrs.find(0, abbrind)].decode()
5617 ttis[i] = (timedelta(0, gmtoff), isdst, abbr)
5618
5619 ti = [None] * len(ut)
5620 for i, idx in enumerate(type_indices):
5621 ti[i] = ttis[idx]
5622
5623 self = cls(ut, ti)
5624
5625 return self
5626
5627 @classmethod
5628 def fromname(cls, name):
5629 path = os.path.join(cls.zoneroot, name)
5630 with open(path, 'rb') as f:
5631 return cls.fromfile(f)
5632
5633 EPOCHORDINAL = date(1970, 1, 1).toordinal()
5634
5635 def fromutc(self, dt):
5636 """datetime in UTC -> datetime in local time."""
5637
5638 if not isinstance(dt, datetime):
5639 raise TypeError("fromutc() requires a datetime argument")
5640 if dt.tzinfo is not self:
5641 raise ValueError("dt.tzinfo is not self")
5642
5643 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
5644 + dt.hour * 3600
5645 + dt.minute * 60
5646 + dt.second)
5647
5648 if timestamp < self.ut[1]:
5649 tti = self.ti[0]
5650 fold = 0
5651 else:
5652 idx = bisect.bisect_right(self.ut, timestamp)
5653 assert self.ut[idx-1] <= timestamp
5654 assert idx == len(self.ut) or timestamp < self.ut[idx]
5655 tti_prev, tti = self.ti[idx-2:idx]
5656 # Detect fold
5657 shift = tti_prev[0] - tti[0]
5658 fold = (shift > timedelta(0, timestamp - self.ut[idx-1]))
5659 dt += tti[0]
5660 if fold:
5661 return dt.replace(fold=1)
5662 else:
5663 return dt
5664
5665 def _find_ti(self, dt, i):
5666 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
5667 + dt.hour * 3600
5668 + dt.minute * 60
5669 + dt.second)
5670 lt = self.lt[dt.fold]
5671 idx = bisect.bisect_right(lt, timestamp)
5672
5673 return self.ti[max(0, idx - 1)][i]
5674
5675 def utcoffset(self, dt):
5676 return self._find_ti(dt, 0)
5677
5678 def dst(self, dt):
5679 isdst = self._find_ti(dt, 1)
5680 # XXX: We cannot accurately determine the "save" value,
5681 # so let's return 1h whenever DST is in effect. Since
5682 # we don't use dst() in fromutc(), it is unlikely that
5683 # it will be needed for anything more than bool(dst()).
5684 return ZERO if isdst else HOUR
5685
5686 def tzname(self, dt):
5687 return self._find_ti(dt, 2)
5688
5689 @classmethod
5690 def zonenames(cls, zonedir=None):
5691 if zonedir is None:
5692 zonedir = cls.zoneroot
Alexander Belopolsky1b8f26c2016-08-11 11:01:45 -04005693 zone_tab = os.path.join(zonedir, 'zone.tab')
5694 try:
5695 f = open(zone_tab)
5696 except OSError:
5697 return
5698 with f:
5699 for line in f:
5700 line = line.strip()
5701 if line and not line.startswith('#'):
5702 yield line.split()[2]
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005703
5704 @classmethod
5705 def stats(cls, start_year=1):
5706 count = gap_count = fold_count = zeros_count = 0
5707 min_gap = min_fold = timedelta.max
5708 max_gap = max_fold = ZERO
5709 min_gap_datetime = max_gap_datetime = datetime.min
5710 min_gap_zone = max_gap_zone = None
5711 min_fold_datetime = max_fold_datetime = datetime.min
5712 min_fold_zone = max_fold_zone = None
5713 stats_since = datetime(start_year, 1, 1) # Starting from 1970 eliminates a lot of noise
5714 for zonename in cls.zonenames():
5715 count += 1
5716 tz = cls.fromname(zonename)
5717 for dt, shift in tz.transitions():
5718 if dt < stats_since:
5719 continue
5720 if shift > ZERO:
5721 gap_count += 1
5722 if (shift, dt) > (max_gap, max_gap_datetime):
5723 max_gap = shift
5724 max_gap_zone = zonename
5725 max_gap_datetime = dt
5726 if (shift, datetime.max - dt) < (min_gap, datetime.max - min_gap_datetime):
5727 min_gap = shift
5728 min_gap_zone = zonename
5729 min_gap_datetime = dt
5730 elif shift < ZERO:
5731 fold_count += 1
5732 shift = -shift
5733 if (shift, dt) > (max_fold, max_fold_datetime):
5734 max_fold = shift
5735 max_fold_zone = zonename
5736 max_fold_datetime = dt
5737 if (shift, datetime.max - dt) < (min_fold, datetime.max - min_fold_datetime):
5738 min_fold = shift
5739 min_fold_zone = zonename
5740 min_fold_datetime = dt
5741 else:
5742 zeros_count += 1
5743 trans_counts = (gap_count, fold_count, zeros_count)
5744 print("Number of zones: %5d" % count)
5745 print("Number of transitions: %5d = %d (gaps) + %d (folds) + %d (zeros)" %
5746 ((sum(trans_counts),) + trans_counts))
5747 print("Min gap: %16s at %s in %s" % (min_gap, min_gap_datetime, min_gap_zone))
5748 print("Max gap: %16s at %s in %s" % (max_gap, max_gap_datetime, max_gap_zone))
5749 print("Min fold: %16s at %s in %s" % (min_fold, min_fold_datetime, min_fold_zone))
5750 print("Max fold: %16s at %s in %s" % (max_fold, max_fold_datetime, max_fold_zone))
5751
5752
5753 def transitions(self):
5754 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
5755 shift = ti[0] - prev_ti[0]
5756 yield datetime.utcfromtimestamp(t), shift
5757
5758 def nondst_folds(self):
5759 """Find all folds with the same value of isdst on both sides of the transition."""
5760 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
5761 shift = ti[0] - prev_ti[0]
5762 if shift < ZERO and ti[1] == prev_ti[1]:
5763 yield datetime.utcfromtimestamp(t), -shift, prev_ti[2], ti[2]
5764
5765 @classmethod
5766 def print_all_nondst_folds(cls, same_abbr=False, start_year=1):
5767 count = 0
5768 for zonename in cls.zonenames():
5769 tz = cls.fromname(zonename)
5770 for dt, shift, prev_abbr, abbr in tz.nondst_folds():
5771 if dt.year < start_year or same_abbr and prev_abbr != abbr:
5772 continue
5773 count += 1
5774 print("%3d) %-30s %s %10s %5s -> %s" %
5775 (count, zonename, dt, shift, prev_abbr, abbr))
5776
5777 def folds(self):
5778 for t, shift in self.transitions():
5779 if shift < ZERO:
5780 yield t, -shift
5781
5782 def gaps(self):
5783 for t, shift in self.transitions():
5784 if shift > ZERO:
5785 yield t, shift
5786
5787 def zeros(self):
5788 for t, shift in self.transitions():
5789 if not shift:
5790 yield t
5791
5792
5793class ZoneInfoTest(unittest.TestCase):
5794 zonename = 'America/New_York'
5795
5796 def setUp(self):
hliu08e7ff6a2019-09-10 18:28:11 +08005797 if sys.platform == "vxworks":
5798 self.skipTest("Skipping zoneinfo tests on VxWorks")
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005799 if sys.platform == "win32":
5800 self.skipTest("Skipping zoneinfo tests on Windows")
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04005801 try:
5802 self.tz = ZoneInfo.fromname(self.zonename)
5803 except FileNotFoundError as err:
5804 self.skipTest("Skipping %s: %s" % (self.zonename, err))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005805
5806 def assertEquivDatetimes(self, a, b):
5807 self.assertEqual((a.replace(tzinfo=None), a.fold, id(a.tzinfo)),
5808 (b.replace(tzinfo=None), b.fold, id(b.tzinfo)))
5809
5810 def test_folds(self):
5811 tz = self.tz
5812 for dt, shift in tz.folds():
5813 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
5814 udt = dt + x
5815 ldt = tz.fromutc(udt.replace(tzinfo=tz))
5816 self.assertEqual(ldt.fold, 1)
5817 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
5818 self.assertEquivDatetimes(adt, ldt)
5819 utcoffset = ldt.utcoffset()
5820 self.assertEqual(ldt.replace(tzinfo=None), udt + utcoffset)
5821 # Round trip
5822 self.assertEquivDatetimes(ldt.astimezone(timezone.utc),
5823 udt.replace(tzinfo=timezone.utc))
5824
5825
5826 for x in [-timedelta.resolution, shift]:
5827 udt = dt + x
5828 udt = udt.replace(tzinfo=tz)
5829 ldt = tz.fromutc(udt)
5830 self.assertEqual(ldt.fold, 0)
5831
5832 def test_gaps(self):
5833 tz = self.tz
5834 for dt, shift in tz.gaps():
5835 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
5836 udt = dt + x
5837 udt = udt.replace(tzinfo=tz)
5838 ldt = tz.fromutc(udt)
5839 self.assertEqual(ldt.fold, 0)
5840 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
5841 self.assertEquivDatetimes(adt, ldt)
5842 utcoffset = ldt.utcoffset()
5843 self.assertEqual(ldt.replace(tzinfo=None), udt.replace(tzinfo=None) + utcoffset)
5844 # Create a local time inside the gap
5845 ldt = tz.fromutc(dt.replace(tzinfo=tz)) - shift + x
5846 self.assertLess(ldt.replace(fold=1).utcoffset(),
5847 ldt.replace(fold=0).utcoffset(),
5848 "At %s." % ldt)
5849
5850 for x in [-timedelta.resolution, shift]:
5851 udt = dt + x
5852 ldt = tz.fromutc(udt.replace(tzinfo=tz))
5853 self.assertEqual(ldt.fold, 0)
5854
5855 def test_system_transitions(self):
5856 if ('Riyadh8' in self.zonename or
5857 # From tzdata NEWS file:
5858 # The files solar87, solar88, and solar89 are no longer distributed.
5859 # They were a negative experiment - that is, a demonstration that
5860 # tz data can represent solar time only with some difficulty and error.
5861 # Their presence in the distribution caused confusion, as Riyadh
5862 # civil time was generally not solar time in those years.
5863 self.zonename.startswith('right/')):
5864 self.skipTest("Skipping %s" % self.zonename)
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04005865 tz = self.tz
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005866 TZ = os.environ.get('TZ')
5867 os.environ['TZ'] = self.zonename
5868 try:
5869 _time.tzset()
5870 for udt, shift in tz.transitions():
Alexander Belopolsky10c2dd22016-08-12 19:08:15 -04005871 if udt.year >= 2037:
5872 # System support for times around the end of 32-bit time_t
5873 # and later is flaky on many systems.
5874 break
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005875 s0 = (udt - datetime(1970, 1, 1)) // SEC
5876 ss = shift // SEC # shift seconds
5877 for x in [-40 * 3600, -20*3600, -1, 0,
5878 ss - 1, ss + 20 * 3600, ss + 40 * 3600]:
5879 s = s0 + x
5880 sdt = datetime.fromtimestamp(s)
5881 tzdt = datetime.fromtimestamp(s, tz).replace(tzinfo=None)
5882 self.assertEquivDatetimes(sdt, tzdt)
5883 s1 = sdt.timestamp()
5884 self.assertEqual(s, s1)
5885 if ss > 0: # gap
5886 # Create local time inside the gap
5887 dt = datetime.fromtimestamp(s0) - shift / 2
5888 ts0 = dt.timestamp()
5889 ts1 = dt.replace(fold=1).timestamp()
5890 self.assertEqual(ts0, s0 + ss / 2)
5891 self.assertEqual(ts1, s0 - ss / 2)
5892 finally:
5893 if TZ is None:
5894 del os.environ['TZ']
5895 else:
5896 os.environ['TZ'] = TZ
5897 _time.tzset()
5898
5899
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04005900class ZoneInfoCompleteTest(unittest.TestSuite):
5901 def __init__(self):
5902 tests = []
5903 if is_resource_enabled('tzdata'):
5904 for name in ZoneInfo.zonenames():
5905 Test = type('ZoneInfoTest[%s]' % name, (ZoneInfoTest,), {})
5906 Test.zonename = name
5907 for method in dir(Test):
5908 if method.startswith('test_'):
5909 tests.append(Test(method))
5910 super().__init__(tests)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005911
5912# Iran had a sub-minute UTC offset before 1946.
5913class IranTest(ZoneInfoTest):
Alexander Belopolsky07e2a0a2016-07-30 11:41:02 -04005914 zonename = 'Asia/Tehran'
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005915
Paul Ganssle04af5b12018-01-24 17:29:30 -05005916
5917class CapiTest(unittest.TestCase):
5918 def setUp(self):
5919 # Since the C API is not present in the _Pure tests, skip all tests
5920 if self.__class__.__name__.endswith('Pure'):
5921 self.skipTest('Not relevant in pure Python')
5922
5923 # This *must* be called, and it must be called first, so until either
5924 # restriction is loosened, we'll call it as part of test setup
5925 _testcapi.test_datetime_capi()
5926
5927 def test_utc_capi(self):
5928 for use_macro in (True, False):
5929 capi_utc = _testcapi.get_timezone_utc_capi(use_macro)
5930
5931 with self.subTest(use_macro=use_macro):
5932 self.assertIs(capi_utc, timezone.utc)
5933
5934 def test_timezones_capi(self):
5935 est_capi, est_macro, est_macro_nn = _testcapi.make_timezones_capi()
5936
5937 exp_named = timezone(timedelta(hours=-5), "EST")
5938 exp_unnamed = timezone(timedelta(hours=-5))
5939
5940 cases = [
5941 ('est_capi', est_capi, exp_named),
5942 ('est_macro', est_macro, exp_named),
5943 ('est_macro_nn', est_macro_nn, exp_unnamed)
5944 ]
5945
5946 for name, tz_act, tz_exp in cases:
5947 with self.subTest(name=name):
5948 self.assertEqual(tz_act, tz_exp)
5949
5950 dt1 = datetime(2000, 2, 4, tzinfo=tz_act)
5951 dt2 = datetime(2000, 2, 4, tzinfo=tz_exp)
5952
5953 self.assertEqual(dt1, dt2)
5954 self.assertEqual(dt1.tzname(), dt2.tzname())
5955
5956 dt_utc = datetime(2000, 2, 4, 5, tzinfo=timezone.utc)
5957
5958 self.assertEqual(dt1.astimezone(timezone.utc), dt_utc)
5959
Joannah Nanjekye2c5fb172019-08-29 09:54:46 -03005960 def test_PyDateTime_DELTA_GET(self):
5961 class TimeDeltaSubclass(timedelta):
5962 pass
5963
5964 for klass in [timedelta, TimeDeltaSubclass]:
5965 for args in [(26, 55, 99999), (26, 55, 99999)]:
5966 d = klass(*args)
5967 with self.subTest(cls=klass, date=args):
5968 days, seconds, microseconds = _testcapi.PyDateTime_DELTA_GET(d)
5969
5970 self.assertEqual(days, d.days)
5971 self.assertEqual(seconds, d.seconds)
5972 self.assertEqual(microseconds, d.microseconds)
5973
5974 def test_PyDateTime_GET(self):
5975 class DateSubclass(date):
5976 pass
5977
5978 for klass in [date, DateSubclass]:
5979 for args in [(2000, 1, 2), (2012, 2, 29)]:
5980 d = klass(*args)
5981 with self.subTest(cls=klass, date=args):
5982 year, month, day = _testcapi.PyDateTime_GET(d)
5983
5984 self.assertEqual(year, d.year)
5985 self.assertEqual(month, d.month)
5986 self.assertEqual(day, d.day)
5987
5988 def test_PyDateTime_DATE_GET(self):
5989 class DateTimeSubclass(datetime):
5990 pass
5991
5992 for klass in [datetime, DateTimeSubclass]:
5993 for args in [(1993, 8, 26, 22, 12, 55, 99999),
5994 (1993, 8, 26, 22, 12, 55, 99999)]:
5995 d = klass(*args)
5996 with self.subTest(cls=klass, date=args):
5997 hour, minute, second, microsecond = _testcapi.PyDateTime_DATE_GET(d)
5998
5999 self.assertEqual(hour, d.hour)
6000 self.assertEqual(minute, d.minute)
6001 self.assertEqual(second, d.second)
6002 self.assertEqual(microsecond, d.microsecond)
6003
6004 def test_PyDateTime_TIME_GET(self):
6005 class TimeSubclass(time):
6006 pass
6007
6008 for klass in [time, TimeSubclass]:
6009 for args in [(12, 30, 20, 10), (12, 30, 20, 10)]:
6010 d = klass(*args)
6011 with self.subTest(cls=klass, date=args):
6012 hour, minute, second, microsecond = _testcapi.PyDateTime_TIME_GET(d)
6013
6014 self.assertEqual(hour, d.hour)
6015 self.assertEqual(minute, d.minute)
6016 self.assertEqual(second, d.second)
6017 self.assertEqual(microsecond, d.microsecond)
6018
Paul Gansslea049f572018-02-22 15:15:32 -05006019 def test_timezones_offset_zero(self):
6020 utc0, utc1, non_utc = _testcapi.get_timezones_offset_zero()
6021
6022 with self.subTest(testname="utc0"):
6023 self.assertIs(utc0, timezone.utc)
6024
6025 with self.subTest(testname="utc1"):
6026 self.assertIs(utc1, timezone.utc)
6027
6028 with self.subTest(testname="non_utc"):
6029 self.assertIsNot(non_utc, timezone.utc)
6030
6031 non_utc_exp = timezone(timedelta(hours=0), "")
6032
6033 self.assertEqual(non_utc, non_utc_exp)
6034
6035 dt1 = datetime(2000, 2, 4, tzinfo=non_utc)
6036 dt2 = datetime(2000, 2, 4, tzinfo=non_utc_exp)
6037
6038 self.assertEqual(dt1, dt2)
6039 self.assertEqual(dt1.tzname(), dt2.tzname())
6040
Paul Ganssle04af5b12018-01-24 17:29:30 -05006041 def test_check_date(self):
6042 class DateSubclass(date):
6043 pass
6044
6045 d = date(2011, 1, 1)
6046 ds = DateSubclass(2011, 1, 1)
6047 dt = datetime(2011, 1, 1)
6048
6049 is_date = _testcapi.datetime_check_date
6050
6051 # Check the ones that should be valid
6052 self.assertTrue(is_date(d))
6053 self.assertTrue(is_date(dt))
6054 self.assertTrue(is_date(ds))
6055 self.assertTrue(is_date(d, True))
6056
6057 # Check that the subclasses do not match exactly
6058 self.assertFalse(is_date(dt, True))
6059 self.assertFalse(is_date(ds, True))
6060
6061 # Check that various other things are not dates at all
6062 args = [tuple(), list(), 1, '2011-01-01',
6063 timedelta(1), timezone.utc, time(12, 00)]
6064 for arg in args:
6065 for exact in (True, False):
6066 with self.subTest(arg=arg, exact=exact):
6067 self.assertFalse(is_date(arg, exact))
6068
6069 def test_check_time(self):
6070 class TimeSubclass(time):
6071 pass
6072
6073 t = time(12, 30)
6074 ts = TimeSubclass(12, 30)
6075
6076 is_time = _testcapi.datetime_check_time
6077
6078 # Check the ones that should be valid
6079 self.assertTrue(is_time(t))
6080 self.assertTrue(is_time(ts))
6081 self.assertTrue(is_time(t, True))
6082
6083 # Check that the subclass does not match exactly
6084 self.assertFalse(is_time(ts, True))
6085
6086 # Check that various other things are not times
6087 args = [tuple(), list(), 1, '2011-01-01',
6088 timedelta(1), timezone.utc, date(2011, 1, 1)]
6089
6090 for arg in args:
6091 for exact in (True, False):
6092 with self.subTest(arg=arg, exact=exact):
6093 self.assertFalse(is_time(arg, exact))
6094
6095 def test_check_datetime(self):
6096 class DateTimeSubclass(datetime):
6097 pass
6098
6099 dt = datetime(2011, 1, 1, 12, 30)
6100 dts = DateTimeSubclass(2011, 1, 1, 12, 30)
6101
6102 is_datetime = _testcapi.datetime_check_datetime
6103
6104 # Check the ones that should be valid
6105 self.assertTrue(is_datetime(dt))
6106 self.assertTrue(is_datetime(dts))
6107 self.assertTrue(is_datetime(dt, True))
6108
6109 # Check that the subclass does not match exactly
6110 self.assertFalse(is_datetime(dts, True))
6111
6112 # Check that various other things are not datetimes
6113 args = [tuple(), list(), 1, '2011-01-01',
6114 timedelta(1), timezone.utc, date(2011, 1, 1)]
6115
6116 for arg in args:
6117 for exact in (True, False):
6118 with self.subTest(arg=arg, exact=exact):
6119 self.assertFalse(is_datetime(arg, exact))
6120
6121 def test_check_delta(self):
6122 class TimeDeltaSubclass(timedelta):
6123 pass
6124
6125 td = timedelta(1)
6126 tds = TimeDeltaSubclass(1)
6127
6128 is_timedelta = _testcapi.datetime_check_delta
6129
6130 # Check the ones that should be valid
6131 self.assertTrue(is_timedelta(td))
6132 self.assertTrue(is_timedelta(tds))
6133 self.assertTrue(is_timedelta(td, True))
6134
6135 # Check that the subclass does not match exactly
6136 self.assertFalse(is_timedelta(tds, True))
6137
6138 # Check that various other things are not timedeltas
6139 args = [tuple(), list(), 1, '2011-01-01',
6140 timezone.utc, date(2011, 1, 1), datetime(2011, 1, 1)]
6141
6142 for arg in args:
6143 for exact in (True, False):
6144 with self.subTest(arg=arg, exact=exact):
6145 self.assertFalse(is_timedelta(arg, exact))
6146
6147 def test_check_tzinfo(self):
6148 class TZInfoSubclass(tzinfo):
6149 pass
6150
6151 tzi = tzinfo()
6152 tzis = TZInfoSubclass()
6153 tz = timezone(timedelta(hours=-5))
6154
6155 is_tzinfo = _testcapi.datetime_check_tzinfo
6156
6157 # Check the ones that should be valid
6158 self.assertTrue(is_tzinfo(tzi))
6159 self.assertTrue(is_tzinfo(tz))
6160 self.assertTrue(is_tzinfo(tzis))
6161 self.assertTrue(is_tzinfo(tzi, True))
6162
6163 # Check that the subclasses do not match exactly
6164 self.assertFalse(is_tzinfo(tz, True))
6165 self.assertFalse(is_tzinfo(tzis, True))
6166
6167 # Check that various other things are not tzinfos
6168 args = [tuple(), list(), 1, '2011-01-01',
6169 date(2011, 1, 1), datetime(2011, 1, 1)]
6170
6171 for arg in args:
6172 for exact in (True, False):
6173 with self.subTest(arg=arg, exact=exact):
6174 self.assertFalse(is_tzinfo(arg, exact))
6175
Edison A98ff4d52019-05-17 13:28:42 -07006176 def test_date_from_date(self):
6177 exp_date = date(1993, 8, 26)
6178
Serhiy Storchaka1f21eaa2019-09-01 12:16:51 +03006179 for macro in False, True:
Edison A98ff4d52019-05-17 13:28:42 -07006180 with self.subTest(macro=macro):
6181 c_api_date = _testcapi.get_date_fromdate(
6182 macro,
6183 exp_date.year,
6184 exp_date.month,
6185 exp_date.day)
6186
6187 self.assertEqual(c_api_date, exp_date)
6188
6189 def test_datetime_from_dateandtime(self):
6190 exp_date = datetime(1993, 8, 26, 22, 12, 55, 99999)
6191
Serhiy Storchaka1f21eaa2019-09-01 12:16:51 +03006192 for macro in False, True:
Edison A98ff4d52019-05-17 13:28:42 -07006193 with self.subTest(macro=macro):
6194 c_api_date = _testcapi.get_datetime_fromdateandtime(
6195 macro,
6196 exp_date.year,
6197 exp_date.month,
6198 exp_date.day,
6199 exp_date.hour,
6200 exp_date.minute,
6201 exp_date.second,
6202 exp_date.microsecond)
6203
6204 self.assertEqual(c_api_date, exp_date)
6205
6206 def test_datetime_from_dateandtimeandfold(self):
6207 exp_date = datetime(1993, 8, 26, 22, 12, 55, 99999)
6208
6209 for fold in [0, 1]:
Serhiy Storchaka1f21eaa2019-09-01 12:16:51 +03006210 for macro in False, True:
Edison A98ff4d52019-05-17 13:28:42 -07006211 with self.subTest(macro=macro, fold=fold):
6212 c_api_date = _testcapi.get_datetime_fromdateandtimeandfold(
6213 macro,
6214 exp_date.year,
6215 exp_date.month,
6216 exp_date.day,
6217 exp_date.hour,
6218 exp_date.minute,
6219 exp_date.second,
6220 exp_date.microsecond,
6221 exp_date.fold)
6222
6223 self.assertEqual(c_api_date, exp_date)
6224 self.assertEqual(c_api_date.fold, exp_date.fold)
6225
6226 def test_time_from_time(self):
6227 exp_time = time(22, 12, 55, 99999)
6228
Serhiy Storchaka1f21eaa2019-09-01 12:16:51 +03006229 for macro in False, True:
Edison A98ff4d52019-05-17 13:28:42 -07006230 with self.subTest(macro=macro):
6231 c_api_time = _testcapi.get_time_fromtime(
6232 macro,
6233 exp_time.hour,
6234 exp_time.minute,
6235 exp_time.second,
6236 exp_time.microsecond)
6237
6238 self.assertEqual(c_api_time, exp_time)
6239
6240 def test_time_from_timeandfold(self):
6241 exp_time = time(22, 12, 55, 99999)
6242
6243 for fold in [0, 1]:
Serhiy Storchaka1f21eaa2019-09-01 12:16:51 +03006244 for macro in False, True:
Edison A98ff4d52019-05-17 13:28:42 -07006245 with self.subTest(macro=macro, fold=fold):
6246 c_api_time = _testcapi.get_time_fromtimeandfold(
6247 macro,
6248 exp_time.hour,
6249 exp_time.minute,
6250 exp_time.second,
6251 exp_time.microsecond,
6252 exp_time.fold)
6253
6254 self.assertEqual(c_api_time, exp_time)
6255 self.assertEqual(c_api_time.fold, exp_time.fold)
6256
6257 def test_delta_from_dsu(self):
6258 exp_delta = timedelta(26, 55, 99999)
6259
Serhiy Storchaka1f21eaa2019-09-01 12:16:51 +03006260 for macro in False, True:
Edison A98ff4d52019-05-17 13:28:42 -07006261 with self.subTest(macro=macro):
6262 c_api_delta = _testcapi.get_delta_fromdsu(
6263 macro,
6264 exp_delta.days,
6265 exp_delta.seconds,
6266 exp_delta.microseconds)
6267
6268 self.assertEqual(c_api_delta, exp_delta)
6269
Paul Ganssle4d8c8c02019-04-27 15:39:40 -04006270 def test_date_from_timestamp(self):
6271 ts = datetime(1995, 4, 12).timestamp()
6272
Serhiy Storchaka1f21eaa2019-09-01 12:16:51 +03006273 for macro in False, True:
Paul Ganssle4d8c8c02019-04-27 15:39:40 -04006274 with self.subTest(macro=macro):
6275 d = _testcapi.get_date_fromtimestamp(int(ts), macro)
6276
6277 self.assertEqual(d, date(1995, 4, 12))
6278
6279 def test_datetime_from_timestamp(self):
Paul Ganssle4d8c8c02019-04-27 15:39:40 -04006280 cases = [
6281 ((1995, 4, 12), None, False),
6282 ((1995, 4, 12), None, True),
6283 ((1995, 4, 12), timezone(timedelta(hours=1)), True),
6284 ((1995, 4, 12, 14, 30), None, False),
6285 ((1995, 4, 12, 14, 30), None, True),
6286 ((1995, 4, 12, 14, 30), timezone(timedelta(hours=1)), True),
6287 ]
6288
6289 from_timestamp = _testcapi.get_datetime_fromtimestamp
6290 for case in cases:
Serhiy Storchaka1f21eaa2019-09-01 12:16:51 +03006291 for macro in False, True:
Paul Ganssle4d8c8c02019-04-27 15:39:40 -04006292 with self.subTest(case=case, macro=macro):
6293 dtup, tzinfo, usetz = case
6294 dt_orig = datetime(*dtup, tzinfo=tzinfo)
6295 ts = int(dt_orig.timestamp())
6296
6297 dt_rt = from_timestamp(ts, tzinfo, usetz, macro)
6298
6299 self.assertEqual(dt_orig, dt_rt)
6300
6301
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04006302def load_tests(loader, standard_tests, pattern):
6303 standard_tests.addTest(ZoneInfoCompleteTest())
6304 return standard_tests
6305
6306
Alexander Belopolskycf86e362010-07-23 19:25:47 +00006307if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -05006308 unittest.main()