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