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