blob: a1ed614cf0cd013b0e226d891e6f3269e40af1c1 [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):
1452 # bpo-35066: make sure trailing '%' doesn't cause
1453 # datetime's strftime to complain
1454 t = self.theclass(2005, 3, 2)
1455 try:
1456 _time.strftime('%')
1457 except ValueError:
1458 self.skipTest('time module does not support trailing %')
1459 self.assertEqual(t.strftime('%'), '%')
1460 self.assertEqual(t.strftime("m:%m d:%d y:%y %"), "m:03 d:02 y:05 %")
1461
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001462 def test_format(self):
1463 dt = self.theclass(2007, 9, 10)
1464 self.assertEqual(dt.__format__(''), str(dt))
1465
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02001466 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001467 dt.__format__(123)
1468
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001469 # check that a derived class's __str__() gets called
1470 class A(self.theclass):
1471 def __str__(self):
1472 return 'A'
1473 a = A(2007, 9, 10)
1474 self.assertEqual(a.__format__(''), 'A')
1475
1476 # check that a derived class's strftime gets called
1477 class B(self.theclass):
1478 def strftime(self, format_spec):
1479 return 'B'
1480 b = B(2007, 9, 10)
1481 self.assertEqual(b.__format__(''), str(dt))
1482
1483 for fmt in ["m:%m d:%d y:%y",
1484 "m:%m d:%d y:%y H:%H M:%M S:%S",
1485 "%z %Z",
1486 ]:
1487 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1488 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1489 self.assertEqual(b.__format__(fmt), 'B')
1490
1491 def test_resolution_info(self):
1492 # XXX: Should min and max respect subclassing?
1493 if issubclass(self.theclass, datetime):
1494 expected_class = datetime
1495 else:
1496 expected_class = date
1497 self.assertIsInstance(self.theclass.min, expected_class)
1498 self.assertIsInstance(self.theclass.max, expected_class)
1499 self.assertIsInstance(self.theclass.resolution, timedelta)
1500 self.assertTrue(self.theclass.max > self.theclass.min)
1501
1502 def test_extreme_timedelta(self):
1503 big = self.theclass.max - self.theclass.min
1504 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
1505 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
1506 # n == 315537897599999999 ~= 2**58.13
1507 justasbig = timedelta(0, 0, n)
1508 self.assertEqual(big, justasbig)
1509 self.assertEqual(self.theclass.min + big, self.theclass.max)
1510 self.assertEqual(self.theclass.max - big, self.theclass.min)
1511
1512 def test_timetuple(self):
1513 for i in range(7):
1514 # January 2, 1956 is a Monday (0)
1515 d = self.theclass(1956, 1, 2+i)
1516 t = d.timetuple()
1517 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
1518 # February 1, 1956 is a Wednesday (2)
1519 d = self.theclass(1956, 2, 1+i)
1520 t = d.timetuple()
1521 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
1522 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
1523 # of the year.
1524 d = self.theclass(1956, 3, 1+i)
1525 t = d.timetuple()
1526 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
1527 self.assertEqual(t.tm_year, 1956)
1528 self.assertEqual(t.tm_mon, 3)
1529 self.assertEqual(t.tm_mday, 1+i)
1530 self.assertEqual(t.tm_hour, 0)
1531 self.assertEqual(t.tm_min, 0)
1532 self.assertEqual(t.tm_sec, 0)
1533 self.assertEqual(t.tm_wday, (3+i)%7)
1534 self.assertEqual(t.tm_yday, 61+i)
1535 self.assertEqual(t.tm_isdst, -1)
1536
1537 def test_pickling(self):
1538 args = 6, 7, 23
1539 orig = self.theclass(*args)
1540 for pickler, unpickler, proto in pickle_choices:
1541 green = pickler.dumps(orig, proto)
1542 derived = unpickler.loads(green)
1543 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02001544 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001545
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02001546 def test_compat_unpickle(self):
1547 tests = [
1548 b"cdatetime\ndate\n(S'\\x07\\xdf\\x0b\\x1b'\ntR.",
1549 b'cdatetime\ndate\n(U\x04\x07\xdf\x0b\x1btR.',
1550 b'\x80\x02cdatetime\ndate\nU\x04\x07\xdf\x0b\x1b\x85R.',
1551 ]
1552 args = 2015, 11, 27
1553 expected = self.theclass(*args)
1554 for data in tests:
1555 for loads in pickle_loads:
1556 derived = loads(data, encoding='latin1')
1557 self.assertEqual(derived, expected)
1558
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001559 def test_compare(self):
1560 t1 = self.theclass(2, 3, 4)
1561 t2 = self.theclass(2, 3, 4)
1562 self.assertEqual(t1, t2)
1563 self.assertTrue(t1 <= t2)
1564 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001565 self.assertFalse(t1 != t2)
1566 self.assertFalse(t1 < t2)
1567 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001568
1569 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
1570 t2 = self.theclass(*args) # this is larger than t1
1571 self.assertTrue(t1 < t2)
1572 self.assertTrue(t2 > t1)
1573 self.assertTrue(t1 <= t2)
1574 self.assertTrue(t2 >= t1)
1575 self.assertTrue(t1 != t2)
1576 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001577 self.assertFalse(t1 == t2)
1578 self.assertFalse(t2 == t1)
1579 self.assertFalse(t1 > t2)
1580 self.assertFalse(t2 < t1)
1581 self.assertFalse(t1 >= t2)
1582 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001583
1584 for badarg in OTHERSTUFF:
1585 self.assertEqual(t1 == badarg, False)
1586 self.assertEqual(t1 != badarg, True)
1587 self.assertEqual(badarg == t1, False)
1588 self.assertEqual(badarg != t1, True)
1589
1590 self.assertRaises(TypeError, lambda: t1 < badarg)
1591 self.assertRaises(TypeError, lambda: t1 > badarg)
1592 self.assertRaises(TypeError, lambda: t1 >= badarg)
1593 self.assertRaises(TypeError, lambda: badarg <= t1)
1594 self.assertRaises(TypeError, lambda: badarg < t1)
1595 self.assertRaises(TypeError, lambda: badarg > t1)
1596 self.assertRaises(TypeError, lambda: badarg >= t1)
1597
1598 def test_mixed_compare(self):
1599 our = self.theclass(2000, 4, 5)
1600
1601 # Our class can be compared for equality to other classes
1602 self.assertEqual(our == 1, False)
1603 self.assertEqual(1 == our, False)
1604 self.assertEqual(our != 1, True)
1605 self.assertEqual(1 != our, True)
1606
1607 # But the ordering is undefined
1608 self.assertRaises(TypeError, lambda: our < 1)
1609 self.assertRaises(TypeError, lambda: 1 < our)
1610
1611 # Repeat those tests with a different class
1612
1613 class SomeClass:
1614 pass
1615
1616 their = SomeClass()
1617 self.assertEqual(our == their, False)
1618 self.assertEqual(their == our, False)
1619 self.assertEqual(our != their, True)
1620 self.assertEqual(their != our, True)
1621 self.assertRaises(TypeError, lambda: our < their)
1622 self.assertRaises(TypeError, lambda: their < our)
1623
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001624 def test_bool(self):
1625 # All dates are considered true.
1626 self.assertTrue(self.theclass.min)
1627 self.assertTrue(self.theclass.max)
1628
Alexander Belopolsky89da3492011-05-02 13:14:24 -04001629 def test_strftime_y2k(self):
1630 for y in (1, 49, 70, 99, 100, 999, 1000, 1970):
Florent Xicluna49ce0682011-11-01 12:56:14 +01001631 d = self.theclass(y, 1, 1)
1632 # Issue 13305: For years < 1000, the value is not always
1633 # padded to 4 digits across platforms. The C standard
1634 # assumes year >= 1900, so it does not specify the number
1635 # of digits.
1636 if d.strftime("%Y") != '%04d' % y:
1637 # Year 42 returns '42', not padded
1638 self.assertEqual(d.strftime("%Y"), '%d' % y)
Victor Stinner15a83e82016-03-12 08:16:48 +01001639 # '0042' is obtained anyway
1640 self.assertEqual(d.strftime("%4Y"), '%04d' % y)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001641
1642 def test_replace(self):
1643 cls = self.theclass
1644 args = [1, 2, 3]
1645 base = cls(*args)
1646 self.assertEqual(base, base.replace())
1647
1648 i = 0
1649 for name, newval in (("year", 2),
1650 ("month", 3),
1651 ("day", 4)):
1652 newargs = args[:]
1653 newargs[i] = newval
1654 expected = cls(*newargs)
1655 got = base.replace(**{name: newval})
1656 self.assertEqual(expected, got)
1657 i += 1
1658
1659 # Out of bounds.
1660 base = cls(2000, 2, 29)
1661 self.assertRaises(ValueError, base.replace, year=2001)
1662
Paul Ganssle191e9932017-11-09 16:34:29 -05001663 def test_subclass_replace(self):
1664 class DateSubclass(self.theclass):
1665 pass
1666
1667 dt = DateSubclass(2012, 1, 1)
1668 self.assertIs(type(dt.replace(year=2013)), DateSubclass)
1669
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001670 def test_subclass_date(self):
1671
1672 class C(self.theclass):
1673 theAnswer = 42
1674
1675 def __new__(cls, *args, **kws):
1676 temp = kws.copy()
1677 extra = temp.pop('extra')
1678 result = self.theclass.__new__(cls, *args, **temp)
1679 result.extra = extra
1680 return result
1681
1682 def newmeth(self, start):
1683 return start + self.year + self.month
1684
1685 args = 2003, 4, 14
1686
1687 dt1 = self.theclass(*args)
1688 dt2 = C(*args, **{'extra': 7})
1689
1690 self.assertEqual(dt2.__class__, C)
1691 self.assertEqual(dt2.theAnswer, 42)
1692 self.assertEqual(dt2.extra, 7)
1693 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1694 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1695
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05001696 def test_subclass_alternate_constructors(self):
1697 # Test that alternate constructors call the constructor
1698 class DateSubclass(self.theclass):
1699 def __new__(cls, *args, **kwargs):
1700 result = self.theclass.__new__(cls, *args, **kwargs)
1701 result.extra = 7
1702
1703 return result
1704
1705 args = (2003, 4, 14)
1706 d_ord = 731319 # Equivalent ordinal date
1707 d_isoformat = '2003-04-14' # Equivalent isoformat()
1708
1709 base_d = DateSubclass(*args)
1710 self.assertIsInstance(base_d, DateSubclass)
1711 self.assertEqual(base_d.extra, 7)
1712
1713 # Timestamp depends on time zone, so we'll calculate the equivalent here
1714 ts = datetime.combine(base_d, time(0)).timestamp()
1715
1716 test_cases = [
1717 ('fromordinal', (d_ord,)),
1718 ('fromtimestamp', (ts,)),
1719 ('fromisoformat', (d_isoformat,)),
1720 ]
1721
1722 for constr_name, constr_args in test_cases:
1723 for base_obj in (DateSubclass, base_d):
1724 # Test both the classmethod and method
1725 with self.subTest(base_obj_type=type(base_obj),
1726 constr_name=constr_name):
1727 constr = getattr(base_obj, constr_name)
1728
1729 dt = constr(*constr_args)
1730
1731 # Test that it creates the right subclass
1732 self.assertIsInstance(dt, DateSubclass)
1733
1734 # Test that it's equal to the base object
1735 self.assertEqual(dt, base_d)
1736
1737 # Test that it called the constructor
1738 self.assertEqual(dt.extra, 7)
1739
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001740 def test_pickling_subclass_date(self):
1741
1742 args = 6, 7, 23
1743 orig = SubclassDate(*args)
1744 for pickler, unpickler, proto in pickle_choices:
1745 green = pickler.dumps(orig, proto)
1746 derived = unpickler.loads(green)
1747 self.assertEqual(orig, derived)
1748
1749 def test_backdoor_resistance(self):
1750 # For fast unpickling, the constructor accepts a pickle byte string.
1751 # This is a low-overhead backdoor. A user can (by intent or
1752 # mistake) pass a string directly, which (if it's the right length)
1753 # will get treated like a pickle, and bypass the normal sanity
1754 # checks in the constructor. This can create insane objects.
1755 # The constructor doesn't want to burn the time to validate all
1756 # fields, but does check the month field. This stops, e.g.,
1757 # datetime.datetime('1995-03-25') from yielding an insane object.
1758 base = b'1995-03-25'
1759 if not issubclass(self.theclass, datetime):
1760 base = base[:4]
1761 for month_byte in b'9', b'\0', b'\r', b'\xff':
1762 self.assertRaises(TypeError, self.theclass,
1763 base[:2] + month_byte + base[3:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001764 if issubclass(self.theclass, datetime):
1765 # Good bytes, but bad tzinfo:
1766 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
1767 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001768
1769 for ord_byte in range(1, 13):
1770 # This shouldn't blow up because of the month byte alone. If
1771 # the implementation changes to do more-careful checking, it may
1772 # blow up because other fields are insane.
1773 self.theclass(base[:2] + bytes([ord_byte]) + base[3:])
1774
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001775 def test_fromisoformat(self):
1776 # Test that isoformat() is reversible
1777 base_dates = [
1778 (1, 1, 1),
1779 (1000, 2, 14),
1780 (1900, 1, 1),
1781 (2000, 2, 29),
1782 (2004, 11, 12),
1783 (2004, 4, 3),
1784 (2017, 5, 30)
1785 ]
1786
1787 for dt_tuple in base_dates:
1788 dt = self.theclass(*dt_tuple)
1789 dt_str = dt.isoformat()
1790 with self.subTest(dt_str=dt_str):
1791 dt_rt = self.theclass.fromisoformat(dt.isoformat())
1792
1793 self.assertEqual(dt, dt_rt)
1794
1795 def test_fromisoformat_subclass(self):
1796 class DateSubclass(self.theclass):
1797 pass
1798
1799 dt = DateSubclass(2014, 12, 14)
1800
1801 dt_rt = DateSubclass.fromisoformat(dt.isoformat())
1802
1803 self.assertIsInstance(dt_rt, DateSubclass)
1804
1805 def test_fromisoformat_fails(self):
1806 # Test that fromisoformat() fails on invalid values
1807 bad_strs = [
1808 '', # Empty string
Paul Ganssle096329f2018-08-23 11:06:20 -04001809 '\ud800', # bpo-34454: Surrogate code point
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001810 '009-03-04', # Not 10 characters
1811 '123456789', # Not a date
1812 '200a-12-04', # Invalid character in year
1813 '2009-1a-04', # Invalid character in month
1814 '2009-12-0a', # Invalid character in day
1815 '2009-01-32', # Invalid day
1816 '2009-02-29', # Invalid leap day
1817 '20090228', # Valid ISO8601 output not from isoformat()
Paul Ganssle096329f2018-08-23 11:06:20 -04001818 '2009\ud80002\ud80028', # Separators are surrogate codepoints
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001819 ]
1820
1821 for bad_str in bad_strs:
1822 with self.assertRaises(ValueError):
1823 self.theclass.fromisoformat(bad_str)
1824
1825 def test_fromisoformat_fails_typeerror(self):
1826 # Test that fromisoformat fails when passed the wrong type
1827 import io
1828
1829 bad_types = [b'2009-03-01', None, io.StringIO('2009-03-01')]
1830 for bad_type in bad_types:
1831 with self.assertRaises(TypeError):
1832 self.theclass.fromisoformat(bad_type)
1833
Paul Ganssle88c09372019-04-29 09:22:03 -04001834 def test_fromisocalendar(self):
1835 # For each test case, assert that fromisocalendar is the
1836 # inverse of the isocalendar function
1837 dates = [
1838 (2016, 4, 3),
1839 (2005, 1, 2), # (2004, 53, 7)
1840 (2008, 12, 30), # (2009, 1, 2)
1841 (2010, 1, 2), # (2009, 53, 6)
1842 (2009, 12, 31), # (2009, 53, 4)
1843 (1900, 1, 1), # Unusual non-leap year (year % 100 == 0)
1844 (1900, 12, 31),
1845 (2000, 1, 1), # Unusual leap year (year % 400 == 0)
1846 (2000, 12, 31),
1847 (2004, 1, 1), # Leap year
1848 (2004, 12, 31),
1849 (1, 1, 1),
1850 (9999, 12, 31),
1851 (MINYEAR, 1, 1),
1852 (MAXYEAR, 12, 31),
1853 ]
1854
1855 for datecomps in dates:
1856 with self.subTest(datecomps=datecomps):
1857 dobj = self.theclass(*datecomps)
1858 isocal = dobj.isocalendar()
1859
1860 d_roundtrip = self.theclass.fromisocalendar(*isocal)
1861
1862 self.assertEqual(dobj, d_roundtrip)
1863
1864 def test_fromisocalendar_value_errors(self):
1865 isocals = [
1866 (2019, 0, 1),
1867 (2019, -1, 1),
1868 (2019, 54, 1),
1869 (2019, 1, 0),
1870 (2019, 1, -1),
1871 (2019, 1, 8),
1872 (2019, 53, 1),
1873 (10000, 1, 1),
1874 (0, 1, 1),
1875 (9999999, 1, 1),
1876 (2<<32, 1, 1),
1877 (2019, 2<<32, 1),
1878 (2019, 1, 2<<32),
1879 ]
1880
1881 for isocal in isocals:
1882 with self.subTest(isocal=isocal):
1883 with self.assertRaises(ValueError):
1884 self.theclass.fromisocalendar(*isocal)
1885
1886 def test_fromisocalendar_type_errors(self):
1887 err_txformers = [
1888 str,
1889 float,
1890 lambda x: None,
1891 ]
1892
1893 # Take a valid base tuple and transform it to contain one argument
1894 # with the wrong type. Repeat this for each argument, e.g.
1895 # [("2019", 1, 1), (2019, "1", 1), (2019, 1, "1"), ...]
1896 isocals = []
1897 base = (2019, 1, 1)
1898 for i in range(3):
1899 for txformer in err_txformers:
1900 err_val = list(base)
1901 err_val[i] = txformer(err_val[i])
1902 isocals.append(tuple(err_val))
1903
1904 for isocal in isocals:
1905 with self.subTest(isocal=isocal):
1906 with self.assertRaises(TypeError):
1907 self.theclass.fromisocalendar(*isocal)
1908
1909
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001910#############################################################################
1911# datetime tests
1912
1913class SubclassDatetime(datetime):
1914 sub_var = 1
1915
1916class TestDateTime(TestDate):
1917
1918 theclass = datetime
1919
1920 def test_basic_attributes(self):
1921 dt = self.theclass(2002, 3, 1, 12, 0)
1922 self.assertEqual(dt.year, 2002)
1923 self.assertEqual(dt.month, 3)
1924 self.assertEqual(dt.day, 1)
1925 self.assertEqual(dt.hour, 12)
1926 self.assertEqual(dt.minute, 0)
1927 self.assertEqual(dt.second, 0)
1928 self.assertEqual(dt.microsecond, 0)
1929
1930 def test_basic_attributes_nonzero(self):
1931 # Make sure all attributes are non-zero so bugs in
1932 # bit-shifting access show up.
1933 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1934 self.assertEqual(dt.year, 2002)
1935 self.assertEqual(dt.month, 3)
1936 self.assertEqual(dt.day, 1)
1937 self.assertEqual(dt.hour, 12)
1938 self.assertEqual(dt.minute, 59)
1939 self.assertEqual(dt.second, 59)
1940 self.assertEqual(dt.microsecond, 8000)
1941
1942 def test_roundtrip(self):
1943 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1944 self.theclass.now()):
1945 # Verify dt -> string -> datetime identity.
1946 s = repr(dt)
1947 self.assertTrue(s.startswith('datetime.'))
1948 s = s[9:]
1949 dt2 = eval(s)
1950 self.assertEqual(dt, dt2)
1951
1952 # Verify identity via reconstructing from pieces.
1953 dt2 = self.theclass(dt.year, dt.month, dt.day,
1954 dt.hour, dt.minute, dt.second,
1955 dt.microsecond)
1956 self.assertEqual(dt, dt2)
1957
1958 def test_isoformat(self):
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001959 t = self.theclass(1, 2, 3, 4, 5, 1, 123)
1960 self.assertEqual(t.isoformat(), "0001-02-03T04:05:01.000123")
1961 self.assertEqual(t.isoformat('T'), "0001-02-03T04:05:01.000123")
1962 self.assertEqual(t.isoformat(' '), "0001-02-03 04:05:01.000123")
1963 self.assertEqual(t.isoformat('\x00'), "0001-02-03\x0004:05:01.000123")
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03001964 # bpo-34482: Check that surrogates are handled properly.
1965 self.assertEqual(t.isoformat('\ud800'),
1966 "0001-02-03\ud80004:05:01.000123")
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001967 self.assertEqual(t.isoformat(timespec='hours'), "0001-02-03T04")
1968 self.assertEqual(t.isoformat(timespec='minutes'), "0001-02-03T04:05")
1969 self.assertEqual(t.isoformat(timespec='seconds'), "0001-02-03T04:05:01")
1970 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1971 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000123")
1972 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01.000123")
1973 self.assertEqual(t.isoformat(sep=' ', timespec='minutes'), "0001-02-03 04:05")
1974 self.assertRaises(ValueError, t.isoformat, timespec='foo')
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03001975 # bpo-34482: Check that surrogates are handled properly.
1976 self.assertRaises(ValueError, t.isoformat, timespec='\ud800')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001977 # str is ISO format with the separator forced to a blank.
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001978 self.assertEqual(str(t), "0001-02-03 04:05:01.000123")
1979
1980 t = self.theclass(1, 2, 3, 4, 5, 1, 999500, tzinfo=timezone.utc)
1981 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999+00:00")
1982
1983 t = self.theclass(1, 2, 3, 4, 5, 1, 999500)
1984 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999")
1985
1986 t = self.theclass(1, 2, 3, 4, 5, 1)
1987 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01")
1988 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1989 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000000")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001990
1991 t = self.theclass(2, 3, 2)
1992 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1993 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1994 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1995 # str is ISO format with the separator forced to a blank.
1996 self.assertEqual(str(t), "0002-03-02 00:00:00")
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001997 # ISO format with timezone
1998 tz = FixedOffset(timedelta(seconds=16), 'XXX')
1999 t = self.theclass(2, 3, 2, tzinfo=tz)
2000 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00+00:00:16")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002001
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002002 def test_isoformat_timezone(self):
2003 tzoffsets = [
2004 ('05:00', timedelta(hours=5)),
2005 ('02:00', timedelta(hours=2)),
2006 ('06:27', timedelta(hours=6, minutes=27)),
2007 ('12:32:30', timedelta(hours=12, minutes=32, seconds=30)),
2008 ('02:04:09.123456', timedelta(hours=2, minutes=4, seconds=9, microseconds=123456))
2009 ]
2010
2011 tzinfos = [
2012 ('', None),
2013 ('+00:00', timezone.utc),
2014 ('+00:00', timezone(timedelta(0))),
2015 ]
2016
2017 tzinfos += [
2018 (prefix + expected, timezone(sign * td))
2019 for expected, td in tzoffsets
2020 for prefix, sign in [('-', -1), ('+', 1)]
2021 ]
2022
2023 dt_base = self.theclass(2016, 4, 1, 12, 37, 9)
2024 exp_base = '2016-04-01T12:37:09'
2025
2026 for exp_tz, tzi in tzinfos:
2027 dt = dt_base.replace(tzinfo=tzi)
2028 exp = exp_base + exp_tz
2029 with self.subTest(tzi=tzi):
2030 assert dt.isoformat() == exp
2031
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002032 def test_format(self):
2033 dt = self.theclass(2007, 9, 10, 4, 5, 1, 123)
2034 self.assertEqual(dt.__format__(''), str(dt))
2035
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02002036 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002037 dt.__format__(123)
2038
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002039 # check that a derived class's __str__() gets called
2040 class A(self.theclass):
2041 def __str__(self):
2042 return 'A'
2043 a = A(2007, 9, 10, 4, 5, 1, 123)
2044 self.assertEqual(a.__format__(''), 'A')
2045
2046 # check that a derived class's strftime gets called
2047 class B(self.theclass):
2048 def strftime(self, format_spec):
2049 return 'B'
2050 b = B(2007, 9, 10, 4, 5, 1, 123)
2051 self.assertEqual(b.__format__(''), str(dt))
2052
2053 for fmt in ["m:%m d:%d y:%y",
2054 "m:%m d:%d y:%y H:%H M:%M S:%S",
2055 "%z %Z",
2056 ]:
2057 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
2058 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
2059 self.assertEqual(b.__format__(fmt), 'B')
2060
2061 def test_more_ctime(self):
2062 # Test fields that TestDate doesn't touch.
2063 import time
2064
2065 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
2066 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
2067 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
2068 # out. The difference is that t.ctime() produces " 2" for the day,
2069 # but platform ctime() produces "02" for the day. According to
2070 # C99, t.ctime() is correct here.
2071 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
2072
2073 # So test a case where that difference doesn't matter.
2074 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
2075 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
2076
2077 def test_tz_independent_comparing(self):
2078 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
2079 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
2080 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
2081 self.assertEqual(dt1, dt3)
2082 self.assertTrue(dt2 > dt3)
2083
2084 # Make sure comparison doesn't forget microseconds, and isn't done
2085 # via comparing a float timestamp (an IEEE double doesn't have enough
Leo Ariasc3d95082018-02-03 18:36:10 -06002086 # precision to span microsecond resolution across years 1 through 9999,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002087 # so comparing via timestamp necessarily calls some distinct values
2088 # equal).
2089 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
2090 us = timedelta(microseconds=1)
2091 dt2 = dt1 + us
2092 self.assertEqual(dt2 - dt1, us)
2093 self.assertTrue(dt1 < dt2)
2094
2095 def test_strftime_with_bad_tzname_replace(self):
2096 # verify ok if tzinfo.tzname().replace() returns a non-string
2097 class MyTzInfo(FixedOffset):
2098 def tzname(self, dt):
2099 class MyStr(str):
2100 def replace(self, *args):
2101 return None
2102 return MyStr('name')
2103 t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
2104 self.assertRaises(TypeError, t.strftime, '%Z')
2105
2106 def test_bad_constructor_arguments(self):
2107 # bad years
2108 self.theclass(MINYEAR, 1, 1) # no exception
2109 self.theclass(MAXYEAR, 1, 1) # no exception
2110 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
2111 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
2112 # bad months
2113 self.theclass(2000, 1, 1) # no exception
2114 self.theclass(2000, 12, 1) # no exception
2115 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
2116 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
2117 # bad days
2118 self.theclass(2000, 2, 29) # no exception
2119 self.theclass(2004, 2, 29) # no exception
2120 self.theclass(2400, 2, 29) # no exception
2121 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
2122 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
2123 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
2124 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
2125 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
2126 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
2127 # bad hours
2128 self.theclass(2000, 1, 31, 0) # no exception
2129 self.theclass(2000, 1, 31, 23) # no exception
2130 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
2131 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
2132 # bad minutes
2133 self.theclass(2000, 1, 31, 23, 0) # no exception
2134 self.theclass(2000, 1, 31, 23, 59) # no exception
2135 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
2136 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
2137 # bad seconds
2138 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
2139 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
2140 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
2141 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
2142 # bad microseconds
2143 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
2144 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
2145 self.assertRaises(ValueError, self.theclass,
2146 2000, 1, 31, 23, 59, 59, -1)
2147 self.assertRaises(ValueError, self.theclass,
2148 2000, 1, 31, 23, 59, 59,
2149 1000000)
Alexander Belopolsky47649ab2016-08-08 17:05:40 -04002150 # bad fold
2151 self.assertRaises(ValueError, self.theclass,
2152 2000, 1, 31, fold=-1)
2153 self.assertRaises(ValueError, self.theclass,
2154 2000, 1, 31, fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002155 # Positional fold:
2156 self.assertRaises(TypeError, self.theclass,
2157 2000, 1, 31, 23, 59, 59, 0, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04002158
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002159 def test_hash_equality(self):
2160 d = self.theclass(2000, 12, 31, 23, 30, 17)
2161 e = self.theclass(2000, 12, 31, 23, 30, 17)
2162 self.assertEqual(d, e)
2163 self.assertEqual(hash(d), hash(e))
2164
2165 dic = {d: 1}
2166 dic[e] = 2
2167 self.assertEqual(len(dic), 1)
2168 self.assertEqual(dic[d], 2)
2169 self.assertEqual(dic[e], 2)
2170
2171 d = self.theclass(2001, 1, 1, 0, 5, 17)
2172 e = self.theclass(2001, 1, 1, 0, 5, 17)
2173 self.assertEqual(d, e)
2174 self.assertEqual(hash(d), hash(e))
2175
2176 dic = {d: 1}
2177 dic[e] = 2
2178 self.assertEqual(len(dic), 1)
2179 self.assertEqual(dic[d], 2)
2180 self.assertEqual(dic[e], 2)
2181
2182 def test_computations(self):
2183 a = self.theclass(2002, 1, 31)
2184 b = self.theclass(1956, 1, 31)
2185 diff = a-b
2186 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
2187 self.assertEqual(diff.seconds, 0)
2188 self.assertEqual(diff.microseconds, 0)
2189 a = self.theclass(2002, 3, 2, 17, 6)
2190 millisec = timedelta(0, 0, 1000)
2191 hour = timedelta(0, 3600)
2192 day = timedelta(1)
2193 week = timedelta(7)
2194 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
2195 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
2196 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
2197 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
2198 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
2199 self.assertEqual(a - hour, a + -hour)
2200 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
2201 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
2202 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
2203 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
2204 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
2205 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
2206 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
2207 self.assertEqual((a + week) - a, week)
2208 self.assertEqual((a + day) - a, day)
2209 self.assertEqual((a + hour) - a, hour)
2210 self.assertEqual((a + millisec) - a, millisec)
2211 self.assertEqual((a - week) - a, -week)
2212 self.assertEqual((a - day) - a, -day)
2213 self.assertEqual((a - hour) - a, -hour)
2214 self.assertEqual((a - millisec) - a, -millisec)
2215 self.assertEqual(a - (a + week), -week)
2216 self.assertEqual(a - (a + day), -day)
2217 self.assertEqual(a - (a + hour), -hour)
2218 self.assertEqual(a - (a + millisec), -millisec)
2219 self.assertEqual(a - (a - week), week)
2220 self.assertEqual(a - (a - day), day)
2221 self.assertEqual(a - (a - hour), hour)
2222 self.assertEqual(a - (a - millisec), millisec)
2223 self.assertEqual(a + (week + day + hour + millisec),
2224 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
2225 self.assertEqual(a + (week + day + hour + millisec),
2226 (((a + week) + day) + hour) + millisec)
2227 self.assertEqual(a - (week + day + hour + millisec),
2228 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
2229 self.assertEqual(a - (week + day + hour + millisec),
2230 (((a - week) - day) - hour) - millisec)
2231 # Add/sub ints or floats should be illegal
2232 for i in 1, 1.0:
2233 self.assertRaises(TypeError, lambda: a+i)
2234 self.assertRaises(TypeError, lambda: a-i)
2235 self.assertRaises(TypeError, lambda: i+a)
2236 self.assertRaises(TypeError, lambda: i-a)
2237
2238 # delta - datetime is senseless.
2239 self.assertRaises(TypeError, lambda: day - a)
2240 # mixing datetime and (delta or datetime) via * or // is senseless
2241 self.assertRaises(TypeError, lambda: day * a)
2242 self.assertRaises(TypeError, lambda: a * day)
2243 self.assertRaises(TypeError, lambda: day // a)
2244 self.assertRaises(TypeError, lambda: a // day)
2245 self.assertRaises(TypeError, lambda: a * a)
2246 self.assertRaises(TypeError, lambda: a // a)
2247 # datetime + datetime is senseless
2248 self.assertRaises(TypeError, lambda: a + a)
2249
2250 def test_pickling(self):
2251 args = 6, 7, 23, 20, 59, 1, 64**2
2252 orig = self.theclass(*args)
2253 for pickler, unpickler, proto in pickle_choices:
2254 green = pickler.dumps(orig, proto)
2255 derived = unpickler.loads(green)
2256 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02002257 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002258
2259 def test_more_pickling(self):
2260 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
Serhiy Storchakabad12572014-12-15 14:03:42 +02002261 for proto in range(pickle.HIGHEST_PROTOCOL + 1):
2262 s = pickle.dumps(a, proto)
2263 b = pickle.loads(s)
2264 self.assertEqual(b.year, 2003)
2265 self.assertEqual(b.month, 2)
2266 self.assertEqual(b.day, 7)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002267
2268 def test_pickling_subclass_datetime(self):
2269 args = 6, 7, 23, 20, 59, 1, 64**2
2270 orig = SubclassDatetime(*args)
2271 for pickler, unpickler, proto in pickle_choices:
2272 green = pickler.dumps(orig, proto)
2273 derived = unpickler.loads(green)
2274 self.assertEqual(orig, derived)
2275
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02002276 def test_compat_unpickle(self):
2277 tests = [
2278 b'cdatetime\ndatetime\n('
2279 b"S'\\x07\\xdf\\x0b\\x1b\\x14;\\x01\\x00\\x10\\x00'\ntR.",
2280
2281 b'cdatetime\ndatetime\n('
2282 b'U\n\x07\xdf\x0b\x1b\x14;\x01\x00\x10\x00tR.',
2283
2284 b'\x80\x02cdatetime\ndatetime\n'
2285 b'U\n\x07\xdf\x0b\x1b\x14;\x01\x00\x10\x00\x85R.',
2286 ]
2287 args = 2015, 11, 27, 20, 59, 1, 64**2
2288 expected = self.theclass(*args)
2289 for data in tests:
2290 for loads in pickle_loads:
2291 derived = loads(data, encoding='latin1')
2292 self.assertEqual(derived, expected)
2293
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002294 def test_more_compare(self):
2295 # The test_compare() inherited from TestDate covers the error cases.
2296 # We just want to test lexicographic ordering on the members datetime
2297 # has that date lacks.
2298 args = [2000, 11, 29, 20, 58, 16, 999998]
2299 t1 = self.theclass(*args)
2300 t2 = self.theclass(*args)
2301 self.assertEqual(t1, t2)
2302 self.assertTrue(t1 <= t2)
2303 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002304 self.assertFalse(t1 != t2)
2305 self.assertFalse(t1 < t2)
2306 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002307
2308 for i in range(len(args)):
2309 newargs = args[:]
2310 newargs[i] = args[i] + 1
2311 t2 = self.theclass(*newargs) # this is larger than t1
2312 self.assertTrue(t1 < t2)
2313 self.assertTrue(t2 > t1)
2314 self.assertTrue(t1 <= t2)
2315 self.assertTrue(t2 >= t1)
2316 self.assertTrue(t1 != t2)
2317 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002318 self.assertFalse(t1 == t2)
2319 self.assertFalse(t2 == t1)
2320 self.assertFalse(t1 > t2)
2321 self.assertFalse(t2 < t1)
2322 self.assertFalse(t1 >= t2)
2323 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002324
2325
2326 # A helper for timestamp constructor tests.
2327 def verify_field_equality(self, expected, got):
2328 self.assertEqual(expected.tm_year, got.year)
2329 self.assertEqual(expected.tm_mon, got.month)
2330 self.assertEqual(expected.tm_mday, got.day)
2331 self.assertEqual(expected.tm_hour, got.hour)
2332 self.assertEqual(expected.tm_min, got.minute)
2333 self.assertEqual(expected.tm_sec, got.second)
2334
2335 def test_fromtimestamp(self):
2336 import time
2337
2338 ts = time.time()
2339 expected = time.localtime(ts)
2340 got = self.theclass.fromtimestamp(ts)
2341 self.verify_field_equality(expected, got)
2342
2343 def test_utcfromtimestamp(self):
2344 import time
2345
2346 ts = time.time()
2347 expected = time.gmtime(ts)
2348 got = self.theclass.utcfromtimestamp(ts)
2349 self.verify_field_equality(expected, got)
2350
Alexander Belopolskya4415142012-06-08 12:33:09 -04002351 # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in
2352 # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0).
2353 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
2354 def test_timestamp_naive(self):
2355 t = self.theclass(1970, 1, 1)
2356 self.assertEqual(t.timestamp(), 18000.0)
2357 t = self.theclass(1970, 1, 1, 1, 2, 3, 4)
2358 self.assertEqual(t.timestamp(),
2359 18000.0 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002360 # Missing hour
2361 t0 = self.theclass(2012, 3, 11, 2, 30)
2362 t1 = t0.replace(fold=1)
2363 self.assertEqual(self.theclass.fromtimestamp(t1.timestamp()),
2364 t0 - timedelta(hours=1))
2365 self.assertEqual(self.theclass.fromtimestamp(t0.timestamp()),
2366 t1 + timedelta(hours=1))
Alexander Belopolskya4415142012-06-08 12:33:09 -04002367 # Ambiguous hour defaults to DST
2368 t = self.theclass(2012, 11, 4, 1, 30)
2369 self.assertEqual(self.theclass.fromtimestamp(t.timestamp()), t)
2370
2371 # Timestamp may raise an overflow error on some platforms
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002372 # XXX: Do we care to support the first and last year?
2373 for t in [self.theclass(2,1,1), self.theclass(9998,12,12)]:
Alexander Belopolskya4415142012-06-08 12:33:09 -04002374 try:
2375 s = t.timestamp()
2376 except OverflowError:
2377 pass
2378 else:
2379 self.assertEqual(self.theclass.fromtimestamp(s), t)
2380
2381 def test_timestamp_aware(self):
2382 t = self.theclass(1970, 1, 1, tzinfo=timezone.utc)
2383 self.assertEqual(t.timestamp(), 0.0)
2384 t = self.theclass(1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone.utc)
2385 self.assertEqual(t.timestamp(),
2386 3600 + 2*60 + 3 + 4*1e-6)
2387 t = self.theclass(1970, 1, 1, 1, 2, 3, 4,
2388 tzinfo=timezone(timedelta(hours=-5), 'EST'))
2389 self.assertEqual(t.timestamp(),
2390 18000 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002391
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002392 @support.run_with_tz('MSK-03') # Something east of Greenwich
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002393 def test_microsecond_rounding(self):
Victor Stinner5ebfe422015-09-18 14:52:15 +02002394 for fts in [self.theclass.fromtimestamp,
2395 self.theclass.utcfromtimestamp]:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002396 zero = fts(0)
2397 self.assertEqual(zero.second, 0)
2398 self.assertEqual(zero.microsecond, 0)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002399 one = fts(1e-6)
Victor Stinner8050ca92012-03-14 00:17:05 +01002400 try:
2401 minus_one = fts(-1e-6)
2402 except OSError:
2403 # localtime(-1) and gmtime(-1) is not supported on Windows
2404 pass
2405 else:
2406 self.assertEqual(minus_one.second, 59)
2407 self.assertEqual(minus_one.microsecond, 999999)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002408
Victor Stinner8050ca92012-03-14 00:17:05 +01002409 t = fts(-1e-8)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002410 self.assertEqual(t, zero)
Victor Stinner8050ca92012-03-14 00:17:05 +01002411 t = fts(-9e-7)
2412 self.assertEqual(t, minus_one)
2413 t = fts(-1e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002414 self.assertEqual(t, zero)
Victor Stinner8820a352015-09-05 10:50:20 +02002415 t = fts(-1/2**7)
2416 self.assertEqual(t.second, 59)
Victor Stinner7667f582015-09-09 01:02:23 +02002417 self.assertEqual(t.microsecond, 992188)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002418
2419 t = fts(1e-7)
2420 self.assertEqual(t, zero)
2421 t = fts(9e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002422 self.assertEqual(t, one)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002423 t = fts(0.99999949)
2424 self.assertEqual(t.second, 0)
2425 self.assertEqual(t.microsecond, 999999)
2426 t = fts(0.9999999)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002427 self.assertEqual(t.second, 1)
2428 self.assertEqual(t.microsecond, 0)
Victor Stinneradfefa52015-09-04 23:57:25 +02002429 t = fts(1/2**7)
2430 self.assertEqual(t.second, 0)
Victor Stinner7667f582015-09-09 01:02:23 +02002431 self.assertEqual(t.microsecond, 7812)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002432
Victor Stinnerb67f0962017-02-10 10:34:02 +01002433 def test_timestamp_limits(self):
2434 # minimum timestamp
2435 min_dt = self.theclass.min.replace(tzinfo=timezone.utc)
2436 min_ts = min_dt.timestamp()
Victor Stinner6f37e362017-02-10 11:45:14 +01002437 try:
2438 # date 0001-01-01 00:00:00+00:00: timestamp=-62135596800
2439 self.assertEqual(self.theclass.fromtimestamp(min_ts, tz=timezone.utc),
2440 min_dt)
Victor Stinner2a35c932017-02-10 12:37:21 +01002441 except (OverflowError, OSError) as exc:
2442 # the date 0001-01-01 doesn't fit into 32-bit time_t,
2443 # or platform doesn't support such very old date
Victor Stinner6f37e362017-02-10 11:45:14 +01002444 self.skipTest(str(exc))
Victor Stinnerb67f0962017-02-10 10:34:02 +01002445
2446 # maximum timestamp: set seconds to zero to avoid rounding issues
2447 max_dt = self.theclass.max.replace(tzinfo=timezone.utc,
2448 second=0, microsecond=0)
2449 max_ts = max_dt.timestamp()
2450 # date 9999-12-31 23:59:00+00:00: timestamp 253402300740
2451 self.assertEqual(self.theclass.fromtimestamp(max_ts, tz=timezone.utc),
2452 max_dt)
2453
2454 # number of seconds greater than 1 year: make sure that the new date
2455 # is not valid in datetime.datetime limits
2456 delta = 3600 * 24 * 400
2457
2458 # too small
2459 ts = min_ts - delta
2460 # converting a Python int to C time_t can raise a OverflowError,
2461 # especially on 32-bit platforms.
2462 with self.assertRaises((ValueError, OverflowError)):
2463 self.theclass.fromtimestamp(ts)
2464 with self.assertRaises((ValueError, OverflowError)):
2465 self.theclass.utcfromtimestamp(ts)
2466
2467 # too big
2468 ts = max_dt.timestamp() + delta
2469 with self.assertRaises((ValueError, OverflowError)):
2470 self.theclass.fromtimestamp(ts)
2471 with self.assertRaises((ValueError, OverflowError)):
2472 self.theclass.utcfromtimestamp(ts)
2473
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002474 def test_insane_fromtimestamp(self):
2475 # It's possible that some platform maps time_t to double,
2476 # and that this test will fail there. This test should
2477 # exempt such platforms (provided they return reasonable
2478 # results!).
2479 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002480 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002481 insane)
2482
2483 def test_insane_utcfromtimestamp(self):
2484 # It's possible that some platform maps time_t to double,
2485 # and that this test will fail there. This test should
2486 # exempt such platforms (provided they return reasonable
2487 # results!).
2488 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002489 self.assertRaises(OverflowError, self.theclass.utcfromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002490 insane)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002491
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002492 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2493 def test_negative_float_fromtimestamp(self):
2494 # The result is tz-dependent; at least test that this doesn't
2495 # fail (like it did before bug 1646728 was fixed).
2496 self.theclass.fromtimestamp(-1.05)
2497
2498 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2499 def test_negative_float_utcfromtimestamp(self):
2500 d = self.theclass.utcfromtimestamp(-1.05)
2501 self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
2502
2503 def test_utcnow(self):
2504 import time
2505
2506 # Call it a success if utcnow() and utcfromtimestamp() are within
2507 # a second of each other.
2508 tolerance = timedelta(seconds=1)
2509 for dummy in range(3):
2510 from_now = self.theclass.utcnow()
2511 from_timestamp = self.theclass.utcfromtimestamp(time.time())
2512 if abs(from_timestamp - from_now) <= tolerance:
2513 break
2514 # Else try again a few times.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002515 self.assertLessEqual(abs(from_timestamp - from_now), tolerance)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002516
2517 def test_strptime(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002518 string = '2004-12-01 13:02:47.197'
2519 format = '%Y-%m-%d %H:%M:%S.%f'
2520 expected = _strptime._strptime_datetime(self.theclass, string, format)
2521 got = self.theclass.strptime(string, format)
2522 self.assertEqual(expected, got)
2523 self.assertIs(type(expected), self.theclass)
2524 self.assertIs(type(got), self.theclass)
2525
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03002526 # bpo-34482: Check that surrogates are handled properly.
2527 inputs = [
2528 ('2004-12-01\ud80013:02:47.197', '%Y-%m-%d\ud800%H:%M:%S.%f'),
2529 ('2004\ud80012-01 13:02:47.197', '%Y\ud800%m-%d %H:%M:%S.%f'),
2530 ('2004-12-01 13:02\ud80047.197', '%Y-%m-%d %H:%M\ud800%S.%f'),
2531 ]
2532 for string, format in inputs:
2533 with self.subTest(string=string, format=format):
2534 expected = _strptime._strptime_datetime(self.theclass, string,
2535 format)
2536 got = self.theclass.strptime(string, format)
2537 self.assertEqual(expected, got)
2538
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002539 strptime = self.theclass.strptime
Mike Gleen6b9c2042019-06-18 19:14:57 +01002540
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002541 self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
2542 self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
Mario Corchero32318932017-10-26 01:35:41 +01002543 self.assertEqual(
2544 strptime("-00:02:01.000003", "%z").utcoffset(),
2545 -timedelta(minutes=2, seconds=1, microseconds=3)
2546 )
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002547 # Only local timezone and UTC are supported
2548 for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
2549 (-_time.timezone, _time.tzname[0])):
2550 if tzseconds < 0:
2551 sign = '-'
2552 seconds = -tzseconds
2553 else:
2554 sign ='+'
2555 seconds = tzseconds
2556 hours, minutes = divmod(seconds//60, 60)
2557 dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
Martin Panterfca22322015-11-16 09:22:19 +00002558 dt = strptime(dtstr, "%z %Z")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002559 self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
2560 self.assertEqual(dt.tzname(), tzname)
2561 # Can produce inconsistent datetime
2562 dtstr, fmt = "+1234 UTC", "%z %Z"
2563 dt = strptime(dtstr, fmt)
2564 self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE)
2565 self.assertEqual(dt.tzname(), 'UTC')
2566 # yet will roundtrip
2567 self.assertEqual(dt.strftime(fmt), dtstr)
2568
2569 # Produce naive datetime if no %z is provided
2570 self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
2571
2572 with self.assertRaises(ValueError): strptime("-2400", "%z")
2573 with self.assertRaises(ValueError): strptime("-000", "%z")
2574
Mike Gleen6b9c2042019-06-18 19:14:57 +01002575 def test_strptime_single_digit(self):
2576 # bpo-34903: Check that single digit dates and times are allowed.
2577
2578 strptime = self.theclass.strptime
2579
2580 with self.assertRaises(ValueError):
2581 # %y does require two digits.
2582 newdate = strptime('01/02/3 04:05:06', '%d/%m/%y %H:%M:%S')
2583 dt1 = self.theclass(2003, 2, 1, 4, 5, 6)
2584 dt2 = self.theclass(2003, 1, 2, 4, 5, 6)
2585 dt3 = self.theclass(2003, 2, 1, 0, 0, 0)
2586 dt4 = self.theclass(2003, 1, 25, 0, 0, 0)
2587 inputs = [
2588 ('%d', '1/02/03 4:5:6', '%d/%m/%y %H:%M:%S', dt1),
2589 ('%m', '01/2/03 4:5:6', '%d/%m/%y %H:%M:%S', dt1),
2590 ('%H', '01/02/03 4:05:06', '%d/%m/%y %H:%M:%S', dt1),
2591 ('%M', '01/02/03 04:5:06', '%d/%m/%y %H:%M:%S', dt1),
2592 ('%S', '01/02/03 04:05:6', '%d/%m/%y %H:%M:%S', dt1),
2593 ('%j', '2/03 04am:05:06', '%j/%y %I%p:%M:%S',dt2),
2594 ('%I', '02/03 4am:05:06', '%j/%y %I%p:%M:%S',dt2),
2595 ('%w', '6/04/03', '%w/%U/%y', dt3),
2596 # %u requires a single digit.
2597 ('%W', '6/4/2003', '%u/%W/%Y', dt3),
2598 ('%V', '6/4/2003', '%u/%V/%G', dt4),
2599 ]
2600 for reason, string, format, target in inputs:
2601 reason = 'test single digit ' + reason
2602 with self.subTest(reason=reason,
2603 string=string,
2604 format=format,
2605 target=target):
2606 newdate = strptime(string, format)
2607 self.assertEqual(newdate, target, msg=reason)
2608
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002609 def test_more_timetuple(self):
2610 # This tests fields beyond those tested by the TestDate.test_timetuple.
2611 t = self.theclass(2004, 12, 31, 6, 22, 33)
2612 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
2613 self.assertEqual(t.timetuple(),
2614 (t.year, t.month, t.day,
2615 t.hour, t.minute, t.second,
2616 t.weekday(),
2617 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
2618 -1))
2619 tt = t.timetuple()
2620 self.assertEqual(tt.tm_year, t.year)
2621 self.assertEqual(tt.tm_mon, t.month)
2622 self.assertEqual(tt.tm_mday, t.day)
2623 self.assertEqual(tt.tm_hour, t.hour)
2624 self.assertEqual(tt.tm_min, t.minute)
2625 self.assertEqual(tt.tm_sec, t.second)
2626 self.assertEqual(tt.tm_wday, t.weekday())
2627 self.assertEqual(tt.tm_yday, t.toordinal() -
2628 date(t.year, 1, 1).toordinal() + 1)
2629 self.assertEqual(tt.tm_isdst, -1)
2630
2631 def test_more_strftime(self):
2632 # This tests fields beyond those tested by the TestDate.test_strftime.
2633 t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
2634 self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
2635 "12 31 04 000047 33 22 06 366")
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04002636 for (s, us), z in [((33, 123), "33.000123"), ((33, 0), "33"),]:
2637 tz = timezone(-timedelta(hours=2, seconds=s, microseconds=us))
2638 t = t.replace(tzinfo=tz)
2639 self.assertEqual(t.strftime("%z"), "-0200" + z)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002640
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03002641 # bpo-34482: Check that surrogates don't cause a crash.
2642 try:
2643 t.strftime('%y\ud800%m %H\ud800%M')
2644 except UnicodeEncodeError:
2645 pass
2646
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002647 def test_extract(self):
2648 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2649 self.assertEqual(dt.date(), date(2002, 3, 4))
2650 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2651
2652 def test_combine(self):
2653 d = date(2002, 3, 4)
2654 t = time(18, 45, 3, 1234)
2655 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2656 combine = self.theclass.combine
2657 dt = combine(d, t)
2658 self.assertEqual(dt, expected)
2659
2660 dt = combine(time=t, date=d)
2661 self.assertEqual(dt, expected)
2662
2663 self.assertEqual(d, dt.date())
2664 self.assertEqual(t, dt.time())
2665 self.assertEqual(dt, combine(dt.date(), dt.time()))
2666
2667 self.assertRaises(TypeError, combine) # need an arg
2668 self.assertRaises(TypeError, combine, d) # need two args
2669 self.assertRaises(TypeError, combine, t, d) # args reversed
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002670 self.assertRaises(TypeError, combine, d, t, 1) # wrong tzinfo type
2671 self.assertRaises(TypeError, combine, d, t, 1, 2) # too many args
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002672 self.assertRaises(TypeError, combine, "date", "time") # wrong types
2673 self.assertRaises(TypeError, combine, d, "time") # wrong type
2674 self.assertRaises(TypeError, combine, "date", t) # wrong type
2675
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002676 # tzinfo= argument
2677 dt = combine(d, t, timezone.utc)
2678 self.assertIs(dt.tzinfo, timezone.utc)
2679 dt = combine(d, t, tzinfo=timezone.utc)
2680 self.assertIs(dt.tzinfo, timezone.utc)
2681 t = time()
2682 dt = combine(dt, t)
2683 self.assertEqual(dt.date(), d)
2684 self.assertEqual(dt.time(), t)
2685
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002686 def test_replace(self):
2687 cls = self.theclass
2688 args = [1, 2, 3, 4, 5, 6, 7]
2689 base = cls(*args)
2690 self.assertEqual(base, base.replace())
2691
2692 i = 0
2693 for name, newval in (("year", 2),
2694 ("month", 3),
2695 ("day", 4),
2696 ("hour", 5),
2697 ("minute", 6),
2698 ("second", 7),
2699 ("microsecond", 8)):
2700 newargs = args[:]
2701 newargs[i] = newval
2702 expected = cls(*newargs)
2703 got = base.replace(**{name: newval})
2704 self.assertEqual(expected, got)
2705 i += 1
2706
2707 # Out of bounds.
2708 base = cls(2000, 2, 29)
2709 self.assertRaises(ValueError, base.replace, year=2001)
2710
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002711 @support.run_with_tz('EDT4')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002712 def test_astimezone(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002713 dt = self.theclass.now()
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002714 f = FixedOffset(44, "0044")
2715 dt_utc = dt.replace(tzinfo=timezone(timedelta(hours=-4), 'EDT'))
2716 self.assertEqual(dt.astimezone(), dt_utc) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002717 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
2718 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002719 dt_f = dt.replace(tzinfo=f) + timedelta(hours=4, minutes=44)
2720 self.assertEqual(dt.astimezone(f), dt_f) # naive
2721 self.assertEqual(dt.astimezone(tz=f), dt_f) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002722
2723 class Bogus(tzinfo):
2724 def utcoffset(self, dt): return None
2725 def dst(self, dt): return timedelta(0)
2726 bog = Bogus()
2727 self.assertRaises(ValueError, dt.astimezone, bog) # naive
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002728 self.assertEqual(dt.replace(tzinfo=bog).astimezone(f), dt_f)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002729
2730 class AlsoBogus(tzinfo):
2731 def utcoffset(self, dt): return timedelta(0)
2732 def dst(self, dt): return None
2733 alsobog = AlsoBogus()
2734 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
2735
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002736 class Broken(tzinfo):
2737 def utcoffset(self, dt): return 1
2738 def dst(self, dt): return 1
2739 broken = Broken()
2740 dt_broken = dt.replace(tzinfo=broken)
2741 with self.assertRaises(TypeError):
2742 dt_broken.astimezone()
2743
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002744 def test_subclass_datetime(self):
2745
2746 class C(self.theclass):
2747 theAnswer = 42
2748
2749 def __new__(cls, *args, **kws):
2750 temp = kws.copy()
2751 extra = temp.pop('extra')
2752 result = self.theclass.__new__(cls, *args, **temp)
2753 result.extra = extra
2754 return result
2755
2756 def newmeth(self, start):
2757 return start + self.year + self.month + self.second
2758
2759 args = 2003, 4, 14, 12, 13, 41
2760
2761 dt1 = self.theclass(*args)
2762 dt2 = C(*args, **{'extra': 7})
2763
2764 self.assertEqual(dt2.__class__, C)
2765 self.assertEqual(dt2.theAnswer, 42)
2766 self.assertEqual(dt2.extra, 7)
2767 self.assertEqual(dt1.toordinal(), dt2.toordinal())
2768 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
2769 dt1.second - 7)
2770
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002771 def test_subclass_alternate_constructors_datetime(self):
2772 # Test that alternate constructors call the constructor
2773 class DateTimeSubclass(self.theclass):
2774 def __new__(cls, *args, **kwargs):
2775 result = self.theclass.__new__(cls, *args, **kwargs)
2776 result.extra = 7
2777
2778 return result
2779
2780 args = (2003, 4, 14, 12, 30, 15, 123456)
2781 d_isoformat = '2003-04-14T12:30:15.123456' # Equivalent isoformat()
2782 utc_ts = 1050323415.123456 # UTC timestamp
2783
2784 base_d = DateTimeSubclass(*args)
2785 self.assertIsInstance(base_d, DateTimeSubclass)
2786 self.assertEqual(base_d.extra, 7)
2787
2788 # Timestamp depends on time zone, so we'll calculate the equivalent here
2789 ts = base_d.timestamp()
2790
2791 test_cases = [
Paul Ganssle89427cd2019-02-04 14:42:04 -05002792 ('fromtimestamp', (ts,), base_d),
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002793 # See https://bugs.python.org/issue32417
Paul Ganssle89427cd2019-02-04 14:42:04 -05002794 ('fromtimestamp', (ts, timezone.utc),
2795 base_d.astimezone(timezone.utc)),
2796 ('utcfromtimestamp', (utc_ts,), base_d),
2797 ('fromisoformat', (d_isoformat,), base_d),
2798 ('strptime', (d_isoformat, '%Y-%m-%dT%H:%M:%S.%f'), base_d),
2799 ('combine', (date(*args[0:3]), time(*args[3:])), base_d),
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002800 ]
2801
Paul Ganssle89427cd2019-02-04 14:42:04 -05002802 for constr_name, constr_args, expected in test_cases:
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002803 for base_obj in (DateTimeSubclass, base_d):
2804 # Test both the classmethod and method
2805 with self.subTest(base_obj_type=type(base_obj),
2806 constr_name=constr_name):
Paul Ganssle89427cd2019-02-04 14:42:04 -05002807 constructor = getattr(base_obj, constr_name)
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002808
Paul Ganssle89427cd2019-02-04 14:42:04 -05002809 dt = constructor(*constr_args)
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002810
2811 # Test that it creates the right subclass
2812 self.assertIsInstance(dt, DateTimeSubclass)
2813
2814 # Test that it's equal to the base object
Paul Ganssle89427cd2019-02-04 14:42:04 -05002815 self.assertEqual(dt, expected)
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002816
2817 # Test that it called the constructor
2818 self.assertEqual(dt.extra, 7)
2819
Paul Ganssle89427cd2019-02-04 14:42:04 -05002820 def test_subclass_now(self):
2821 # Test that alternate constructors call the constructor
2822 class DateTimeSubclass(self.theclass):
2823 def __new__(cls, *args, **kwargs):
2824 result = self.theclass.__new__(cls, *args, **kwargs)
2825 result.extra = 7
2826
2827 return result
2828
2829 test_cases = [
2830 ('now', 'now', {}),
2831 ('utcnow', 'utcnow', {}),
2832 ('now_utc', 'now', {'tz': timezone.utc}),
2833 ('now_fixed', 'now', {'tz': timezone(timedelta(hours=-5), "EST")}),
2834 ]
2835
2836 for name, meth_name, kwargs in test_cases:
2837 with self.subTest(name):
2838 constr = getattr(DateTimeSubclass, meth_name)
2839 dt = constr(**kwargs)
2840
2841 self.assertIsInstance(dt, DateTimeSubclass)
2842 self.assertEqual(dt.extra, 7)
2843
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002844 def test_fromisoformat_datetime(self):
2845 # Test that isoformat() is reversible
2846 base_dates = [
2847 (1, 1, 1),
2848 (1900, 1, 1),
2849 (2004, 11, 12),
2850 (2017, 5, 30)
2851 ]
2852
2853 base_times = [
2854 (0, 0, 0, 0),
2855 (0, 0, 0, 241000),
2856 (0, 0, 0, 234567),
2857 (12, 30, 45, 234567)
2858 ]
2859
2860 separators = [' ', 'T']
2861
2862 tzinfos = [None, timezone.utc,
2863 timezone(timedelta(hours=-5)),
2864 timezone(timedelta(hours=2))]
2865
2866 dts = [self.theclass(*date_tuple, *time_tuple, tzinfo=tzi)
2867 for date_tuple in base_dates
2868 for time_tuple in base_times
2869 for tzi in tzinfos]
2870
2871 for dt in dts:
2872 for sep in separators:
2873 dtstr = dt.isoformat(sep=sep)
2874
2875 with self.subTest(dtstr=dtstr):
2876 dt_rt = self.theclass.fromisoformat(dtstr)
2877 self.assertEqual(dt, dt_rt)
2878
2879 def test_fromisoformat_timezone(self):
2880 base_dt = self.theclass(2014, 12, 30, 12, 30, 45, 217456)
2881
2882 tzoffsets = [
2883 timedelta(hours=5), timedelta(hours=2),
2884 timedelta(hours=6, minutes=27),
2885 timedelta(hours=12, minutes=32, seconds=30),
2886 timedelta(hours=2, minutes=4, seconds=9, microseconds=123456)
2887 ]
2888
2889 tzoffsets += [-1 * td for td in tzoffsets]
2890
2891 tzinfos = [None, timezone.utc,
2892 timezone(timedelta(hours=0))]
2893
2894 tzinfos += [timezone(td) for td in tzoffsets]
2895
2896 for tzi in tzinfos:
2897 dt = base_dt.replace(tzinfo=tzi)
2898 dtstr = dt.isoformat()
2899
2900 with self.subTest(tstr=dtstr):
2901 dt_rt = self.theclass.fromisoformat(dtstr)
2902 assert dt == dt_rt, dt_rt
2903
2904 def test_fromisoformat_separators(self):
2905 separators = [
2906 ' ', 'T', '\u007f', # 1-bit widths
2907 '\u0080', 'ʁ', # 2-bit widths
2908 'ᛇ', '時', # 3-bit widths
Paul Ganssle096329f2018-08-23 11:06:20 -04002909 '🐍', # 4-bit widths
2910 '\ud800', # bpo-34454: Surrogate code point
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002911 ]
2912
2913 for sep in separators:
2914 dt = self.theclass(2018, 1, 31, 23, 59, 47, 124789)
2915 dtstr = dt.isoformat(sep=sep)
2916
2917 with self.subTest(dtstr=dtstr):
2918 dt_rt = self.theclass.fromisoformat(dtstr)
2919 self.assertEqual(dt, dt_rt)
2920
2921 def test_fromisoformat_ambiguous(self):
2922 # Test strings like 2018-01-31+12:15 (where +12:15 is not a time zone)
2923 separators = ['+', '-']
2924 for sep in separators:
2925 dt = self.theclass(2018, 1, 31, 12, 15)
2926 dtstr = dt.isoformat(sep=sep)
2927
2928 with self.subTest(dtstr=dtstr):
2929 dt_rt = self.theclass.fromisoformat(dtstr)
2930 self.assertEqual(dt, dt_rt)
2931
2932 def test_fromisoformat_timespecs(self):
2933 datetime_bases = [
2934 (2009, 12, 4, 8, 17, 45, 123456),
2935 (2009, 12, 4, 8, 17, 45, 0)]
2936
2937 tzinfos = [None, timezone.utc,
2938 timezone(timedelta(hours=-5)),
2939 timezone(timedelta(hours=2)),
2940 timezone(timedelta(hours=6, minutes=27))]
2941
2942 timespecs = ['hours', 'minutes', 'seconds',
2943 'milliseconds', 'microseconds']
2944
2945 for ip, ts in enumerate(timespecs):
2946 for tzi in tzinfos:
2947 for dt_tuple in datetime_bases:
2948 if ts == 'milliseconds':
2949 new_microseconds = 1000 * (dt_tuple[6] // 1000)
2950 dt_tuple = dt_tuple[0:6] + (new_microseconds,)
2951
2952 dt = self.theclass(*(dt_tuple[0:(4 + ip)]), tzinfo=tzi)
2953 dtstr = dt.isoformat(timespec=ts)
2954 with self.subTest(dtstr=dtstr):
2955 dt_rt = self.theclass.fromisoformat(dtstr)
2956 self.assertEqual(dt, dt_rt)
2957
2958 def test_fromisoformat_fails_datetime(self):
2959 # Test that fromisoformat() fails on invalid values
2960 bad_strs = [
2961 '', # Empty string
Paul Ganssle096329f2018-08-23 11:06:20 -04002962 '\ud800', # bpo-34454: Surrogate code point
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002963 '2009.04-19T03', # Wrong first separator
2964 '2009-04.19T03', # Wrong second separator
2965 '2009-04-19T0a', # Invalid hours
2966 '2009-04-19T03:1a:45', # Invalid minutes
2967 '2009-04-19T03:15:4a', # Invalid seconds
2968 '2009-04-19T03;15:45', # Bad first time separator
2969 '2009-04-19T03:15;45', # Bad second time separator
2970 '2009-04-19T03:15:4500:00', # Bad time zone separator
2971 '2009-04-19T03:15:45.2345', # Too many digits for milliseconds
2972 '2009-04-19T03:15:45.1234567', # Too many digits for microseconds
2973 '2009-04-19T03:15:45.123456+24:30', # Invalid time zone offset
2974 '2009-04-19T03:15:45.123456-24:30', # Invalid negative offset
2975 '2009-04-10ᛇᛇᛇᛇᛇ12:15', # Too many unicode separators
Paul Ganssle096329f2018-08-23 11:06:20 -04002976 '2009-04\ud80010T12:15', # Surrogate char in date
2977 '2009-04-10T12\ud80015', # Surrogate char in time
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002978 '2009-04-19T1', # Incomplete hours
2979 '2009-04-19T12:3', # Incomplete minutes
2980 '2009-04-19T12:30:4', # Incomplete seconds
2981 '2009-04-19T12:', # Ends with time separator
2982 '2009-04-19T12:30:', # Ends with time separator
2983 '2009-04-19T12:30:45.', # Ends with time separator
2984 '2009-04-19T12:30:45.123456+', # Ends with timzone separator
2985 '2009-04-19T12:30:45.123456-', # Ends with timzone separator
2986 '2009-04-19T12:30:45.123456-05:00a', # Extra text
2987 '2009-04-19T12:30:45.123-05:00a', # Extra text
2988 '2009-04-19T12:30:45-05:00a', # Extra text
2989 ]
2990
2991 for bad_str in bad_strs:
2992 with self.subTest(bad_str=bad_str):
2993 with self.assertRaises(ValueError):
2994 self.theclass.fromisoformat(bad_str)
2995
Paul Ganssle3df85402018-10-22 12:32:52 -04002996 def test_fromisoformat_fails_surrogate(self):
2997 # Test that when fromisoformat() fails with a surrogate character as
2998 # the separator, the error message contains the original string
2999 dtstr = "2018-01-03\ud80001:0113"
3000
3001 with self.assertRaisesRegex(ValueError, re.escape(repr(dtstr))):
3002 self.theclass.fromisoformat(dtstr)
3003
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003004 def test_fromisoformat_utc(self):
3005 dt_str = '2014-04-19T13:21:13+00:00'
3006 dt = self.theclass.fromisoformat(dt_str)
3007
3008 self.assertIs(dt.tzinfo, timezone.utc)
3009
3010 def test_fromisoformat_subclass(self):
3011 class DateTimeSubclass(self.theclass):
3012 pass
3013
3014 dt = DateTimeSubclass(2014, 12, 14, 9, 30, 45, 457390,
3015 tzinfo=timezone(timedelta(hours=10, minutes=45)))
3016
3017 dt_rt = DateTimeSubclass.fromisoformat(dt.isoformat())
3018
3019 self.assertEqual(dt, dt_rt)
3020 self.assertIsInstance(dt_rt, DateTimeSubclass)
3021
3022
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003023class TestSubclassDateTime(TestDateTime):
3024 theclass = SubclassDatetime
3025 # Override tests not designed for subclass
Zachary Ware9fe6d862013-12-08 00:20:35 -06003026 @unittest.skip('not appropriate for subclasses')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003027 def test_roundtrip(self):
3028 pass
3029
3030class SubclassTime(time):
3031 sub_var = 1
3032
3033class TestTime(HarmlessMixedComparison, unittest.TestCase):
3034
3035 theclass = time
3036
3037 def test_basic_attributes(self):
3038 t = self.theclass(12, 0)
3039 self.assertEqual(t.hour, 12)
3040 self.assertEqual(t.minute, 0)
3041 self.assertEqual(t.second, 0)
3042 self.assertEqual(t.microsecond, 0)
3043
3044 def test_basic_attributes_nonzero(self):
3045 # Make sure all attributes are non-zero so bugs in
3046 # bit-shifting access show up.
3047 t = self.theclass(12, 59, 59, 8000)
3048 self.assertEqual(t.hour, 12)
3049 self.assertEqual(t.minute, 59)
3050 self.assertEqual(t.second, 59)
3051 self.assertEqual(t.microsecond, 8000)
3052
3053 def test_roundtrip(self):
3054 t = self.theclass(1, 2, 3, 4)
3055
3056 # Verify t -> string -> time identity.
3057 s = repr(t)
3058 self.assertTrue(s.startswith('datetime.'))
3059 s = s[9:]
3060 t2 = eval(s)
3061 self.assertEqual(t, t2)
3062
3063 # Verify identity via reconstructing from pieces.
3064 t2 = self.theclass(t.hour, t.minute, t.second,
3065 t.microsecond)
3066 self.assertEqual(t, t2)
3067
3068 def test_comparing(self):
3069 args = [1, 2, 3, 4]
3070 t1 = self.theclass(*args)
3071 t2 = self.theclass(*args)
3072 self.assertEqual(t1, t2)
3073 self.assertTrue(t1 <= t2)
3074 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003075 self.assertFalse(t1 != t2)
3076 self.assertFalse(t1 < t2)
3077 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003078
3079 for i in range(len(args)):
3080 newargs = args[:]
3081 newargs[i] = args[i] + 1
3082 t2 = self.theclass(*newargs) # this is larger than t1
3083 self.assertTrue(t1 < t2)
3084 self.assertTrue(t2 > t1)
3085 self.assertTrue(t1 <= t2)
3086 self.assertTrue(t2 >= t1)
3087 self.assertTrue(t1 != t2)
3088 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003089 self.assertFalse(t1 == t2)
3090 self.assertFalse(t2 == t1)
3091 self.assertFalse(t1 > t2)
3092 self.assertFalse(t2 < t1)
3093 self.assertFalse(t1 >= t2)
3094 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003095
3096 for badarg in OTHERSTUFF:
3097 self.assertEqual(t1 == badarg, False)
3098 self.assertEqual(t1 != badarg, True)
3099 self.assertEqual(badarg == t1, False)
3100 self.assertEqual(badarg != t1, True)
3101
3102 self.assertRaises(TypeError, lambda: t1 <= badarg)
3103 self.assertRaises(TypeError, lambda: t1 < badarg)
3104 self.assertRaises(TypeError, lambda: t1 > badarg)
3105 self.assertRaises(TypeError, lambda: t1 >= badarg)
3106 self.assertRaises(TypeError, lambda: badarg <= t1)
3107 self.assertRaises(TypeError, lambda: badarg < t1)
3108 self.assertRaises(TypeError, lambda: badarg > t1)
3109 self.assertRaises(TypeError, lambda: badarg >= t1)
3110
3111 def test_bad_constructor_arguments(self):
3112 # bad hours
3113 self.theclass(0, 0) # no exception
3114 self.theclass(23, 0) # no exception
3115 self.assertRaises(ValueError, self.theclass, -1, 0)
3116 self.assertRaises(ValueError, self.theclass, 24, 0)
3117 # bad minutes
3118 self.theclass(23, 0) # no exception
3119 self.theclass(23, 59) # no exception
3120 self.assertRaises(ValueError, self.theclass, 23, -1)
3121 self.assertRaises(ValueError, self.theclass, 23, 60)
3122 # bad seconds
3123 self.theclass(23, 59, 0) # no exception
3124 self.theclass(23, 59, 59) # no exception
3125 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
3126 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
3127 # bad microseconds
3128 self.theclass(23, 59, 59, 0) # no exception
3129 self.theclass(23, 59, 59, 999999) # no exception
3130 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
3131 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
3132
3133 def test_hash_equality(self):
3134 d = self.theclass(23, 30, 17)
3135 e = self.theclass(23, 30, 17)
3136 self.assertEqual(d, e)
3137 self.assertEqual(hash(d), hash(e))
3138
3139 dic = {d: 1}
3140 dic[e] = 2
3141 self.assertEqual(len(dic), 1)
3142 self.assertEqual(dic[d], 2)
3143 self.assertEqual(dic[e], 2)
3144
3145 d = self.theclass(0, 5, 17)
3146 e = self.theclass(0, 5, 17)
3147 self.assertEqual(d, e)
3148 self.assertEqual(hash(d), hash(e))
3149
3150 dic = {d: 1}
3151 dic[e] = 2
3152 self.assertEqual(len(dic), 1)
3153 self.assertEqual(dic[d], 2)
3154 self.assertEqual(dic[e], 2)
3155
3156 def test_isoformat(self):
3157 t = self.theclass(4, 5, 1, 123)
3158 self.assertEqual(t.isoformat(), "04:05:01.000123")
3159 self.assertEqual(t.isoformat(), str(t))
3160
3161 t = self.theclass()
3162 self.assertEqual(t.isoformat(), "00:00:00")
3163 self.assertEqual(t.isoformat(), str(t))
3164
3165 t = self.theclass(microsecond=1)
3166 self.assertEqual(t.isoformat(), "00:00:00.000001")
3167 self.assertEqual(t.isoformat(), str(t))
3168
3169 t = self.theclass(microsecond=10)
3170 self.assertEqual(t.isoformat(), "00:00:00.000010")
3171 self.assertEqual(t.isoformat(), str(t))
3172
3173 t = self.theclass(microsecond=100)
3174 self.assertEqual(t.isoformat(), "00:00:00.000100")
3175 self.assertEqual(t.isoformat(), str(t))
3176
3177 t = self.theclass(microsecond=1000)
3178 self.assertEqual(t.isoformat(), "00:00:00.001000")
3179 self.assertEqual(t.isoformat(), str(t))
3180
3181 t = self.theclass(microsecond=10000)
3182 self.assertEqual(t.isoformat(), "00:00:00.010000")
3183 self.assertEqual(t.isoformat(), str(t))
3184
3185 t = self.theclass(microsecond=100000)
3186 self.assertEqual(t.isoformat(), "00:00:00.100000")
3187 self.assertEqual(t.isoformat(), str(t))
3188
Alexander Belopolskya2998a62016-03-06 14:58:43 -05003189 t = self.theclass(hour=12, minute=34, second=56, microsecond=123456)
3190 self.assertEqual(t.isoformat(timespec='hours'), "12")
3191 self.assertEqual(t.isoformat(timespec='minutes'), "12:34")
3192 self.assertEqual(t.isoformat(timespec='seconds'), "12:34:56")
3193 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.123")
3194 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.123456")
3195 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56.123456")
3196 self.assertRaises(ValueError, t.isoformat, timespec='monkey')
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03003197 # bpo-34482: Check that surrogates are handled properly.
3198 self.assertRaises(ValueError, t.isoformat, timespec='\ud800')
Alexander Belopolskya2998a62016-03-06 14:58:43 -05003199
3200 t = self.theclass(hour=12, minute=34, second=56, microsecond=999500)
3201 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.999")
3202
3203 t = self.theclass(hour=12, minute=34, second=56, microsecond=0)
3204 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.000")
3205 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.000000")
3206 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56")
3207
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003208 def test_isoformat_timezone(self):
3209 tzoffsets = [
3210 ('05:00', timedelta(hours=5)),
3211 ('02:00', timedelta(hours=2)),
3212 ('06:27', timedelta(hours=6, minutes=27)),
3213 ('12:32:30', timedelta(hours=12, minutes=32, seconds=30)),
3214 ('02:04:09.123456', timedelta(hours=2, minutes=4, seconds=9, microseconds=123456))
3215 ]
3216
3217 tzinfos = [
3218 ('', None),
3219 ('+00:00', timezone.utc),
3220 ('+00:00', timezone(timedelta(0))),
3221 ]
3222
3223 tzinfos += [
3224 (prefix + expected, timezone(sign * td))
3225 for expected, td in tzoffsets
3226 for prefix, sign in [('-', -1), ('+', 1)]
3227 ]
3228
3229 t_base = self.theclass(12, 37, 9)
3230 exp_base = '12:37:09'
3231
3232 for exp_tz, tzi in tzinfos:
3233 t = t_base.replace(tzinfo=tzi)
3234 exp = exp_base + exp_tz
3235 with self.subTest(tzi=tzi):
3236 assert t.isoformat() == exp
3237
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003238 def test_1653736(self):
3239 # verify it doesn't accept extra keyword arguments
3240 t = self.theclass(second=1)
3241 self.assertRaises(TypeError, t.isoformat, foo=3)
3242
3243 def test_strftime(self):
3244 t = self.theclass(1, 2, 3, 4)
3245 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
3246 # A naive object replaces %z and %Z with empty strings.
3247 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
3248
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03003249 # bpo-34482: Check that surrogates don't cause a crash.
3250 try:
3251 t.strftime('%H\ud800%M')
3252 except UnicodeEncodeError:
3253 pass
3254
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003255 def test_format(self):
3256 t = self.theclass(1, 2, 3, 4)
3257 self.assertEqual(t.__format__(''), str(t))
3258
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02003259 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04003260 t.__format__(123)
3261
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003262 # check that a derived class's __str__() gets called
3263 class A(self.theclass):
3264 def __str__(self):
3265 return 'A'
3266 a = A(1, 2, 3, 4)
3267 self.assertEqual(a.__format__(''), 'A')
3268
3269 # check that a derived class's strftime gets called
3270 class B(self.theclass):
3271 def strftime(self, format_spec):
3272 return 'B'
3273 b = B(1, 2, 3, 4)
3274 self.assertEqual(b.__format__(''), str(t))
3275
3276 for fmt in ['%H %M %S',
3277 ]:
3278 self.assertEqual(t.__format__(fmt), t.strftime(fmt))
3279 self.assertEqual(a.__format__(fmt), t.strftime(fmt))
3280 self.assertEqual(b.__format__(fmt), 'B')
3281
3282 def test_str(self):
3283 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
3284 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
3285 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
3286 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
3287 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
3288
3289 def test_repr(self):
3290 name = 'datetime.' + self.theclass.__name__
3291 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
3292 "%s(1, 2, 3, 4)" % name)
3293 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
3294 "%s(10, 2, 3, 4000)" % name)
3295 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
3296 "%s(0, 2, 3, 400000)" % name)
3297 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
3298 "%s(12, 2, 3)" % name)
3299 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
3300 "%s(23, 15)" % name)
3301
3302 def test_resolution_info(self):
3303 self.assertIsInstance(self.theclass.min, self.theclass)
3304 self.assertIsInstance(self.theclass.max, self.theclass)
3305 self.assertIsInstance(self.theclass.resolution, timedelta)
3306 self.assertTrue(self.theclass.max > self.theclass.min)
3307
3308 def test_pickling(self):
3309 args = 20, 59, 16, 64**2
3310 orig = self.theclass(*args)
3311 for pickler, unpickler, proto in pickle_choices:
3312 green = pickler.dumps(orig, proto)
3313 derived = unpickler.loads(green)
3314 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003315 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003316
3317 def test_pickling_subclass_time(self):
3318 args = 20, 59, 16, 64**2
3319 orig = SubclassTime(*args)
3320 for pickler, unpickler, proto in pickle_choices:
3321 green = pickler.dumps(orig, proto)
3322 derived = unpickler.loads(green)
3323 self.assertEqual(orig, derived)
3324
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02003325 def test_compat_unpickle(self):
3326 tests = [
Justin Blanchard122376d2019-08-29 03:36:15 -04003327 (b"cdatetime\ntime\n(S'\\x14;\\x10\\x00\\x10\\x00'\ntR.",
3328 (20, 59, 16, 64**2)),
3329 (b'cdatetime\ntime\n(U\x06\x14;\x10\x00\x10\x00tR.',
3330 (20, 59, 16, 64**2)),
3331 (b'\x80\x02cdatetime\ntime\nU\x06\x14;\x10\x00\x10\x00\x85R.',
3332 (20, 59, 16, 64**2)),
3333 (b"cdatetime\ntime\n(S'\\x14;\\x19\\x00\\x10\\x00'\ntR.",
3334 (20, 59, 25, 64**2)),
3335 (b'cdatetime\ntime\n(U\x06\x14;\x19\x00\x10\x00tR.',
3336 (20, 59, 25, 64**2)),
3337 (b'\x80\x02cdatetime\ntime\nU\x06\x14;\x19\x00\x10\x00\x85R.',
3338 (20, 59, 25, 64**2)),
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02003339 ]
Justin Blanchard122376d2019-08-29 03:36:15 -04003340 for i, (data, args) in enumerate(tests):
3341 with self.subTest(i=i):
3342 expected = self.theclass(*args)
3343 for loads in pickle_loads:
3344 derived = loads(data, encoding='latin1')
3345 self.assertEqual(derived, expected)
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02003346
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003347 def test_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003348 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003349 cls = self.theclass
3350 self.assertTrue(cls(1))
3351 self.assertTrue(cls(0, 1))
3352 self.assertTrue(cls(0, 0, 1))
3353 self.assertTrue(cls(0, 0, 0, 1))
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003354 self.assertTrue(cls(0))
3355 self.assertTrue(cls())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003356
3357 def test_replace(self):
3358 cls = self.theclass
3359 args = [1, 2, 3, 4]
3360 base = cls(*args)
3361 self.assertEqual(base, base.replace())
3362
3363 i = 0
3364 for name, newval in (("hour", 5),
3365 ("minute", 6),
3366 ("second", 7),
3367 ("microsecond", 8)):
3368 newargs = args[:]
3369 newargs[i] = newval
3370 expected = cls(*newargs)
3371 got = base.replace(**{name: newval})
3372 self.assertEqual(expected, got)
3373 i += 1
3374
3375 # Out of bounds.
3376 base = cls(1)
3377 self.assertRaises(ValueError, base.replace, hour=24)
3378 self.assertRaises(ValueError, base.replace, minute=-1)
3379 self.assertRaises(ValueError, base.replace, second=100)
3380 self.assertRaises(ValueError, base.replace, microsecond=1000000)
3381
Paul Ganssle191e9932017-11-09 16:34:29 -05003382 def test_subclass_replace(self):
3383 class TimeSubclass(self.theclass):
3384 pass
3385
3386 ctime = TimeSubclass(12, 30)
3387 self.assertIs(type(ctime.replace(hour=10)), TimeSubclass)
3388
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003389 def test_subclass_time(self):
3390
3391 class C(self.theclass):
3392 theAnswer = 42
3393
3394 def __new__(cls, *args, **kws):
3395 temp = kws.copy()
3396 extra = temp.pop('extra')
3397 result = self.theclass.__new__(cls, *args, **temp)
3398 result.extra = extra
3399 return result
3400
3401 def newmeth(self, start):
3402 return start + self.hour + self.second
3403
3404 args = 4, 5, 6
3405
3406 dt1 = self.theclass(*args)
3407 dt2 = C(*args, **{'extra': 7})
3408
3409 self.assertEqual(dt2.__class__, C)
3410 self.assertEqual(dt2.theAnswer, 42)
3411 self.assertEqual(dt2.extra, 7)
3412 self.assertEqual(dt1.isoformat(), dt2.isoformat())
3413 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
3414
3415 def test_backdoor_resistance(self):
3416 # see TestDate.test_backdoor_resistance().
3417 base = '2:59.0'
3418 for hour_byte in ' ', '9', chr(24), '\xff':
3419 self.assertRaises(TypeError, self.theclass,
3420 hour_byte + base[1:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04003421 # Good bytes, but bad tzinfo:
3422 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
3423 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003424
3425# A mixin for classes with a tzinfo= argument. Subclasses must define
Martin Panter46f50722016-05-26 05:35:26 +00003426# theclass as a class attribute, and theclass(1, 1, 1, tzinfo=whatever)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003427# must be legit (which is true for time and datetime).
3428class TZInfoBase:
3429
3430 def test_argument_passing(self):
3431 cls = self.theclass
3432 # A datetime passes itself on, a time passes None.
3433 class introspective(tzinfo):
3434 def tzname(self, dt): return dt and "real" or "none"
3435 def utcoffset(self, dt):
3436 return timedelta(minutes = dt and 42 or -42)
3437 dst = utcoffset
3438
3439 obj = cls(1, 2, 3, tzinfo=introspective())
3440
3441 expected = cls is time and "none" or "real"
3442 self.assertEqual(obj.tzname(), expected)
3443
3444 expected = timedelta(minutes=(cls is time and -42 or 42))
3445 self.assertEqual(obj.utcoffset(), expected)
3446 self.assertEqual(obj.dst(), expected)
3447
3448 def test_bad_tzinfo_classes(self):
3449 cls = self.theclass
3450 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
3451
3452 class NiceTry(object):
3453 def __init__(self): pass
3454 def utcoffset(self, dt): pass
3455 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
3456
3457 class BetterTry(tzinfo):
3458 def __init__(self): pass
3459 def utcoffset(self, dt): pass
3460 b = BetterTry()
3461 t = cls(1, 1, 1, tzinfo=b)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003462 self.assertIs(t.tzinfo, b)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003463
3464 def test_utc_offset_out_of_bounds(self):
3465 class Edgy(tzinfo):
3466 def __init__(self, offset):
3467 self.offset = timedelta(minutes=offset)
3468 def utcoffset(self, dt):
3469 return self.offset
3470
3471 cls = self.theclass
3472 for offset, legit in ((-1440, False),
3473 (-1439, True),
3474 (1439, True),
3475 (1440, False)):
3476 if cls is time:
3477 t = cls(1, 2, 3, tzinfo=Edgy(offset))
3478 elif cls is datetime:
3479 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
3480 else:
3481 assert 0, "impossible"
3482 if legit:
3483 aofs = abs(offset)
3484 h, m = divmod(aofs, 60)
3485 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
3486 if isinstance(t, datetime):
3487 t = t.timetz()
3488 self.assertEqual(str(t), "01:02:03" + tag)
3489 else:
3490 self.assertRaises(ValueError, str, t)
3491
3492 def test_tzinfo_classes(self):
3493 cls = self.theclass
3494 class C1(tzinfo):
3495 def utcoffset(self, dt): return None
3496 def dst(self, dt): return None
3497 def tzname(self, dt): return None
3498 for t in (cls(1, 1, 1),
3499 cls(1, 1, 1, tzinfo=None),
3500 cls(1, 1, 1, tzinfo=C1())):
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003501 self.assertIsNone(t.utcoffset())
3502 self.assertIsNone(t.dst())
3503 self.assertIsNone(t.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003504
3505 class C3(tzinfo):
3506 def utcoffset(self, dt): return timedelta(minutes=-1439)
3507 def dst(self, dt): return timedelta(minutes=1439)
3508 def tzname(self, dt): return "aname"
3509 t = cls(1, 1, 1, tzinfo=C3())
3510 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
3511 self.assertEqual(t.dst(), timedelta(minutes=1439))
3512 self.assertEqual(t.tzname(), "aname")
3513
3514 # Wrong types.
3515 class C4(tzinfo):
3516 def utcoffset(self, dt): return "aname"
3517 def dst(self, dt): return 7
3518 def tzname(self, dt): return 0
3519 t = cls(1, 1, 1, tzinfo=C4())
3520 self.assertRaises(TypeError, t.utcoffset)
3521 self.assertRaises(TypeError, t.dst)
3522 self.assertRaises(TypeError, t.tzname)
3523
3524 # Offset out of range.
3525 class C6(tzinfo):
3526 def utcoffset(self, dt): return timedelta(hours=-24)
3527 def dst(self, dt): return timedelta(hours=24)
3528 t = cls(1, 1, 1, tzinfo=C6())
3529 self.assertRaises(ValueError, t.utcoffset)
3530 self.assertRaises(ValueError, t.dst)
3531
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04003532 # Not a whole number of seconds.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003533 class C7(tzinfo):
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04003534 def utcoffset(self, dt): return timedelta(microseconds=61)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003535 def dst(self, dt): return timedelta(microseconds=-81)
3536 t = cls(1, 1, 1, tzinfo=C7())
Alexander Belopolsky018d3532017-07-31 10:26:50 -04003537 self.assertEqual(t.utcoffset(), timedelta(microseconds=61))
3538 self.assertEqual(t.dst(), timedelta(microseconds=-81))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003539
3540 def test_aware_compare(self):
3541 cls = self.theclass
3542
3543 # Ensure that utcoffset() gets ignored if the comparands have
3544 # the same tzinfo member.
3545 class OperandDependentOffset(tzinfo):
3546 def utcoffset(self, t):
3547 if t.minute < 10:
3548 # d0 and d1 equal after adjustment
3549 return timedelta(minutes=t.minute)
3550 else:
3551 # d2 off in the weeds
3552 return timedelta(minutes=59)
3553
3554 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
3555 d0 = base.replace(minute=3)
3556 d1 = base.replace(minute=9)
3557 d2 = base.replace(minute=11)
3558 for x in d0, d1, d2:
3559 for y in d0, d1, d2:
3560 for op in lt, le, gt, ge, eq, ne:
3561 got = op(x, y)
3562 expected = op(x.minute, y.minute)
3563 self.assertEqual(got, expected)
3564
3565 # However, if they're different members, uctoffset is not ignored.
penguindustin96466302019-05-06 14:57:17 -04003566 # Note that a time can't actually have an operand-dependent offset,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003567 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
3568 # so skip this test for time.
3569 if cls is not time:
3570 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
3571 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
3572 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
3573 for x in d0, d1, d2:
3574 for y in d0, d1, d2:
3575 got = (x > y) - (x < y)
3576 if (x is d0 or x is d1) and (y is d0 or y is d1):
3577 expected = 0
3578 elif x is y is d2:
3579 expected = 0
3580 elif x is d2:
3581 expected = -1
3582 else:
3583 assert y is d2
3584 expected = 1
3585 self.assertEqual(got, expected)
3586
3587
3588# Testing time objects with a non-None tzinfo.
3589class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
3590 theclass = time
3591
3592 def test_empty(self):
3593 t = self.theclass()
3594 self.assertEqual(t.hour, 0)
3595 self.assertEqual(t.minute, 0)
3596 self.assertEqual(t.second, 0)
3597 self.assertEqual(t.microsecond, 0)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003598 self.assertIsNone(t.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003599
3600 def test_zones(self):
3601 est = FixedOffset(-300, "EST", 1)
3602 utc = FixedOffset(0, "UTC", -2)
3603 met = FixedOffset(60, "MET", 3)
3604 t1 = time( 7, 47, tzinfo=est)
3605 t2 = time(12, 47, tzinfo=utc)
3606 t3 = time(13, 47, tzinfo=met)
3607 t4 = time(microsecond=40)
3608 t5 = time(microsecond=40, tzinfo=utc)
3609
3610 self.assertEqual(t1.tzinfo, est)
3611 self.assertEqual(t2.tzinfo, utc)
3612 self.assertEqual(t3.tzinfo, met)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003613 self.assertIsNone(t4.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003614 self.assertEqual(t5.tzinfo, utc)
3615
3616 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
3617 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
3618 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003619 self.assertIsNone(t4.utcoffset())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003620 self.assertRaises(TypeError, t1.utcoffset, "no args")
3621
3622 self.assertEqual(t1.tzname(), "EST")
3623 self.assertEqual(t2.tzname(), "UTC")
3624 self.assertEqual(t3.tzname(), "MET")
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003625 self.assertIsNone(t4.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003626 self.assertRaises(TypeError, t1.tzname, "no args")
3627
3628 self.assertEqual(t1.dst(), timedelta(minutes=1))
3629 self.assertEqual(t2.dst(), timedelta(minutes=-2))
3630 self.assertEqual(t3.dst(), timedelta(minutes=3))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003631 self.assertIsNone(t4.dst())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003632 self.assertRaises(TypeError, t1.dst, "no args")
3633
3634 self.assertEqual(hash(t1), hash(t2))
3635 self.assertEqual(hash(t1), hash(t3))
3636 self.assertEqual(hash(t2), hash(t3))
3637
3638 self.assertEqual(t1, t2)
3639 self.assertEqual(t1, t3)
3640 self.assertEqual(t2, t3)
Alexander Belopolsky08313822012-06-15 20:19:47 -04003641 self.assertNotEqual(t4, t5) # mixed tz-aware & naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003642 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
3643 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
3644
3645 self.assertEqual(str(t1), "07:47:00-05:00")
3646 self.assertEqual(str(t2), "12:47:00+00:00")
3647 self.assertEqual(str(t3), "13:47:00+01:00")
3648 self.assertEqual(str(t4), "00:00:00.000040")
3649 self.assertEqual(str(t5), "00:00:00.000040+00:00")
3650
3651 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
3652 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
3653 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
3654 self.assertEqual(t4.isoformat(), "00:00:00.000040")
3655 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
3656
3657 d = 'datetime.time'
3658 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
3659 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
3660 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
3661 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
3662 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
3663
3664 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
3665 "07:47:00 %Z=EST %z=-0500")
3666 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
3667 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
3668
3669 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
3670 t1 = time(23, 59, tzinfo=yuck)
3671 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
3672 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
3673
3674 # Check that an invalid tzname result raises an exception.
3675 class Badtzname(tzinfo):
Alexander Belopolskye239d232010-12-08 23:31:48 +00003676 tz = 42
3677 def tzname(self, dt): return self.tz
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003678 t = time(2, 3, 4, tzinfo=Badtzname())
3679 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
3680 self.assertRaises(TypeError, t.strftime, "%Z")
3681
Alexander Belopolskye239d232010-12-08 23:31:48 +00003682 # Issue #6697:
Utkarsh Upadhyay287c5592017-07-21 02:14:54 +02003683 if '_Fast' in self.__class__.__name__:
Alexander Belopolskye239d232010-12-08 23:31:48 +00003684 Badtzname.tz = '\ud800'
3685 self.assertRaises(ValueError, t.strftime, "%Z")
3686
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003687 def test_hash_edge_cases(self):
3688 # Offsets that overflow a basic time.
3689 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
3690 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
3691 self.assertEqual(hash(t1), hash(t2))
3692
3693 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
3694 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
3695 self.assertEqual(hash(t1), hash(t2))
3696
3697 def test_pickling(self):
3698 # Try one without a tzinfo.
3699 args = 20, 59, 16, 64**2
3700 orig = self.theclass(*args)
3701 for pickler, unpickler, proto in pickle_choices:
3702 green = pickler.dumps(orig, proto)
3703 derived = unpickler.loads(green)
3704 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003705 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003706
3707 # Try one with a tzinfo.
3708 tinfo = PicklableFixedOffset(-300, 'cookie')
3709 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
3710 for pickler, unpickler, proto in pickle_choices:
3711 green = pickler.dumps(orig, proto)
3712 derived = unpickler.loads(green)
3713 self.assertEqual(orig, derived)
3714 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3715 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3716 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003717 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003718
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02003719 def test_compat_unpickle(self):
3720 tests = [
3721 b"cdatetime\ntime\n(S'\\x05\\x06\\x07\\x01\\xe2@'\n"
3722 b"ctest.datetimetester\nPicklableFixedOffset\n(tR"
3723 b"(dS'_FixedOffset__offset'\ncdatetime\ntimedelta\n"
3724 b"(I-1\nI68400\nI0\ntRs"
3725 b"S'_FixedOffset__dstoffset'\nNs"
3726 b"S'_FixedOffset__name'\nS'cookie'\nsbtR.",
3727
3728 b'cdatetime\ntime\n(U\x06\x05\x06\x07\x01\xe2@'
3729 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
3730 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
3731 b'(J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00tR'
3732 b'U\x17_FixedOffset__dstoffsetN'
3733 b'U\x12_FixedOffset__nameU\x06cookieubtR.',
3734
3735 b'\x80\x02cdatetime\ntime\nU\x06\x05\x06\x07\x01\xe2@'
3736 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
3737 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
3738 b'J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00\x87R'
3739 b'U\x17_FixedOffset__dstoffsetN'
3740 b'U\x12_FixedOffset__nameU\x06cookieub\x86R.',
3741 ]
3742
3743 tinfo = PicklableFixedOffset(-300, 'cookie')
3744 expected = self.theclass(5, 6, 7, 123456, tzinfo=tinfo)
3745 for data in tests:
3746 for loads in pickle_loads:
3747 derived = loads(data, encoding='latin1')
3748 self.assertEqual(derived, expected, repr(data))
3749 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3750 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3751 self.assertEqual(derived.tzname(), 'cookie')
3752
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003753 def test_more_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003754 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003755 cls = self.theclass
3756
3757 t = cls(0, tzinfo=FixedOffset(-300, ""))
3758 self.assertTrue(t)
3759
3760 t = cls(5, tzinfo=FixedOffset(-300, ""))
3761 self.assertTrue(t)
3762
3763 t = cls(5, tzinfo=FixedOffset(300, ""))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003764 self.assertTrue(t)
3765
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003766 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
3767 self.assertTrue(t)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003768
3769 def test_replace(self):
3770 cls = self.theclass
3771 z100 = FixedOffset(100, "+100")
3772 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3773 args = [1, 2, 3, 4, z100]
3774 base = cls(*args)
3775 self.assertEqual(base, base.replace())
3776
3777 i = 0
3778 for name, newval in (("hour", 5),
3779 ("minute", 6),
3780 ("second", 7),
3781 ("microsecond", 8),
3782 ("tzinfo", zm200)):
3783 newargs = args[:]
3784 newargs[i] = newval
3785 expected = cls(*newargs)
3786 got = base.replace(**{name: newval})
3787 self.assertEqual(expected, got)
3788 i += 1
3789
3790 # Ensure we can get rid of a tzinfo.
3791 self.assertEqual(base.tzname(), "+100")
3792 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003793 self.assertIsNone(base2.tzinfo)
3794 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003795
3796 # Ensure we can add one.
3797 base3 = base2.replace(tzinfo=z100)
3798 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003799 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003800
3801 # Out of bounds.
3802 base = cls(1)
3803 self.assertRaises(ValueError, base.replace, hour=24)
3804 self.assertRaises(ValueError, base.replace, minute=-1)
3805 self.assertRaises(ValueError, base.replace, second=100)
3806 self.assertRaises(ValueError, base.replace, microsecond=1000000)
3807
3808 def test_mixed_compare(self):
Serhiy Storchaka17e52642019-08-04 12:38:46 +03003809 t1 = self.theclass(1, 2, 3)
3810 t2 = self.theclass(1, 2, 3)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003811 self.assertEqual(t1, t2)
3812 t2 = t2.replace(tzinfo=None)
3813 self.assertEqual(t1, t2)
3814 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3815 self.assertEqual(t1, t2)
3816 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04003817 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003818
3819 # In time w/ identical tzinfo objects, utcoffset is ignored.
3820 class Varies(tzinfo):
3821 def __init__(self):
3822 self.offset = timedelta(minutes=22)
3823 def utcoffset(self, t):
3824 self.offset += timedelta(minutes=1)
3825 return self.offset
3826
3827 v = Varies()
3828 t1 = t2.replace(tzinfo=v)
3829 t2 = t2.replace(tzinfo=v)
3830 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3831 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3832 self.assertEqual(t1, t2)
3833
3834 # But if they're not identical, it isn't ignored.
3835 t2 = t2.replace(tzinfo=Varies())
3836 self.assertTrue(t1 < t2) # t1's offset counter still going up
3837
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003838 def test_fromisoformat(self):
3839 time_examples = [
3840 (0, 0, 0, 0),
3841 (23, 59, 59, 999999),
3842 ]
3843
3844 hh = (9, 12, 20)
3845 mm = (5, 30)
3846 ss = (4, 45)
3847 usec = (0, 245000, 678901)
3848
3849 time_examples += list(itertools.product(hh, mm, ss, usec))
3850
3851 tzinfos = [None, timezone.utc,
3852 timezone(timedelta(hours=2)),
3853 timezone(timedelta(hours=6, minutes=27))]
3854
3855 for ttup in time_examples:
3856 for tzi in tzinfos:
3857 t = self.theclass(*ttup, tzinfo=tzi)
3858 tstr = t.isoformat()
3859
3860 with self.subTest(tstr=tstr):
3861 t_rt = self.theclass.fromisoformat(tstr)
3862 self.assertEqual(t, t_rt)
3863
3864 def test_fromisoformat_timezone(self):
3865 base_time = self.theclass(12, 30, 45, 217456)
3866
3867 tzoffsets = [
3868 timedelta(hours=5), timedelta(hours=2),
3869 timedelta(hours=6, minutes=27),
3870 timedelta(hours=12, minutes=32, seconds=30),
3871 timedelta(hours=2, minutes=4, seconds=9, microseconds=123456)
3872 ]
3873
3874 tzoffsets += [-1 * td for td in tzoffsets]
3875
3876 tzinfos = [None, timezone.utc,
3877 timezone(timedelta(hours=0))]
3878
3879 tzinfos += [timezone(td) for td in tzoffsets]
3880
3881 for tzi in tzinfos:
3882 t = base_time.replace(tzinfo=tzi)
3883 tstr = t.isoformat()
3884
3885 with self.subTest(tstr=tstr):
3886 t_rt = self.theclass.fromisoformat(tstr)
3887 assert t == t_rt, t_rt
3888
3889 def test_fromisoformat_timespecs(self):
3890 time_bases = [
3891 (8, 17, 45, 123456),
3892 (8, 17, 45, 0)
3893 ]
3894
3895 tzinfos = [None, timezone.utc,
3896 timezone(timedelta(hours=-5)),
3897 timezone(timedelta(hours=2)),
3898 timezone(timedelta(hours=6, minutes=27))]
3899
3900 timespecs = ['hours', 'minutes', 'seconds',
3901 'milliseconds', 'microseconds']
3902
3903 for ip, ts in enumerate(timespecs):
3904 for tzi in tzinfos:
3905 for t_tuple in time_bases:
3906 if ts == 'milliseconds':
3907 new_microseconds = 1000 * (t_tuple[-1] // 1000)
3908 t_tuple = t_tuple[0:-1] + (new_microseconds,)
3909
3910 t = self.theclass(*(t_tuple[0:(1 + ip)]), tzinfo=tzi)
3911 tstr = t.isoformat(timespec=ts)
3912 with self.subTest(tstr=tstr):
3913 t_rt = self.theclass.fromisoformat(tstr)
3914 self.assertEqual(t, t_rt)
3915
3916 def test_fromisoformat_fails(self):
3917 bad_strs = [
3918 '', # Empty string
Paul Ganssle096329f2018-08-23 11:06:20 -04003919 '12\ud80000', # Invalid separator - surrogate char
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003920 '12:', # Ends on a separator
3921 '12:30:', # Ends on a separator
3922 '12:30:15.', # Ends on a separator
3923 '1', # Incomplete hours
3924 '12:3', # Incomplete minutes
3925 '12:30:1', # Incomplete seconds
3926 '1a:30:45.334034', # Invalid character in hours
3927 '12:a0:45.334034', # Invalid character in minutes
3928 '12:30:a5.334034', # Invalid character in seconds
3929 '12:30:45.1234', # Too many digits for milliseconds
3930 '12:30:45.1234567', # Too many digits for microseconds
3931 '12:30:45.123456+24:30', # Invalid time zone offset
3932 '12:30:45.123456-24:30', # Invalid negative offset
3933 '12:30:45', # Uses full-width unicode colons
3934 '12:30:45․123456', # Uses \u2024 in place of decimal point
3935 '12:30:45a', # Extra at tend of basic time
3936 '12:30:45.123a', # Extra at end of millisecond time
3937 '12:30:45.123456a', # Extra at end of microsecond time
3938 '12:30:45.123456+12:00:30a', # Extra at end of full time
3939 ]
3940
3941 for bad_str in bad_strs:
3942 with self.subTest(bad_str=bad_str):
3943 with self.assertRaises(ValueError):
3944 self.theclass.fromisoformat(bad_str)
3945
3946 def test_fromisoformat_fails_typeerror(self):
3947 # Test the fromisoformat fails when passed the wrong type
3948 import io
3949
3950 bad_types = [b'12:30:45', None, io.StringIO('12:30:45')]
3951
3952 for bad_type in bad_types:
3953 with self.assertRaises(TypeError):
3954 self.theclass.fromisoformat(bad_type)
3955
3956 def test_fromisoformat_subclass(self):
3957 class TimeSubclass(self.theclass):
3958 pass
3959
3960 tsc = TimeSubclass(12, 14, 45, 203745, tzinfo=timezone.utc)
3961 tsc_rt = TimeSubclass.fromisoformat(tsc.isoformat())
3962
3963 self.assertEqual(tsc, tsc_rt)
3964 self.assertIsInstance(tsc_rt, TimeSubclass)
3965
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003966 def test_subclass_timetz(self):
3967
3968 class C(self.theclass):
3969 theAnswer = 42
3970
3971 def __new__(cls, *args, **kws):
3972 temp = kws.copy()
3973 extra = temp.pop('extra')
3974 result = self.theclass.__new__(cls, *args, **temp)
3975 result.extra = extra
3976 return result
3977
3978 def newmeth(self, start):
3979 return start + self.hour + self.second
3980
3981 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3982
3983 dt1 = self.theclass(*args)
3984 dt2 = C(*args, **{'extra': 7})
3985
3986 self.assertEqual(dt2.__class__, C)
3987 self.assertEqual(dt2.theAnswer, 42)
3988 self.assertEqual(dt2.extra, 7)
3989 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3990 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
3991
3992
3993# Testing datetime objects with a non-None tzinfo.
3994
3995class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
3996 theclass = datetime
3997
3998 def test_trivial(self):
3999 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
4000 self.assertEqual(dt.year, 1)
4001 self.assertEqual(dt.month, 2)
4002 self.assertEqual(dt.day, 3)
4003 self.assertEqual(dt.hour, 4)
4004 self.assertEqual(dt.minute, 5)
4005 self.assertEqual(dt.second, 6)
4006 self.assertEqual(dt.microsecond, 7)
4007 self.assertEqual(dt.tzinfo, None)
4008
4009 def test_even_more_compare(self):
4010 # The test_compare() and test_more_compare() inherited from TestDate
4011 # and TestDateTime covered non-tzinfo cases.
4012
4013 # Smallest possible after UTC adjustment.
4014 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
4015 # Largest possible after UTC adjustment.
4016 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
4017 tzinfo=FixedOffset(-1439, ""))
4018
4019 # Make sure those compare correctly, and w/o overflow.
4020 self.assertTrue(t1 < t2)
4021 self.assertTrue(t1 != t2)
4022 self.assertTrue(t2 > t1)
4023
4024 self.assertEqual(t1, t1)
4025 self.assertEqual(t2, t2)
4026
4027 # Equal afer adjustment.
4028 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
4029 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
4030 self.assertEqual(t1, t2)
4031
4032 # Change t1 not to subtract a minute, and t1 should be larger.
4033 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
4034 self.assertTrue(t1 > t2)
4035
4036 # Change t1 to subtract 2 minutes, and t1 should be smaller.
4037 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
4038 self.assertTrue(t1 < t2)
4039
4040 # Back to the original t1, but make seconds resolve it.
4041 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
4042 second=1)
4043 self.assertTrue(t1 > t2)
4044
4045 # Likewise, but make microseconds resolve it.
4046 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
4047 microsecond=1)
4048 self.assertTrue(t1 > t2)
4049
Alexander Belopolsky08313822012-06-15 20:19:47 -04004050 # Make t2 naive and it should differ.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004051 t2 = self.theclass.min
Alexander Belopolsky08313822012-06-15 20:19:47 -04004052 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004053 self.assertEqual(t2, t2)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04004054 # and > comparison should fail
4055 with self.assertRaises(TypeError):
4056 t1 > t2
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004057
4058 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
4059 class Naive(tzinfo):
4060 def utcoffset(self, dt): return None
4061 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
Alexander Belopolsky08313822012-06-15 20:19:47 -04004062 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004063 self.assertEqual(t2, t2)
4064
4065 # OTOH, it's OK to compare two of these mixing the two ways of being
4066 # naive.
4067 t1 = self.theclass(5, 6, 7)
4068 self.assertEqual(t1, t2)
4069
4070 # Try a bogus uctoffset.
4071 class Bogus(tzinfo):
4072 def utcoffset(self, dt):
4073 return timedelta(minutes=1440) # out of bounds
4074 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
4075 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
4076 self.assertRaises(ValueError, lambda: t1 == t2)
4077
4078 def test_pickling(self):
4079 # Try one without a tzinfo.
4080 args = 6, 7, 23, 20, 59, 1, 64**2
4081 orig = self.theclass(*args)
4082 for pickler, unpickler, proto in pickle_choices:
4083 green = pickler.dumps(orig, proto)
4084 derived = unpickler.loads(green)
4085 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02004086 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004087
4088 # Try one with a tzinfo.
4089 tinfo = PicklableFixedOffset(-300, 'cookie')
4090 orig = self.theclass(*args, **{'tzinfo': tinfo})
4091 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
4092 for pickler, unpickler, proto in pickle_choices:
4093 green = pickler.dumps(orig, proto)
4094 derived = unpickler.loads(green)
4095 self.assertEqual(orig, derived)
4096 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
4097 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
4098 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02004099 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004100
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02004101 def test_compat_unpickle(self):
4102 tests = [
4103 b'cdatetime\ndatetime\n'
4104 b"(S'\\x07\\xdf\\x0b\\x1b\\x14;\\x01\\x01\\xe2@'\n"
4105 b'ctest.datetimetester\nPicklableFixedOffset\n(tR'
4106 b"(dS'_FixedOffset__offset'\ncdatetime\ntimedelta\n"
4107 b'(I-1\nI68400\nI0\ntRs'
4108 b"S'_FixedOffset__dstoffset'\nNs"
4109 b"S'_FixedOffset__name'\nS'cookie'\nsbtR.",
4110
4111 b'cdatetime\ndatetime\n'
4112 b'(U\n\x07\xdf\x0b\x1b\x14;\x01\x01\xe2@'
4113 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
4114 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
4115 b'(J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00tR'
4116 b'U\x17_FixedOffset__dstoffsetN'
4117 b'U\x12_FixedOffset__nameU\x06cookieubtR.',
4118
4119 b'\x80\x02cdatetime\ndatetime\n'
4120 b'U\n\x07\xdf\x0b\x1b\x14;\x01\x01\xe2@'
4121 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
4122 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
4123 b'J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00\x87R'
4124 b'U\x17_FixedOffset__dstoffsetN'
4125 b'U\x12_FixedOffset__nameU\x06cookieub\x86R.',
4126 ]
4127 args = 2015, 11, 27, 20, 59, 1, 123456
4128 tinfo = PicklableFixedOffset(-300, 'cookie')
4129 expected = self.theclass(*args, **{'tzinfo': tinfo})
4130 for data in tests:
4131 for loads in pickle_loads:
4132 derived = loads(data, encoding='latin1')
4133 self.assertEqual(derived, expected)
4134 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
4135 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
4136 self.assertEqual(derived.tzname(), 'cookie')
4137
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004138 def test_extreme_hashes(self):
4139 # If an attempt is made to hash these via subtracting the offset
4140 # then hashing a datetime object, OverflowError results. The
4141 # Python implementation used to blow up here.
4142 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
4143 hash(t)
4144 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
4145 tzinfo=FixedOffset(-1439, ""))
4146 hash(t)
4147
4148 # OTOH, an OOB offset should blow up.
4149 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
4150 self.assertRaises(ValueError, hash, t)
4151
4152 def test_zones(self):
4153 est = FixedOffset(-300, "EST")
4154 utc = FixedOffset(0, "UTC")
4155 met = FixedOffset(60, "MET")
4156 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
4157 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
4158 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
4159 self.assertEqual(t1.tzinfo, est)
4160 self.assertEqual(t2.tzinfo, utc)
4161 self.assertEqual(t3.tzinfo, met)
4162 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
4163 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
4164 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
4165 self.assertEqual(t1.tzname(), "EST")
4166 self.assertEqual(t2.tzname(), "UTC")
4167 self.assertEqual(t3.tzname(), "MET")
4168 self.assertEqual(hash(t1), hash(t2))
4169 self.assertEqual(hash(t1), hash(t3))
4170 self.assertEqual(hash(t2), hash(t3))
4171 self.assertEqual(t1, t2)
4172 self.assertEqual(t1, t3)
4173 self.assertEqual(t2, t3)
4174 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
4175 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
4176 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
4177 d = 'datetime.datetime(2002, 3, 19, '
4178 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
4179 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
4180 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
4181
4182 def test_combine(self):
4183 met = FixedOffset(60, "MET")
4184 d = date(2002, 3, 4)
4185 tz = time(18, 45, 3, 1234, tzinfo=met)
4186 dt = datetime.combine(d, tz)
4187 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
4188 tzinfo=met))
4189
4190 def test_extract(self):
4191 met = FixedOffset(60, "MET")
4192 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
4193 self.assertEqual(dt.date(), date(2002, 3, 4))
4194 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
4195 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
4196
4197 def test_tz_aware_arithmetic(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004198 now = self.theclass.now()
4199 tz55 = FixedOffset(-330, "west 5:30")
4200 timeaware = now.time().replace(tzinfo=tz55)
4201 nowaware = self.theclass.combine(now.date(), timeaware)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004202 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004203 self.assertEqual(nowaware.timetz(), timeaware)
4204
4205 # Can't mix aware and non-aware.
4206 self.assertRaises(TypeError, lambda: now - nowaware)
4207 self.assertRaises(TypeError, lambda: nowaware - now)
4208
4209 # And adding datetime's doesn't make sense, aware or not.
4210 self.assertRaises(TypeError, lambda: now + nowaware)
4211 self.assertRaises(TypeError, lambda: nowaware + now)
4212 self.assertRaises(TypeError, lambda: nowaware + nowaware)
4213
4214 # Subtracting should yield 0.
4215 self.assertEqual(now - now, timedelta(0))
4216 self.assertEqual(nowaware - nowaware, timedelta(0))
4217
4218 # Adding a delta should preserve tzinfo.
4219 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
4220 nowawareplus = nowaware + delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004221 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004222 nowawareplus2 = delta + nowaware
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004223 self.assertIs(nowawareplus2.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004224 self.assertEqual(nowawareplus, nowawareplus2)
4225
4226 # that - delta should be what we started with, and that - what we
4227 # started with should be delta.
4228 diff = nowawareplus - delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004229 self.assertIs(diff.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004230 self.assertEqual(nowaware, diff)
4231 self.assertRaises(TypeError, lambda: delta - nowawareplus)
4232 self.assertEqual(nowawareplus - nowaware, delta)
4233
4234 # Make up a random timezone.
4235 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
4236 # Attach it to nowawareplus.
4237 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004238 self.assertIs(nowawareplus.tzinfo, tzr)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004239 # Make sure the difference takes the timezone adjustments into account.
4240 got = nowaware - nowawareplus
4241 # Expected: (nowaware base - nowaware offset) -
4242 # (nowawareplus base - nowawareplus offset) =
4243 # (nowaware base - nowawareplus base) +
4244 # (nowawareplus offset - nowaware offset) =
4245 # -delta + nowawareplus offset - nowaware offset
4246 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
4247 self.assertEqual(got, expected)
4248
4249 # Try max possible difference.
4250 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
4251 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
4252 tzinfo=FixedOffset(-1439, "max"))
4253 maxdiff = max - min
4254 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
4255 timedelta(minutes=2*1439))
4256 # Different tzinfo, but the same offset
4257 tza = timezone(HOUR, 'A')
4258 tzb = timezone(HOUR, 'B')
4259 delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
4260 self.assertEqual(delta, self.theclass.min - self.theclass.max)
4261
4262 def test_tzinfo_now(self):
4263 meth = self.theclass.now
4264 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4265 base = meth()
4266 # Try with and without naming the keyword.
4267 off42 = FixedOffset(42, "42")
4268 another = meth(off42)
4269 again = meth(tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004270 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004271 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
4272 # Bad argument with and w/o naming the keyword.
4273 self.assertRaises(TypeError, meth, 16)
4274 self.assertRaises(TypeError, meth, tzinfo=16)
4275 # Bad keyword name.
4276 self.assertRaises(TypeError, meth, tinfo=off42)
4277 # Too many args.
4278 self.assertRaises(TypeError, meth, off42, off42)
4279
4280 # We don't know which time zone we're in, and don't have a tzinfo
4281 # class to represent it, so seeing whether a tz argument actually
4282 # does a conversion is tricky.
4283 utc = FixedOffset(0, "utc", 0)
4284 for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
4285 timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
4286 for dummy in range(3):
4287 now = datetime.now(weirdtz)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004288 self.assertIs(now.tzinfo, weirdtz)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004289 utcnow = datetime.utcnow().replace(tzinfo=utc)
4290 now2 = utcnow.astimezone(weirdtz)
4291 if abs(now - now2) < timedelta(seconds=30):
4292 break
4293 # Else the code is broken, or more than 30 seconds passed between
4294 # calls; assuming the latter, just try again.
4295 else:
4296 # Three strikes and we're out.
4297 self.fail("utcnow(), now(tz), or astimezone() may be broken")
4298
4299 def test_tzinfo_fromtimestamp(self):
4300 import time
4301 meth = self.theclass.fromtimestamp
4302 ts = time.time()
4303 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4304 base = meth(ts)
4305 # Try with and without naming the keyword.
4306 off42 = FixedOffset(42, "42")
4307 another = meth(ts, off42)
4308 again = meth(ts, tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004309 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004310 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
4311 # Bad argument with and w/o naming the keyword.
4312 self.assertRaises(TypeError, meth, ts, 16)
4313 self.assertRaises(TypeError, meth, ts, tzinfo=16)
4314 # Bad keyword name.
4315 self.assertRaises(TypeError, meth, ts, tinfo=off42)
4316 # Too many args.
4317 self.assertRaises(TypeError, meth, ts, off42, off42)
4318 # Too few args.
4319 self.assertRaises(TypeError, meth)
4320
4321 # Try to make sure tz= actually does some conversion.
4322 timestamp = 1000000000
4323 utcdatetime = datetime.utcfromtimestamp(timestamp)
4324 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
4325 # But on some flavor of Mac, it's nowhere near that. So we can't have
4326 # any idea here what time that actually is, we can only test that
4327 # relative changes match.
4328 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
4329 tz = FixedOffset(utcoffset, "tz", 0)
4330 expected = utcdatetime + utcoffset
4331 got = datetime.fromtimestamp(timestamp, tz)
4332 self.assertEqual(expected, got.replace(tzinfo=None))
4333
4334 def test_tzinfo_utcnow(self):
4335 meth = self.theclass.utcnow
4336 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4337 base = meth()
4338 # Try with and without naming the keyword; for whatever reason,
4339 # utcnow() doesn't accept a tzinfo argument.
4340 off42 = FixedOffset(42, "42")
4341 self.assertRaises(TypeError, meth, off42)
4342 self.assertRaises(TypeError, meth, tzinfo=off42)
4343
4344 def test_tzinfo_utcfromtimestamp(self):
4345 import time
4346 meth = self.theclass.utcfromtimestamp
4347 ts = time.time()
4348 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4349 base = meth(ts)
4350 # Try with and without naming the keyword; for whatever reason,
4351 # utcfromtimestamp() doesn't accept a tzinfo argument.
4352 off42 = FixedOffset(42, "42")
4353 self.assertRaises(TypeError, meth, ts, off42)
4354 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
4355
4356 def test_tzinfo_timetuple(self):
4357 # TestDateTime tested most of this. datetime adds a twist to the
4358 # DST flag.
4359 class DST(tzinfo):
4360 def __init__(self, dstvalue):
4361 if isinstance(dstvalue, int):
4362 dstvalue = timedelta(minutes=dstvalue)
4363 self.dstvalue = dstvalue
4364 def dst(self, dt):
4365 return self.dstvalue
4366
4367 cls = self.theclass
4368 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
4369 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
4370 t = d.timetuple()
4371 self.assertEqual(1, t.tm_year)
4372 self.assertEqual(1, t.tm_mon)
4373 self.assertEqual(1, t.tm_mday)
4374 self.assertEqual(10, t.tm_hour)
4375 self.assertEqual(20, t.tm_min)
4376 self.assertEqual(30, t.tm_sec)
4377 self.assertEqual(0, t.tm_wday)
4378 self.assertEqual(1, t.tm_yday)
4379 self.assertEqual(flag, t.tm_isdst)
4380
4381 # dst() returns wrong type.
4382 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
4383
4384 # dst() at the edge.
4385 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
4386 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
4387
4388 # dst() out of range.
4389 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
4390 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
4391
4392 def test_utctimetuple(self):
4393 class DST(tzinfo):
4394 def __init__(self, dstvalue=0):
4395 if isinstance(dstvalue, int):
4396 dstvalue = timedelta(minutes=dstvalue)
4397 self.dstvalue = dstvalue
4398 def dst(self, dt):
4399 return self.dstvalue
4400
4401 cls = self.theclass
4402 # This can't work: DST didn't implement utcoffset.
4403 self.assertRaises(NotImplementedError,
4404 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
4405
4406 class UOFS(DST):
4407 def __init__(self, uofs, dofs=None):
4408 DST.__init__(self, dofs)
4409 self.uofs = timedelta(minutes=uofs)
4410 def utcoffset(self, dt):
4411 return self.uofs
4412
4413 for dstvalue in -33, 33, 0, None:
4414 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
4415 t = d.utctimetuple()
4416 self.assertEqual(d.year, t.tm_year)
4417 self.assertEqual(d.month, t.tm_mon)
4418 self.assertEqual(d.day, t.tm_mday)
4419 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
4420 self.assertEqual(13, t.tm_min)
4421 self.assertEqual(d.second, t.tm_sec)
4422 self.assertEqual(d.weekday(), t.tm_wday)
4423 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
4424 t.tm_yday)
4425 # Ensure tm_isdst is 0 regardless of what dst() says: DST
4426 # is never in effect for a UTC time.
4427 self.assertEqual(0, t.tm_isdst)
4428
4429 # For naive datetime, utctimetuple == timetuple except for isdst
4430 d = cls(1, 2, 3, 10, 20, 30, 40)
4431 t = d.utctimetuple()
4432 self.assertEqual(t[:-1], d.timetuple()[:-1])
4433 self.assertEqual(0, t.tm_isdst)
4434 # Same if utcoffset is None
4435 class NOFS(DST):
4436 def utcoffset(self, dt):
4437 return None
4438 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
4439 t = d.utctimetuple()
4440 self.assertEqual(t[:-1], d.timetuple()[:-1])
4441 self.assertEqual(0, t.tm_isdst)
4442 # Check that bad tzinfo is detected
4443 class BOFS(DST):
4444 def utcoffset(self, dt):
4445 return "EST"
4446 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
4447 self.assertRaises(TypeError, d.utctimetuple)
4448
4449 # Check that utctimetuple() is the same as
4450 # astimezone(utc).timetuple()
4451 d = cls(2010, 11, 13, 14, 15, 16, 171819)
4452 for tz in [timezone.min, timezone.utc, timezone.max]:
4453 dtz = d.replace(tzinfo=tz)
4454 self.assertEqual(dtz.utctimetuple()[:-1],
4455 dtz.astimezone(timezone.utc).timetuple()[:-1])
4456 # At the edges, UTC adjustment can produce years out-of-range
4457 # for a datetime object. Ensure that an OverflowError is
4458 # raised.
4459 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
4460 # That goes back 1 minute less than a full day.
4461 self.assertRaises(OverflowError, tiny.utctimetuple)
4462
4463 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
4464 # That goes forward 1 minute less than a full day.
4465 self.assertRaises(OverflowError, huge.utctimetuple)
4466 # More overflow cases
4467 tiny = cls.min.replace(tzinfo=timezone(MINUTE))
4468 self.assertRaises(OverflowError, tiny.utctimetuple)
4469 huge = cls.max.replace(tzinfo=timezone(-MINUTE))
4470 self.assertRaises(OverflowError, huge.utctimetuple)
4471
4472 def test_tzinfo_isoformat(self):
4473 zero = FixedOffset(0, "+00:00")
4474 plus = FixedOffset(220, "+03:40")
4475 minus = FixedOffset(-231, "-03:51")
4476 unknown = FixedOffset(None, "")
4477
4478 cls = self.theclass
4479 datestr = '0001-02-03'
4480 for ofs in None, zero, plus, minus, unknown:
4481 for us in 0, 987001:
4482 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
4483 timestr = '04:05:59' + (us and '.987001' or '')
4484 ofsstr = ofs is not None and d.tzname() or ''
4485 tailstr = timestr + ofsstr
4486 iso = d.isoformat()
4487 self.assertEqual(iso, datestr + 'T' + tailstr)
4488 self.assertEqual(iso, d.isoformat('T'))
4489 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
4490 self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
4491 self.assertEqual(str(d), datestr + ' ' + tailstr)
4492
4493 def test_replace(self):
4494 cls = self.theclass
4495 z100 = FixedOffset(100, "+100")
4496 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
4497 args = [1, 2, 3, 4, 5, 6, 7, z100]
4498 base = cls(*args)
4499 self.assertEqual(base, base.replace())
4500
4501 i = 0
4502 for name, newval in (("year", 2),
4503 ("month", 3),
4504 ("day", 4),
4505 ("hour", 5),
4506 ("minute", 6),
4507 ("second", 7),
4508 ("microsecond", 8),
4509 ("tzinfo", zm200)):
4510 newargs = args[:]
4511 newargs[i] = newval
4512 expected = cls(*newargs)
4513 got = base.replace(**{name: newval})
4514 self.assertEqual(expected, got)
4515 i += 1
4516
4517 # Ensure we can get rid of a tzinfo.
4518 self.assertEqual(base.tzname(), "+100")
4519 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004520 self.assertIsNone(base2.tzinfo)
4521 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004522
4523 # Ensure we can add one.
4524 base3 = base2.replace(tzinfo=z100)
4525 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004526 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004527
4528 # Out of bounds.
4529 base = cls(2000, 2, 29)
4530 self.assertRaises(ValueError, base.replace, year=2001)
4531
4532 def test_more_astimezone(self):
4533 # The inherited test_astimezone covered some trivial and error cases.
4534 fnone = FixedOffset(None, "None")
4535 f44m = FixedOffset(44, "44")
4536 fm5h = FixedOffset(-timedelta(hours=5), "m300")
4537
4538 dt = self.theclass.now(tz=f44m)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004539 self.assertIs(dt.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004540 # Replacing with degenerate tzinfo raises an exception.
4541 self.assertRaises(ValueError, dt.astimezone, fnone)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004542 # Replacing with same tzinfo makes no change.
4543 x = dt.astimezone(dt.tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004544 self.assertIs(x.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004545 self.assertEqual(x.date(), dt.date())
4546 self.assertEqual(x.time(), dt.time())
4547
4548 # Replacing with different tzinfo does adjust.
4549 got = dt.astimezone(fm5h)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004550 self.assertIs(got.tzinfo, fm5h)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004551 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
4552 expected = dt - dt.utcoffset() # in effect, convert to UTC
4553 expected += fm5h.utcoffset(dt) # and from there to local time
4554 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
4555 self.assertEqual(got.date(), expected.date())
4556 self.assertEqual(got.time(), expected.time())
4557 self.assertEqual(got.timetz(), expected.timetz())
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004558 self.assertIs(got.tzinfo, expected.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004559 self.assertEqual(got, expected)
4560
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004561 @support.run_with_tz('UTC')
4562 def test_astimezone_default_utc(self):
4563 dt = self.theclass.now(timezone.utc)
4564 self.assertEqual(dt.astimezone(None), dt)
4565 self.assertEqual(dt.astimezone(), dt)
4566
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004567 # Note that offset in TZ variable has the opposite sign to that
4568 # produced by %z directive.
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004569 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4570 def test_astimezone_default_eastern(self):
4571 dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
4572 local = dt.astimezone()
4573 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004574 self.assertEqual(local.strftime("%z %Z"), "-0500 EST")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004575 dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc)
4576 local = dt.astimezone()
4577 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004578 self.assertEqual(local.strftime("%z %Z"), "-0400 EDT")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004579
Alexander Belopolsky1dcf4f92016-03-25 15:42:59 -04004580 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4581 def test_astimezone_default_near_fold(self):
4582 # Issue #26616.
4583 u = datetime(2015, 11, 1, 5, tzinfo=timezone.utc)
4584 t = u.astimezone()
4585 s = t.astimezone()
4586 self.assertEqual(t.tzinfo, s.tzinfo)
4587
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004588 def test_aware_subtract(self):
4589 cls = self.theclass
4590
4591 # Ensure that utcoffset() is ignored when the operands have the
4592 # same tzinfo member.
4593 class OperandDependentOffset(tzinfo):
4594 def utcoffset(self, t):
4595 if t.minute < 10:
4596 # d0 and d1 equal after adjustment
4597 return timedelta(minutes=t.minute)
4598 else:
4599 # d2 off in the weeds
4600 return timedelta(minutes=59)
4601
4602 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
4603 d0 = base.replace(minute=3)
4604 d1 = base.replace(minute=9)
4605 d2 = base.replace(minute=11)
4606 for x in d0, d1, d2:
4607 for y in d0, d1, d2:
4608 got = x - y
4609 expected = timedelta(minutes=x.minute - y.minute)
4610 self.assertEqual(got, expected)
4611
4612 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
4613 # ignored.
4614 base = cls(8, 9, 10, 11, 12, 13, 14)
4615 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
4616 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
4617 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
4618 for x in d0, d1, d2:
4619 for y in d0, d1, d2:
4620 got = x - y
4621 if (x is d0 or x is d1) and (y is d0 or y is d1):
4622 expected = timedelta(0)
4623 elif x is y is d2:
4624 expected = timedelta(0)
4625 elif x is d2:
4626 expected = timedelta(minutes=(11-59)-0)
4627 else:
4628 assert y is d2
4629 expected = timedelta(minutes=0-(11-59))
4630 self.assertEqual(got, expected)
4631
4632 def test_mixed_compare(self):
4633 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
4634 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
4635 self.assertEqual(t1, t2)
4636 t2 = t2.replace(tzinfo=None)
4637 self.assertEqual(t1, t2)
4638 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
4639 self.assertEqual(t1, t2)
4640 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04004641 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004642
4643 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
4644 class Varies(tzinfo):
4645 def __init__(self):
4646 self.offset = timedelta(minutes=22)
4647 def utcoffset(self, t):
4648 self.offset += timedelta(minutes=1)
4649 return self.offset
4650
4651 v = Varies()
4652 t1 = t2.replace(tzinfo=v)
4653 t2 = t2.replace(tzinfo=v)
4654 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
4655 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
4656 self.assertEqual(t1, t2)
4657
4658 # But if they're not identical, it isn't ignored.
4659 t2 = t2.replace(tzinfo=Varies())
4660 self.assertTrue(t1 < t2) # t1's offset counter still going up
4661
4662 def test_subclass_datetimetz(self):
4663
4664 class C(self.theclass):
4665 theAnswer = 42
4666
4667 def __new__(cls, *args, **kws):
4668 temp = kws.copy()
4669 extra = temp.pop('extra')
4670 result = self.theclass.__new__(cls, *args, **temp)
4671 result.extra = extra
4672 return result
4673
4674 def newmeth(self, start):
4675 return start + self.hour + self.year
4676
4677 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
4678
4679 dt1 = self.theclass(*args)
4680 dt2 = C(*args, **{'extra': 7})
4681
4682 self.assertEqual(dt2.__class__, C)
4683 self.assertEqual(dt2.theAnswer, 42)
4684 self.assertEqual(dt2.extra, 7)
4685 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
4686 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
4687
4688# Pain to set up DST-aware tzinfo classes.
4689
4690def first_sunday_on_or_after(dt):
4691 days_to_go = 6 - dt.weekday()
4692 if days_to_go:
4693 dt += timedelta(days_to_go)
4694 return dt
4695
4696ZERO = timedelta(0)
4697MINUTE = timedelta(minutes=1)
4698HOUR = timedelta(hours=1)
4699DAY = timedelta(days=1)
4700# In the US, DST starts at 2am (standard time) on the first Sunday in April.
4701DSTSTART = datetime(1, 4, 1, 2)
4702# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
4703# which is the first Sunday on or after Oct 25. Because we view 1:MM as
4704# being standard time on that day, there is no spelling in local time of
4705# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
4706DSTEND = datetime(1, 10, 25, 1)
4707
4708class USTimeZone(tzinfo):
4709
4710 def __init__(self, hours, reprname, stdname, dstname):
4711 self.stdoffset = timedelta(hours=hours)
4712 self.reprname = reprname
4713 self.stdname = stdname
4714 self.dstname = dstname
4715
4716 def __repr__(self):
4717 return self.reprname
4718
4719 def tzname(self, dt):
4720 if self.dst(dt):
4721 return self.dstname
4722 else:
4723 return self.stdname
4724
4725 def utcoffset(self, dt):
4726 return self.stdoffset + self.dst(dt)
4727
4728 def dst(self, dt):
4729 if dt is None or dt.tzinfo is None:
4730 # An exception instead may be sensible here, in one or more of
4731 # the cases.
4732 return ZERO
4733 assert dt.tzinfo is self
4734
4735 # Find first Sunday in April.
4736 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
4737 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
4738
4739 # Find last Sunday in October.
4740 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
4741 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
4742
4743 # Can't compare naive to aware objects, so strip the timezone from
4744 # dt first.
4745 if start <= dt.replace(tzinfo=None) < end:
4746 return HOUR
4747 else:
4748 return ZERO
4749
4750Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
4751Central = USTimeZone(-6, "Central", "CST", "CDT")
4752Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
4753Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
4754utc_real = FixedOffset(0, "UTC", 0)
4755# For better test coverage, we want another flavor of UTC that's west of
4756# the Eastern and Pacific timezones.
4757utc_fake = FixedOffset(-12*60, "UTCfake", 0)
4758
4759class TestTimezoneConversions(unittest.TestCase):
4760 # The DST switch times for 2002, in std time.
4761 dston = datetime(2002, 4, 7, 2)
4762 dstoff = datetime(2002, 10, 27, 1)
4763
4764 theclass = datetime
4765
4766 # Check a time that's inside DST.
4767 def checkinside(self, dt, tz, utc, dston, dstoff):
4768 self.assertEqual(dt.dst(), HOUR)
4769
4770 # Conversion to our own timezone is always an identity.
4771 self.assertEqual(dt.astimezone(tz), dt)
4772
4773 asutc = dt.astimezone(utc)
4774 there_and_back = asutc.astimezone(tz)
4775
4776 # Conversion to UTC and back isn't always an identity here,
4777 # because there are redundant spellings (in local time) of
4778 # UTC time when DST begins: the clock jumps from 1:59:59
4779 # to 3:00:00, and a local time of 2:MM:SS doesn't really
4780 # make sense then. The classes above treat 2:MM:SS as
4781 # daylight time then (it's "after 2am"), really an alias
4782 # for 1:MM:SS standard time. The latter form is what
4783 # conversion back from UTC produces.
4784 if dt.date() == dston.date() and dt.hour == 2:
4785 # We're in the redundant hour, and coming back from
4786 # UTC gives the 1:MM:SS standard-time spelling.
4787 self.assertEqual(there_and_back + HOUR, dt)
4788 # Although during was considered to be in daylight
4789 # time, there_and_back is not.
4790 self.assertEqual(there_and_back.dst(), ZERO)
4791 # They're the same times in UTC.
4792 self.assertEqual(there_and_back.astimezone(utc),
4793 dt.astimezone(utc))
4794 else:
4795 # We're not in the redundant hour.
4796 self.assertEqual(dt, there_and_back)
4797
4798 # Because we have a redundant spelling when DST begins, there is
Ezio Melotti3b3499b2011-03-16 11:35:38 +02004799 # (unfortunately) an hour when DST ends that can't be spelled at all in
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004800 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
4801 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
4802 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
4803 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
4804 # expressed in local time. Nevertheless, we want conversion back
4805 # from UTC to mimic the local clock's "repeat an hour" behavior.
4806 nexthour_utc = asutc + HOUR
4807 nexthour_tz = nexthour_utc.astimezone(tz)
4808 if dt.date() == dstoff.date() and dt.hour == 0:
4809 # We're in the hour before the last DST hour. The last DST hour
4810 # is ineffable. We want the conversion back to repeat 1:MM.
4811 self.assertEqual(nexthour_tz, dt.replace(hour=1))
4812 nexthour_utc += HOUR
4813 nexthour_tz = nexthour_utc.astimezone(tz)
4814 self.assertEqual(nexthour_tz, dt.replace(hour=1))
4815 else:
4816 self.assertEqual(nexthour_tz - dt, HOUR)
4817
4818 # Check a time that's outside DST.
4819 def checkoutside(self, dt, tz, utc):
4820 self.assertEqual(dt.dst(), ZERO)
4821
4822 # Conversion to our own timezone is always an identity.
4823 self.assertEqual(dt.astimezone(tz), dt)
4824
4825 # Converting to UTC and back is an identity too.
4826 asutc = dt.astimezone(utc)
4827 there_and_back = asutc.astimezone(tz)
4828 self.assertEqual(dt, there_and_back)
4829
4830 def convert_between_tz_and_utc(self, tz, utc):
4831 dston = self.dston.replace(tzinfo=tz)
4832 # Because 1:MM on the day DST ends is taken as being standard time,
4833 # there is no spelling in tz for the last hour of daylight time.
4834 # For purposes of the test, the last hour of DST is 0:MM, which is
4835 # taken as being daylight time (and 1:MM is taken as being standard
4836 # time).
4837 dstoff = self.dstoff.replace(tzinfo=tz)
4838 for delta in (timedelta(weeks=13),
4839 DAY,
4840 HOUR,
4841 timedelta(minutes=1),
4842 timedelta(microseconds=1)):
4843
4844 self.checkinside(dston, tz, utc, dston, dstoff)
4845 for during in dston + delta, dstoff - delta:
4846 self.checkinside(during, tz, utc, dston, dstoff)
4847
4848 self.checkoutside(dstoff, tz, utc)
4849 for outside in dston - delta, dstoff + delta:
4850 self.checkoutside(outside, tz, utc)
4851
4852 def test_easy(self):
4853 # Despite the name of this test, the endcases are excruciating.
4854 self.convert_between_tz_and_utc(Eastern, utc_real)
4855 self.convert_between_tz_and_utc(Pacific, utc_real)
4856 self.convert_between_tz_and_utc(Eastern, utc_fake)
4857 self.convert_between_tz_and_utc(Pacific, utc_fake)
4858 # The next is really dancing near the edge. It works because
4859 # Pacific and Eastern are far enough apart that their "problem
4860 # hours" don't overlap.
4861 self.convert_between_tz_and_utc(Eastern, Pacific)
4862 self.convert_between_tz_and_utc(Pacific, Eastern)
4863 # OTOH, these fail! Don't enable them. The difficulty is that
4864 # the edge case tests assume that every hour is representable in
4865 # the "utc" class. This is always true for a fixed-offset tzinfo
4866 # class (lke utc_real and utc_fake), but not for Eastern or Central.
4867 # For these adjacent DST-aware time zones, the range of time offsets
4868 # tested ends up creating hours in the one that aren't representable
4869 # in the other. For the same reason, we would see failures in the
4870 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
4871 # offset deltas in convert_between_tz_and_utc().
4872 #
4873 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
4874 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
4875
4876 def test_tricky(self):
4877 # 22:00 on day before daylight starts.
4878 fourback = self.dston - timedelta(hours=4)
4879 ninewest = FixedOffset(-9*60, "-0900", 0)
4880 fourback = fourback.replace(tzinfo=ninewest)
4881 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
4882 # 2", we should get the 3 spelling.
4883 # If we plug 22:00 the day before into Eastern, it "looks like std
4884 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
4885 # to 22:00 lands on 2:00, which makes no sense in local time (the
4886 # local clock jumps from 1 to 3). The point here is to make sure we
4887 # get the 3 spelling.
4888 expected = self.dston.replace(hour=3)
4889 got = fourback.astimezone(Eastern).replace(tzinfo=None)
4890 self.assertEqual(expected, got)
4891
4892 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
4893 # case we want the 1:00 spelling.
4894 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
4895 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
4896 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
4897 # spelling.
4898 expected = self.dston.replace(hour=1)
4899 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
4900 self.assertEqual(expected, got)
4901
4902 # Now on the day DST ends, we want "repeat an hour" behavior.
4903 # UTC 4:MM 5:MM 6:MM 7:MM checking these
4904 # EST 23:MM 0:MM 1:MM 2:MM
4905 # EDT 0:MM 1:MM 2:MM 3:MM
4906 # wall 0:MM 1:MM 1:MM 2:MM against these
4907 for utc in utc_real, utc_fake:
4908 for tz in Eastern, Pacific:
4909 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
4910 # Convert that to UTC.
4911 first_std_hour -= tz.utcoffset(None)
4912 # Adjust for possibly fake UTC.
4913 asutc = first_std_hour + utc.utcoffset(None)
4914 # First UTC hour to convert; this is 4:00 when utc=utc_real &
4915 # tz=Eastern.
4916 asutcbase = asutc.replace(tzinfo=utc)
4917 for tzhour in (0, 1, 1, 2):
4918 expectedbase = self.dstoff.replace(hour=tzhour)
4919 for minute in 0, 30, 59:
4920 expected = expectedbase.replace(minute=minute)
4921 asutc = asutcbase.replace(minute=minute)
4922 astz = asutc.astimezone(tz)
4923 self.assertEqual(astz.replace(tzinfo=None), expected)
4924 asutcbase += HOUR
4925
4926
4927 def test_bogus_dst(self):
4928 class ok(tzinfo):
4929 def utcoffset(self, dt): return HOUR
4930 def dst(self, dt): return HOUR
4931
4932 now = self.theclass.now().replace(tzinfo=utc_real)
4933 # Doesn't blow up.
4934 now.astimezone(ok())
4935
4936 # Does blow up.
4937 class notok(ok):
4938 def dst(self, dt): return None
4939 self.assertRaises(ValueError, now.astimezone, notok())
4940
4941 # Sometimes blow up. In the following, tzinfo.dst()
4942 # implementation may return None or not None depending on
4943 # whether DST is assumed to be in effect. In this situation,
4944 # a ValueError should be raised by astimezone().
4945 class tricky_notok(ok):
4946 def dst(self, dt):
4947 if dt.year == 2000:
4948 return None
4949 else:
4950 return 10*HOUR
4951 dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
4952 self.assertRaises(ValueError, dt.astimezone, tricky_notok())
4953
4954 def test_fromutc(self):
4955 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
4956 now = datetime.utcnow().replace(tzinfo=utc_real)
4957 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
4958 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
4959 enow = Eastern.fromutc(now) # doesn't blow up
4960 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
4961 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
4962 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
4963
4964 # Always converts UTC to standard time.
4965 class FauxUSTimeZone(USTimeZone):
4966 def fromutc(self, dt):
4967 return dt + self.stdoffset
4968 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
4969
4970 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
4971 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
4972 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
4973
4974 # Check around DST start.
4975 start = self.dston.replace(hour=4, tzinfo=Eastern)
4976 fstart = start.replace(tzinfo=FEastern)
4977 for wall in 23, 0, 1, 3, 4, 5:
4978 expected = start.replace(hour=wall)
4979 if wall == 23:
4980 expected -= timedelta(days=1)
4981 got = Eastern.fromutc(start)
4982 self.assertEqual(expected, got)
4983
4984 expected = fstart + FEastern.stdoffset
4985 got = FEastern.fromutc(fstart)
4986 self.assertEqual(expected, got)
4987
4988 # Ensure astimezone() calls fromutc() too.
4989 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
4990 self.assertEqual(expected, got)
4991
4992 start += HOUR
4993 fstart += HOUR
4994
4995 # Check around DST end.
4996 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
4997 fstart = start.replace(tzinfo=FEastern)
4998 for wall in 0, 1, 1, 2, 3, 4:
4999 expected = start.replace(hour=wall)
5000 got = Eastern.fromutc(start)
5001 self.assertEqual(expected, got)
5002
5003 expected = fstart + FEastern.stdoffset
5004 got = FEastern.fromutc(fstart)
5005 self.assertEqual(expected, got)
5006
5007 # Ensure astimezone() calls fromutc() too.
5008 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
5009 self.assertEqual(expected, got)
5010
5011 start += HOUR
5012 fstart += HOUR
5013
5014
5015#############################################################################
5016# oddballs
5017
5018class Oddballs(unittest.TestCase):
5019
5020 def test_bug_1028306(self):
5021 # Trying to compare a date to a datetime should act like a mixed-
5022 # type comparison, despite that datetime is a subclass of date.
5023 as_date = date.today()
5024 as_datetime = datetime.combine(as_date, time())
5025 self.assertTrue(as_date != as_datetime)
5026 self.assertTrue(as_datetime != as_date)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02005027 self.assertFalse(as_date == as_datetime)
5028 self.assertFalse(as_datetime == as_date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00005029 self.assertRaises(TypeError, lambda: as_date < as_datetime)
5030 self.assertRaises(TypeError, lambda: as_datetime < as_date)
5031 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
5032 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
5033 self.assertRaises(TypeError, lambda: as_date > as_datetime)
5034 self.assertRaises(TypeError, lambda: as_datetime > as_date)
5035 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
5036 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
5037
Raymond Hettinger15f44ab2016-08-30 10:47:49 -07005038 # Nevertheless, comparison should work with the base-class (date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00005039 # projection if use of a date method is forced.
5040 self.assertEqual(as_date.__eq__(as_datetime), True)
5041 different_day = (as_date.day + 1) % 20 + 1
5042 as_different = as_datetime.replace(day= different_day)
5043 self.assertEqual(as_date.__eq__(as_different), False)
5044
5045 # And date should compare with other subclasses of date. If a
5046 # subclass wants to stop this, it's up to the subclass to do so.
5047 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
5048 self.assertEqual(as_date, date_sc)
5049 self.assertEqual(date_sc, as_date)
5050
5051 # Ditto for datetimes.
5052 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
5053 as_date.day, 0, 0, 0)
5054 self.assertEqual(as_datetime, datetime_sc)
5055 self.assertEqual(datetime_sc, as_datetime)
5056
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04005057 def test_extra_attributes(self):
5058 for x in [date.today(),
5059 time(),
5060 datetime.utcnow(),
5061 timedelta(),
5062 tzinfo(),
5063 timezone(timedelta())]:
5064 with self.assertRaises(AttributeError):
5065 x.abc = 1
5066
5067 def test_check_arg_types(self):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04005068 class Number:
5069 def __init__(self, value):
5070 self.value = value
5071 def __int__(self):
5072 return self.value
5073
5074 for xx in [decimal.Decimal(10),
5075 decimal.Decimal('10.9'),
5076 Number(10)]:
Serhiy Storchaka6a44f6e2019-02-25 17:57:58 +02005077 with self.assertWarns(DeprecationWarning):
5078 self.assertEqual(datetime(10, 10, 10, 10, 10, 10, 10),
5079 datetime(xx, xx, xx, xx, xx, xx, xx))
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04005080
5081 with self.assertRaisesRegex(TypeError, '^an integer is required '
R David Murray44b548d2016-09-08 13:59:53 -04005082 r'\(got type str\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04005083 datetime(10, 10, '10')
5084
5085 f10 = Number(10.9)
5086 with self.assertRaisesRegex(TypeError, '^__int__ returned non-int '
Serhiy Storchaka6a44f6e2019-02-25 17:57:58 +02005087 r'\(type float\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04005088 datetime(10, 10, f10)
5089
5090 class Float(float):
5091 pass
5092 s10 = Float(10.9)
5093 with self.assertRaisesRegex(TypeError, '^integer argument expected, '
5094 'got float$'):
5095 datetime(10, 10, s10)
5096
5097 with self.assertRaises(TypeError):
5098 datetime(10., 10, 10)
5099 with self.assertRaises(TypeError):
5100 datetime(10, 10., 10)
5101 with self.assertRaises(TypeError):
5102 datetime(10, 10, 10.)
5103 with self.assertRaises(TypeError):
5104 datetime(10, 10, 10, 10.)
5105 with self.assertRaises(TypeError):
5106 datetime(10, 10, 10, 10, 10.)
5107 with self.assertRaises(TypeError):
5108 datetime(10, 10, 10, 10, 10, 10.)
5109 with self.assertRaises(TypeError):
5110 datetime(10, 10, 10, 10, 10, 10, 10.)
5111
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005112#############################################################################
5113# Local Time Disambiguation
5114
5115# An experimental reimplementation of fromutc that respects the "fold" flag.
5116
5117class tzinfo2(tzinfo):
5118
5119 def fromutc(self, dt):
5120 "datetime in UTC -> datetime in local time."
5121
5122 if not isinstance(dt, datetime):
5123 raise TypeError("fromutc() requires a datetime argument")
5124 if dt.tzinfo is not self:
5125 raise ValueError("dt.tzinfo is not self")
5126 # Returned value satisfies
5127 # dt + ldt.utcoffset() = ldt
5128 off0 = dt.replace(fold=0).utcoffset()
5129 off1 = dt.replace(fold=1).utcoffset()
5130 if off0 is None or off1 is None or dt.dst() is None:
5131 raise ValueError
5132 if off0 == off1:
5133 ldt = dt + off0
5134 off1 = ldt.utcoffset()
5135 if off0 == off1:
5136 return ldt
5137 # Now, we discovered both possible offsets, so
5138 # we can just try four possible solutions:
5139 for off in [off0, off1]:
5140 ldt = dt + off
5141 if ldt.utcoffset() == off:
5142 return ldt
5143 ldt = ldt.replace(fold=1)
5144 if ldt.utcoffset() == off:
5145 return ldt
5146
5147 raise ValueError("No suitable local time found")
5148
5149# Reimplementing simplified US timezones to respect the "fold" flag:
5150
5151class USTimeZone2(tzinfo2):
5152
5153 def __init__(self, hours, reprname, stdname, dstname):
5154 self.stdoffset = timedelta(hours=hours)
5155 self.reprname = reprname
5156 self.stdname = stdname
5157 self.dstname = dstname
5158
5159 def __repr__(self):
5160 return self.reprname
5161
5162 def tzname(self, dt):
5163 if self.dst(dt):
5164 return self.dstname
5165 else:
5166 return self.stdname
5167
5168 def utcoffset(self, dt):
5169 return self.stdoffset + self.dst(dt)
5170
5171 def dst(self, dt):
5172 if dt is None or dt.tzinfo is None:
5173 # An exception instead may be sensible here, in one or more of
5174 # the cases.
5175 return ZERO
5176 assert dt.tzinfo is self
5177
5178 # Find first Sunday in April.
5179 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
5180 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
5181
5182 # Find last Sunday in October.
5183 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
5184 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
5185
5186 # Can't compare naive to aware objects, so strip the timezone from
5187 # dt first.
5188 dt = dt.replace(tzinfo=None)
5189 if start + HOUR <= dt < end:
5190 # DST is in effect.
5191 return HOUR
5192 elif end <= dt < end + HOUR:
5193 # Fold (an ambiguous hour): use dt.fold to disambiguate.
5194 return ZERO if dt.fold else HOUR
5195 elif start <= dt < start + HOUR:
5196 # Gap (a non-existent hour): reverse the fold rule.
5197 return HOUR if dt.fold else ZERO
5198 else:
5199 # DST is off.
5200 return ZERO
5201
5202Eastern2 = USTimeZone2(-5, "Eastern2", "EST", "EDT")
5203Central2 = USTimeZone2(-6, "Central2", "CST", "CDT")
5204Mountain2 = USTimeZone2(-7, "Mountain2", "MST", "MDT")
5205Pacific2 = USTimeZone2(-8, "Pacific2", "PST", "PDT")
5206
5207# Europe_Vilnius_1941 tzinfo implementation reproduces the following
5208# 1941 transition from Olson's tzdist:
5209#
5210# Zone NAME GMTOFF RULES FORMAT [UNTIL]
5211# ZoneEurope/Vilnius 1:00 - CET 1940 Aug 3
5212# 3:00 - MSK 1941 Jun 24
5213# 1:00 C-Eur CE%sT 1944 Aug
5214#
5215# $ zdump -v Europe/Vilnius | grep 1941
5216# Europe/Vilnius Mon Jun 23 20:59:59 1941 UTC = Mon Jun 23 23:59:59 1941 MSK isdst=0 gmtoff=10800
5217# Europe/Vilnius Mon Jun 23 21:00:00 1941 UTC = Mon Jun 23 23:00:00 1941 CEST isdst=1 gmtoff=7200
5218
5219class Europe_Vilnius_1941(tzinfo):
5220 def _utc_fold(self):
5221 return [datetime(1941, 6, 23, 21, tzinfo=self), # Mon Jun 23 21:00:00 1941 UTC
5222 datetime(1941, 6, 23, 22, tzinfo=self)] # Mon Jun 23 22:00:00 1941 UTC
5223
5224 def _loc_fold(self):
5225 return [datetime(1941, 6, 23, 23, tzinfo=self), # Mon Jun 23 23:00:00 1941 MSK / CEST
5226 datetime(1941, 6, 24, 0, tzinfo=self)] # Mon Jun 24 00:00:00 1941 CEST
5227
5228 def utcoffset(self, dt):
5229 fold_start, fold_stop = self._loc_fold()
5230 if dt < fold_start:
5231 return 3 * HOUR
5232 if dt < fold_stop:
5233 return (2 if dt.fold else 3) * HOUR
5234 # if dt >= fold_stop
5235 return 2 * HOUR
5236
5237 def dst(self, dt):
5238 fold_start, fold_stop = self._loc_fold()
5239 if dt < fold_start:
5240 return 0 * HOUR
5241 if dt < fold_stop:
5242 return (1 if dt.fold else 0) * HOUR
5243 # if dt >= fold_stop
5244 return 1 * HOUR
5245
5246 def tzname(self, dt):
5247 fold_start, fold_stop = self._loc_fold()
5248 if dt < fold_start:
5249 return 'MSK'
5250 if dt < fold_stop:
5251 return ('MSK', 'CEST')[dt.fold]
5252 # if dt >= fold_stop
5253 return 'CEST'
5254
5255 def fromutc(self, dt):
5256 assert dt.fold == 0
5257 assert dt.tzinfo is self
5258 if dt.year != 1941:
5259 raise NotImplementedError
5260 fold_start, fold_stop = self._utc_fold()
5261 if dt < fold_start:
5262 return dt + 3 * HOUR
5263 if dt < fold_stop:
5264 return (dt + 2 * HOUR).replace(fold=1)
5265 # if dt >= fold_stop
5266 return dt + 2 * HOUR
5267
5268
5269class TestLocalTimeDisambiguation(unittest.TestCase):
5270
5271 def test_vilnius_1941_fromutc(self):
5272 Vilnius = Europe_Vilnius_1941()
5273
5274 gdt = datetime(1941, 6, 23, 20, 59, 59, tzinfo=timezone.utc)
5275 ldt = gdt.astimezone(Vilnius)
5276 self.assertEqual(ldt.strftime("%c %Z%z"),
5277 'Mon Jun 23 23:59:59 1941 MSK+0300')
5278 self.assertEqual(ldt.fold, 0)
5279 self.assertFalse(ldt.dst())
5280
5281 gdt = datetime(1941, 6, 23, 21, tzinfo=timezone.utc)
5282 ldt = gdt.astimezone(Vilnius)
5283 self.assertEqual(ldt.strftime("%c %Z%z"),
5284 'Mon Jun 23 23:00:00 1941 CEST+0200')
5285 self.assertEqual(ldt.fold, 1)
5286 self.assertTrue(ldt.dst())
5287
5288 gdt = datetime(1941, 6, 23, 22, tzinfo=timezone.utc)
5289 ldt = gdt.astimezone(Vilnius)
5290 self.assertEqual(ldt.strftime("%c %Z%z"),
5291 'Tue Jun 24 00:00:00 1941 CEST+0200')
5292 self.assertEqual(ldt.fold, 0)
5293 self.assertTrue(ldt.dst())
5294
5295 def test_vilnius_1941_toutc(self):
5296 Vilnius = Europe_Vilnius_1941()
5297
5298 ldt = datetime(1941, 6, 23, 22, 59, 59, tzinfo=Vilnius)
5299 gdt = ldt.astimezone(timezone.utc)
5300 self.assertEqual(gdt.strftime("%c %Z"),
5301 'Mon Jun 23 19:59:59 1941 UTC')
5302
5303 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius)
5304 gdt = ldt.astimezone(timezone.utc)
5305 self.assertEqual(gdt.strftime("%c %Z"),
5306 'Mon Jun 23 20:59:59 1941 UTC')
5307
5308 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius, fold=1)
5309 gdt = ldt.astimezone(timezone.utc)
5310 self.assertEqual(gdt.strftime("%c %Z"),
5311 'Mon Jun 23 21:59:59 1941 UTC')
5312
5313 ldt = datetime(1941, 6, 24, 0, tzinfo=Vilnius)
5314 gdt = ldt.astimezone(timezone.utc)
5315 self.assertEqual(gdt.strftime("%c %Z"),
5316 'Mon Jun 23 22:00:00 1941 UTC')
5317
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005318 def test_constructors(self):
5319 t = time(0, fold=1)
5320 dt = datetime(1, 1, 1, fold=1)
5321 self.assertEqual(t.fold, 1)
5322 self.assertEqual(dt.fold, 1)
5323 with self.assertRaises(TypeError):
5324 time(0, 0, 0, 0, None, 0)
5325
5326 def test_member(self):
5327 dt = datetime(1, 1, 1, fold=1)
5328 t = dt.time()
5329 self.assertEqual(t.fold, 1)
5330 t = dt.timetz()
5331 self.assertEqual(t.fold, 1)
5332
5333 def test_replace(self):
5334 t = time(0)
5335 dt = datetime(1, 1, 1)
5336 self.assertEqual(t.replace(fold=1).fold, 1)
5337 self.assertEqual(dt.replace(fold=1).fold, 1)
5338 self.assertEqual(t.replace(fold=0).fold, 0)
5339 self.assertEqual(dt.replace(fold=0).fold, 0)
5340 # Check that replacement of other fields does not change "fold".
5341 t = t.replace(fold=1, tzinfo=Eastern)
5342 dt = dt.replace(fold=1, tzinfo=Eastern)
5343 self.assertEqual(t.replace(tzinfo=None).fold, 1)
5344 self.assertEqual(dt.replace(tzinfo=None).fold, 1)
Serhiy Storchaka314d6fc2017-03-31 22:48:16 +03005345 # Out of bounds.
5346 with self.assertRaises(ValueError):
5347 t.replace(fold=2)
5348 with self.assertRaises(ValueError):
5349 dt.replace(fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005350 # Check that fold is a keyword-only argument
5351 with self.assertRaises(TypeError):
5352 t.replace(1, 1, 1, None, 1)
5353 with self.assertRaises(TypeError):
5354 dt.replace(1, 1, 1, 1, 1, 1, 1, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04005355
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005356 def test_comparison(self):
5357 t = time(0)
5358 dt = datetime(1, 1, 1)
5359 self.assertEqual(t, t.replace(fold=1))
5360 self.assertEqual(dt, dt.replace(fold=1))
5361
5362 def test_hash(self):
5363 t = time(0)
5364 dt = datetime(1, 1, 1)
5365 self.assertEqual(hash(t), hash(t.replace(fold=1)))
5366 self.assertEqual(hash(dt), hash(dt.replace(fold=1)))
5367
5368 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5369 def test_fromtimestamp(self):
5370 s = 1414906200
5371 dt0 = datetime.fromtimestamp(s)
5372 dt1 = datetime.fromtimestamp(s + 3600)
5373 self.assertEqual(dt0.fold, 0)
5374 self.assertEqual(dt1.fold, 1)
5375
5376 @support.run_with_tz('Australia/Lord_Howe')
5377 def test_fromtimestamp_lord_howe(self):
5378 tm = _time.localtime(1.4e9)
5379 if _time.strftime('%Z%z', tm) != 'LHST+1030':
5380 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
5381 # $ TZ=Australia/Lord_Howe date -r 1428158700
5382 # Sun Apr 5 01:45:00 LHDT 2015
5383 # $ TZ=Australia/Lord_Howe date -r 1428160500
5384 # Sun Apr 5 01:45:00 LHST 2015
5385 s = 1428158700
5386 t0 = datetime.fromtimestamp(s)
5387 t1 = datetime.fromtimestamp(s + 1800)
5388 self.assertEqual(t0, t1)
5389 self.assertEqual(t0.fold, 0)
5390 self.assertEqual(t1.fold, 1)
5391
Ammar Askar96d1e692018-07-25 09:54:58 -07005392 def test_fromtimestamp_low_fold_detection(self):
5393 # Ensure that fold detection doesn't cause an
5394 # OSError for really low values, see bpo-29097
5395 self.assertEqual(datetime.fromtimestamp(0).fold, 0)
5396
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005397 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5398 def test_timestamp(self):
5399 dt0 = datetime(2014, 11, 2, 1, 30)
5400 dt1 = dt0.replace(fold=1)
5401 self.assertEqual(dt0.timestamp() + 3600,
5402 dt1.timestamp())
5403
5404 @support.run_with_tz('Australia/Lord_Howe')
5405 def test_timestamp_lord_howe(self):
5406 tm = _time.localtime(1.4e9)
5407 if _time.strftime('%Z%z', tm) != 'LHST+1030':
5408 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
5409 t = datetime(2015, 4, 5, 1, 45)
5410 s0 = t.replace(fold=0).timestamp()
5411 s1 = t.replace(fold=1).timestamp()
5412 self.assertEqual(s0 + 1800, s1)
5413
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005414 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5415 def test_astimezone(self):
5416 dt0 = datetime(2014, 11, 2, 1, 30)
5417 dt1 = dt0.replace(fold=1)
5418 # Convert both naive instances to aware.
5419 adt0 = dt0.astimezone()
5420 adt1 = dt1.astimezone()
5421 # Check that the first instance in DST zone and the second in STD
5422 self.assertEqual(adt0.tzname(), 'EDT')
5423 self.assertEqual(adt1.tzname(), 'EST')
5424 self.assertEqual(adt0 + HOUR, adt1)
5425 # Aware instances with fixed offset tzinfo's always have fold=0
5426 self.assertEqual(adt0.fold, 0)
5427 self.assertEqual(adt1.fold, 0)
5428
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005429 def test_pickle_fold(self):
5430 t = time(fold=1)
5431 dt = datetime(1, 1, 1, fold=1)
5432 for pickler, unpickler, proto in pickle_choices:
5433 for x in [t, dt]:
5434 s = pickler.dumps(x, proto)
5435 y = unpickler.loads(s)
5436 self.assertEqual(x, y)
5437 self.assertEqual((0 if proto < 4 else x.fold), y.fold)
5438
5439 def test_repr(self):
5440 t = time(fold=1)
5441 dt = datetime(1, 1, 1, fold=1)
5442 self.assertEqual(repr(t), 'datetime.time(0, 0, fold=1)')
5443 self.assertEqual(repr(dt),
5444 'datetime.datetime(1, 1, 1, 0, 0, fold=1)')
5445
5446 def test_dst(self):
5447 # Let's first establish that things work in regular times.
5448 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
5449 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
5450 self.assertEqual(dt_summer.dst(), HOUR)
5451 self.assertEqual(dt_winter.dst(), ZERO)
5452 # The disambiguation flag is ignored
5453 self.assertEqual(dt_summer.replace(fold=1).dst(), HOUR)
5454 self.assertEqual(dt_winter.replace(fold=1).dst(), ZERO)
5455
5456 # Pick local time in the fold.
5457 for minute in [0, 30, 59]:
5458 dt = datetime(2002, 10, 27, 1, minute, tzinfo=Eastern2)
5459 # With fold=0 (the default) it is in DST.
5460 self.assertEqual(dt.dst(), HOUR)
5461 # With fold=1 it is in STD.
5462 self.assertEqual(dt.replace(fold=1).dst(), ZERO)
5463
5464 # Pick local time in the gap.
5465 for minute in [0, 30, 59]:
5466 dt = datetime(2002, 4, 7, 2, minute, tzinfo=Eastern2)
5467 # With fold=0 (the default) it is in STD.
5468 self.assertEqual(dt.dst(), ZERO)
5469 # With fold=1 it is in DST.
5470 self.assertEqual(dt.replace(fold=1).dst(), HOUR)
5471
5472
5473 def test_utcoffset(self):
5474 # Let's first establish that things work in regular times.
5475 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
5476 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
5477 self.assertEqual(dt_summer.utcoffset(), -4 * HOUR)
5478 self.assertEqual(dt_winter.utcoffset(), -5 * HOUR)
5479 # The disambiguation flag is ignored
5480 self.assertEqual(dt_summer.replace(fold=1).utcoffset(), -4 * HOUR)
5481 self.assertEqual(dt_winter.replace(fold=1).utcoffset(), -5 * HOUR)
5482
5483 def test_fromutc(self):
5484 # Let's first establish that things work in regular times.
5485 u_summer = datetime(2002, 10, 27, 6, tzinfo=Eastern2) - timedelta.resolution
5486 u_winter = datetime(2002, 10, 27, 7, tzinfo=Eastern2)
5487 t_summer = Eastern2.fromutc(u_summer)
5488 t_winter = Eastern2.fromutc(u_winter)
5489 self.assertEqual(t_summer, u_summer - 4 * HOUR)
5490 self.assertEqual(t_winter, u_winter - 5 * HOUR)
5491 self.assertEqual(t_summer.fold, 0)
5492 self.assertEqual(t_winter.fold, 0)
5493
5494 # What happens in the fall-back fold?
5495 u = datetime(2002, 10, 27, 5, 30, tzinfo=Eastern2)
5496 t0 = Eastern2.fromutc(u)
5497 u += HOUR
5498 t1 = Eastern2.fromutc(u)
5499 self.assertEqual(t0, t1)
5500 self.assertEqual(t0.fold, 0)
5501 self.assertEqual(t1.fold, 1)
5502 # The tricky part is when u is in the local fold:
5503 u = datetime(2002, 10, 27, 1, 30, tzinfo=Eastern2)
5504 t = Eastern2.fromutc(u)
5505 self.assertEqual((t.day, t.hour), (26, 21))
5506 # .. or gets into the local fold after a standard time adjustment
5507 u = datetime(2002, 10, 27, 6, 30, tzinfo=Eastern2)
5508 t = Eastern2.fromutc(u)
5509 self.assertEqual((t.day, t.hour), (27, 1))
5510
5511 # What happens in the spring-forward gap?
5512 u = datetime(2002, 4, 7, 2, 0, tzinfo=Eastern2)
5513 t = Eastern2.fromutc(u)
5514 self.assertEqual((t.day, t.hour), (6, 21))
5515
5516 def test_mixed_compare_regular(self):
5517 t = datetime(2000, 1, 1, tzinfo=Eastern2)
5518 self.assertEqual(t, t.astimezone(timezone.utc))
5519 t = datetime(2000, 6, 1, tzinfo=Eastern2)
5520 self.assertEqual(t, t.astimezone(timezone.utc))
5521
5522 def test_mixed_compare_fold(self):
5523 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
5524 t_fold_utc = t_fold.astimezone(timezone.utc)
5525 self.assertNotEqual(t_fold, t_fold_utc)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04005526 self.assertNotEqual(t_fold_utc, t_fold)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005527
5528 def test_mixed_compare_gap(self):
5529 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
5530 t_gap_utc = t_gap.astimezone(timezone.utc)
5531 self.assertNotEqual(t_gap, t_gap_utc)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04005532 self.assertNotEqual(t_gap_utc, t_gap)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005533
5534 def test_hash_aware(self):
5535 t = datetime(2000, 1, 1, tzinfo=Eastern2)
5536 self.assertEqual(hash(t), hash(t.replace(fold=1)))
5537 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
5538 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
5539 self.assertEqual(hash(t_fold), hash(t_fold.replace(fold=1)))
5540 self.assertEqual(hash(t_gap), hash(t_gap.replace(fold=1)))
5541
5542SEC = timedelta(0, 1)
5543
5544def pairs(iterable):
5545 a, b = itertools.tee(iterable)
5546 next(b, None)
5547 return zip(a, b)
5548
5549class ZoneInfo(tzinfo):
5550 zoneroot = '/usr/share/zoneinfo'
5551 def __init__(self, ut, ti):
5552 """
5553
5554 :param ut: array
5555 Array of transition point timestamps
5556 :param ti: list
5557 A list of (offset, isdst, abbr) tuples
5558 :return: None
5559 """
5560 self.ut = ut
5561 self.ti = ti
5562 self.lt = self.invert(ut, ti)
5563
5564 @staticmethod
5565 def invert(ut, ti):
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04005566 lt = (array('q', ut), array('q', ut))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005567 if ut:
5568 offset = ti[0][0] // SEC
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04005569 lt[0][0] += offset
5570 lt[1][0] += offset
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005571 for i in range(1, len(ut)):
5572 lt[0][i] += ti[i-1][0] // SEC
5573 lt[1][i] += ti[i][0] // SEC
5574 return lt
5575
5576 @classmethod
5577 def fromfile(cls, fileobj):
5578 if fileobj.read(4).decode() != "TZif":
5579 raise ValueError("not a zoneinfo file")
5580 fileobj.seek(32)
5581 counts = array('i')
5582 counts.fromfile(fileobj, 3)
5583 if sys.byteorder != 'big':
5584 counts.byteswap()
5585
5586 ut = array('i')
5587 ut.fromfile(fileobj, counts[0])
5588 if sys.byteorder != 'big':
5589 ut.byteswap()
5590
5591 type_indices = array('B')
5592 type_indices.fromfile(fileobj, counts[0])
5593
5594 ttis = []
5595 for i in range(counts[1]):
5596 ttis.append(struct.unpack(">lbb", fileobj.read(6)))
5597
5598 abbrs = fileobj.read(counts[2])
5599
5600 # Convert ttis
5601 for i, (gmtoff, isdst, abbrind) in enumerate(ttis):
5602 abbr = abbrs[abbrind:abbrs.find(0, abbrind)].decode()
5603 ttis[i] = (timedelta(0, gmtoff), isdst, abbr)
5604
5605 ti = [None] * len(ut)
5606 for i, idx in enumerate(type_indices):
5607 ti[i] = ttis[idx]
5608
5609 self = cls(ut, ti)
5610
5611 return self
5612
5613 @classmethod
5614 def fromname(cls, name):
5615 path = os.path.join(cls.zoneroot, name)
5616 with open(path, 'rb') as f:
5617 return cls.fromfile(f)
5618
5619 EPOCHORDINAL = date(1970, 1, 1).toordinal()
5620
5621 def fromutc(self, dt):
5622 """datetime in UTC -> datetime in local time."""
5623
5624 if not isinstance(dt, datetime):
5625 raise TypeError("fromutc() requires a datetime argument")
5626 if dt.tzinfo is not self:
5627 raise ValueError("dt.tzinfo is not self")
5628
5629 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
5630 + dt.hour * 3600
5631 + dt.minute * 60
5632 + dt.second)
5633
5634 if timestamp < self.ut[1]:
5635 tti = self.ti[0]
5636 fold = 0
5637 else:
5638 idx = bisect.bisect_right(self.ut, timestamp)
5639 assert self.ut[idx-1] <= timestamp
5640 assert idx == len(self.ut) or timestamp < self.ut[idx]
5641 tti_prev, tti = self.ti[idx-2:idx]
5642 # Detect fold
5643 shift = tti_prev[0] - tti[0]
5644 fold = (shift > timedelta(0, timestamp - self.ut[idx-1]))
5645 dt += tti[0]
5646 if fold:
5647 return dt.replace(fold=1)
5648 else:
5649 return dt
5650
5651 def _find_ti(self, dt, i):
5652 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
5653 + dt.hour * 3600
5654 + dt.minute * 60
5655 + dt.second)
5656 lt = self.lt[dt.fold]
5657 idx = bisect.bisect_right(lt, timestamp)
5658
5659 return self.ti[max(0, idx - 1)][i]
5660
5661 def utcoffset(self, dt):
5662 return self._find_ti(dt, 0)
5663
5664 def dst(self, dt):
5665 isdst = self._find_ti(dt, 1)
5666 # XXX: We cannot accurately determine the "save" value,
5667 # so let's return 1h whenever DST is in effect. Since
5668 # we don't use dst() in fromutc(), it is unlikely that
5669 # it will be needed for anything more than bool(dst()).
5670 return ZERO if isdst else HOUR
5671
5672 def tzname(self, dt):
5673 return self._find_ti(dt, 2)
5674
5675 @classmethod
5676 def zonenames(cls, zonedir=None):
5677 if zonedir is None:
5678 zonedir = cls.zoneroot
Alexander Belopolsky1b8f26c2016-08-11 11:01:45 -04005679 zone_tab = os.path.join(zonedir, 'zone.tab')
5680 try:
5681 f = open(zone_tab)
5682 except OSError:
5683 return
5684 with f:
5685 for line in f:
5686 line = line.strip()
5687 if line and not line.startswith('#'):
5688 yield line.split()[2]
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005689
5690 @classmethod
5691 def stats(cls, start_year=1):
5692 count = gap_count = fold_count = zeros_count = 0
5693 min_gap = min_fold = timedelta.max
5694 max_gap = max_fold = ZERO
5695 min_gap_datetime = max_gap_datetime = datetime.min
5696 min_gap_zone = max_gap_zone = None
5697 min_fold_datetime = max_fold_datetime = datetime.min
5698 min_fold_zone = max_fold_zone = None
5699 stats_since = datetime(start_year, 1, 1) # Starting from 1970 eliminates a lot of noise
5700 for zonename in cls.zonenames():
5701 count += 1
5702 tz = cls.fromname(zonename)
5703 for dt, shift in tz.transitions():
5704 if dt < stats_since:
5705 continue
5706 if shift > ZERO:
5707 gap_count += 1
5708 if (shift, dt) > (max_gap, max_gap_datetime):
5709 max_gap = shift
5710 max_gap_zone = zonename
5711 max_gap_datetime = dt
5712 if (shift, datetime.max - dt) < (min_gap, datetime.max - min_gap_datetime):
5713 min_gap = shift
5714 min_gap_zone = zonename
5715 min_gap_datetime = dt
5716 elif shift < ZERO:
5717 fold_count += 1
5718 shift = -shift
5719 if (shift, dt) > (max_fold, max_fold_datetime):
5720 max_fold = shift
5721 max_fold_zone = zonename
5722 max_fold_datetime = dt
5723 if (shift, datetime.max - dt) < (min_fold, datetime.max - min_fold_datetime):
5724 min_fold = shift
5725 min_fold_zone = zonename
5726 min_fold_datetime = dt
5727 else:
5728 zeros_count += 1
5729 trans_counts = (gap_count, fold_count, zeros_count)
5730 print("Number of zones: %5d" % count)
5731 print("Number of transitions: %5d = %d (gaps) + %d (folds) + %d (zeros)" %
5732 ((sum(trans_counts),) + trans_counts))
5733 print("Min gap: %16s at %s in %s" % (min_gap, min_gap_datetime, min_gap_zone))
5734 print("Max gap: %16s at %s in %s" % (max_gap, max_gap_datetime, max_gap_zone))
5735 print("Min fold: %16s at %s in %s" % (min_fold, min_fold_datetime, min_fold_zone))
5736 print("Max fold: %16s at %s in %s" % (max_fold, max_fold_datetime, max_fold_zone))
5737
5738
5739 def transitions(self):
5740 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
5741 shift = ti[0] - prev_ti[0]
5742 yield datetime.utcfromtimestamp(t), shift
5743
5744 def nondst_folds(self):
5745 """Find all folds with the same value of isdst on both sides of the transition."""
5746 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
5747 shift = ti[0] - prev_ti[0]
5748 if shift < ZERO and ti[1] == prev_ti[1]:
5749 yield datetime.utcfromtimestamp(t), -shift, prev_ti[2], ti[2]
5750
5751 @classmethod
5752 def print_all_nondst_folds(cls, same_abbr=False, start_year=1):
5753 count = 0
5754 for zonename in cls.zonenames():
5755 tz = cls.fromname(zonename)
5756 for dt, shift, prev_abbr, abbr in tz.nondst_folds():
5757 if dt.year < start_year or same_abbr and prev_abbr != abbr:
5758 continue
5759 count += 1
5760 print("%3d) %-30s %s %10s %5s -> %s" %
5761 (count, zonename, dt, shift, prev_abbr, abbr))
5762
5763 def folds(self):
5764 for t, shift in self.transitions():
5765 if shift < ZERO:
5766 yield t, -shift
5767
5768 def gaps(self):
5769 for t, shift in self.transitions():
5770 if shift > ZERO:
5771 yield t, shift
5772
5773 def zeros(self):
5774 for t, shift in self.transitions():
5775 if not shift:
5776 yield t
5777
5778
5779class ZoneInfoTest(unittest.TestCase):
5780 zonename = 'America/New_York'
5781
5782 def setUp(self):
hliu08e7ff6a2019-09-10 18:28:11 +08005783 if sys.platform == "vxworks":
5784 self.skipTest("Skipping zoneinfo tests on VxWorks")
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005785 if sys.platform == "win32":
5786 self.skipTest("Skipping zoneinfo tests on Windows")
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04005787 try:
5788 self.tz = ZoneInfo.fromname(self.zonename)
5789 except FileNotFoundError as err:
5790 self.skipTest("Skipping %s: %s" % (self.zonename, err))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005791
5792 def assertEquivDatetimes(self, a, b):
5793 self.assertEqual((a.replace(tzinfo=None), a.fold, id(a.tzinfo)),
5794 (b.replace(tzinfo=None), b.fold, id(b.tzinfo)))
5795
5796 def test_folds(self):
5797 tz = self.tz
5798 for dt, shift in tz.folds():
5799 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
5800 udt = dt + x
5801 ldt = tz.fromutc(udt.replace(tzinfo=tz))
5802 self.assertEqual(ldt.fold, 1)
5803 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
5804 self.assertEquivDatetimes(adt, ldt)
5805 utcoffset = ldt.utcoffset()
5806 self.assertEqual(ldt.replace(tzinfo=None), udt + utcoffset)
5807 # Round trip
5808 self.assertEquivDatetimes(ldt.astimezone(timezone.utc),
5809 udt.replace(tzinfo=timezone.utc))
5810
5811
5812 for x in [-timedelta.resolution, shift]:
5813 udt = dt + x
5814 udt = udt.replace(tzinfo=tz)
5815 ldt = tz.fromutc(udt)
5816 self.assertEqual(ldt.fold, 0)
5817
5818 def test_gaps(self):
5819 tz = self.tz
5820 for dt, shift in tz.gaps():
5821 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
5822 udt = dt + x
5823 udt = udt.replace(tzinfo=tz)
5824 ldt = tz.fromutc(udt)
5825 self.assertEqual(ldt.fold, 0)
5826 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
5827 self.assertEquivDatetimes(adt, ldt)
5828 utcoffset = ldt.utcoffset()
5829 self.assertEqual(ldt.replace(tzinfo=None), udt.replace(tzinfo=None) + utcoffset)
5830 # Create a local time inside the gap
5831 ldt = tz.fromutc(dt.replace(tzinfo=tz)) - shift + x
5832 self.assertLess(ldt.replace(fold=1).utcoffset(),
5833 ldt.replace(fold=0).utcoffset(),
5834 "At %s." % ldt)
5835
5836 for x in [-timedelta.resolution, shift]:
5837 udt = dt + x
5838 ldt = tz.fromutc(udt.replace(tzinfo=tz))
5839 self.assertEqual(ldt.fold, 0)
5840
5841 def test_system_transitions(self):
5842 if ('Riyadh8' in self.zonename or
5843 # From tzdata NEWS file:
5844 # The files solar87, solar88, and solar89 are no longer distributed.
5845 # They were a negative experiment - that is, a demonstration that
5846 # tz data can represent solar time only with some difficulty and error.
5847 # Their presence in the distribution caused confusion, as Riyadh
5848 # civil time was generally not solar time in those years.
5849 self.zonename.startswith('right/')):
5850 self.skipTest("Skipping %s" % self.zonename)
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04005851 tz = self.tz
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005852 TZ = os.environ.get('TZ')
5853 os.environ['TZ'] = self.zonename
5854 try:
5855 _time.tzset()
5856 for udt, shift in tz.transitions():
Alexander Belopolsky10c2dd22016-08-12 19:08:15 -04005857 if udt.year >= 2037:
5858 # System support for times around the end of 32-bit time_t
5859 # and later is flaky on many systems.
5860 break
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005861 s0 = (udt - datetime(1970, 1, 1)) // SEC
5862 ss = shift // SEC # shift seconds
5863 for x in [-40 * 3600, -20*3600, -1, 0,
5864 ss - 1, ss + 20 * 3600, ss + 40 * 3600]:
5865 s = s0 + x
5866 sdt = datetime.fromtimestamp(s)
5867 tzdt = datetime.fromtimestamp(s, tz).replace(tzinfo=None)
5868 self.assertEquivDatetimes(sdt, tzdt)
5869 s1 = sdt.timestamp()
5870 self.assertEqual(s, s1)
5871 if ss > 0: # gap
5872 # Create local time inside the gap
5873 dt = datetime.fromtimestamp(s0) - shift / 2
5874 ts0 = dt.timestamp()
5875 ts1 = dt.replace(fold=1).timestamp()
5876 self.assertEqual(ts0, s0 + ss / 2)
5877 self.assertEqual(ts1, s0 - ss / 2)
5878 finally:
5879 if TZ is None:
5880 del os.environ['TZ']
5881 else:
5882 os.environ['TZ'] = TZ
5883 _time.tzset()
5884
5885
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04005886class ZoneInfoCompleteTest(unittest.TestSuite):
5887 def __init__(self):
5888 tests = []
5889 if is_resource_enabled('tzdata'):
5890 for name in ZoneInfo.zonenames():
5891 Test = type('ZoneInfoTest[%s]' % name, (ZoneInfoTest,), {})
5892 Test.zonename = name
5893 for method in dir(Test):
5894 if method.startswith('test_'):
5895 tests.append(Test(method))
5896 super().__init__(tests)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005897
5898# Iran had a sub-minute UTC offset before 1946.
5899class IranTest(ZoneInfoTest):
Alexander Belopolsky07e2a0a2016-07-30 11:41:02 -04005900 zonename = 'Asia/Tehran'
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005901
Paul Ganssle04af5b12018-01-24 17:29:30 -05005902
5903class CapiTest(unittest.TestCase):
5904 def setUp(self):
5905 # Since the C API is not present in the _Pure tests, skip all tests
5906 if self.__class__.__name__.endswith('Pure'):
5907 self.skipTest('Not relevant in pure Python')
5908
5909 # This *must* be called, and it must be called first, so until either
5910 # restriction is loosened, we'll call it as part of test setup
5911 _testcapi.test_datetime_capi()
5912
5913 def test_utc_capi(self):
5914 for use_macro in (True, False):
5915 capi_utc = _testcapi.get_timezone_utc_capi(use_macro)
5916
5917 with self.subTest(use_macro=use_macro):
5918 self.assertIs(capi_utc, timezone.utc)
5919
5920 def test_timezones_capi(self):
5921 est_capi, est_macro, est_macro_nn = _testcapi.make_timezones_capi()
5922
5923 exp_named = timezone(timedelta(hours=-5), "EST")
5924 exp_unnamed = timezone(timedelta(hours=-5))
5925
5926 cases = [
5927 ('est_capi', est_capi, exp_named),
5928 ('est_macro', est_macro, exp_named),
5929 ('est_macro_nn', est_macro_nn, exp_unnamed)
5930 ]
5931
5932 for name, tz_act, tz_exp in cases:
5933 with self.subTest(name=name):
5934 self.assertEqual(tz_act, tz_exp)
5935
5936 dt1 = datetime(2000, 2, 4, tzinfo=tz_act)
5937 dt2 = datetime(2000, 2, 4, tzinfo=tz_exp)
5938
5939 self.assertEqual(dt1, dt2)
5940 self.assertEqual(dt1.tzname(), dt2.tzname())
5941
5942 dt_utc = datetime(2000, 2, 4, 5, tzinfo=timezone.utc)
5943
5944 self.assertEqual(dt1.astimezone(timezone.utc), dt_utc)
5945
Joannah Nanjekye2c5fb172019-08-29 09:54:46 -03005946 def test_PyDateTime_DELTA_GET(self):
5947 class TimeDeltaSubclass(timedelta):
5948 pass
5949
5950 for klass in [timedelta, TimeDeltaSubclass]:
5951 for args in [(26, 55, 99999), (26, 55, 99999)]:
5952 d = klass(*args)
5953 with self.subTest(cls=klass, date=args):
5954 days, seconds, microseconds = _testcapi.PyDateTime_DELTA_GET(d)
5955
5956 self.assertEqual(days, d.days)
5957 self.assertEqual(seconds, d.seconds)
5958 self.assertEqual(microseconds, d.microseconds)
5959
5960 def test_PyDateTime_GET(self):
5961 class DateSubclass(date):
5962 pass
5963
5964 for klass in [date, DateSubclass]:
5965 for args in [(2000, 1, 2), (2012, 2, 29)]:
5966 d = klass(*args)
5967 with self.subTest(cls=klass, date=args):
5968 year, month, day = _testcapi.PyDateTime_GET(d)
5969
5970 self.assertEqual(year, d.year)
5971 self.assertEqual(month, d.month)
5972 self.assertEqual(day, d.day)
5973
5974 def test_PyDateTime_DATE_GET(self):
5975 class DateTimeSubclass(datetime):
5976 pass
5977
5978 for klass in [datetime, DateTimeSubclass]:
5979 for args in [(1993, 8, 26, 22, 12, 55, 99999),
5980 (1993, 8, 26, 22, 12, 55, 99999)]:
5981 d = klass(*args)
5982 with self.subTest(cls=klass, date=args):
5983 hour, minute, second, microsecond = _testcapi.PyDateTime_DATE_GET(d)
5984
5985 self.assertEqual(hour, d.hour)
5986 self.assertEqual(minute, d.minute)
5987 self.assertEqual(second, d.second)
5988 self.assertEqual(microsecond, d.microsecond)
5989
5990 def test_PyDateTime_TIME_GET(self):
5991 class TimeSubclass(time):
5992 pass
5993
5994 for klass in [time, TimeSubclass]:
5995 for args in [(12, 30, 20, 10), (12, 30, 20, 10)]:
5996 d = klass(*args)
5997 with self.subTest(cls=klass, date=args):
5998 hour, minute, second, microsecond = _testcapi.PyDateTime_TIME_GET(d)
5999
6000 self.assertEqual(hour, d.hour)
6001 self.assertEqual(minute, d.minute)
6002 self.assertEqual(second, d.second)
6003 self.assertEqual(microsecond, d.microsecond)
6004
Paul Gansslea049f572018-02-22 15:15:32 -05006005 def test_timezones_offset_zero(self):
6006 utc0, utc1, non_utc = _testcapi.get_timezones_offset_zero()
6007
6008 with self.subTest(testname="utc0"):
6009 self.assertIs(utc0, timezone.utc)
6010
6011 with self.subTest(testname="utc1"):
6012 self.assertIs(utc1, timezone.utc)
6013
6014 with self.subTest(testname="non_utc"):
6015 self.assertIsNot(non_utc, timezone.utc)
6016
6017 non_utc_exp = timezone(timedelta(hours=0), "")
6018
6019 self.assertEqual(non_utc, non_utc_exp)
6020
6021 dt1 = datetime(2000, 2, 4, tzinfo=non_utc)
6022 dt2 = datetime(2000, 2, 4, tzinfo=non_utc_exp)
6023
6024 self.assertEqual(dt1, dt2)
6025 self.assertEqual(dt1.tzname(), dt2.tzname())
6026
Paul Ganssle04af5b12018-01-24 17:29:30 -05006027 def test_check_date(self):
6028 class DateSubclass(date):
6029 pass
6030
6031 d = date(2011, 1, 1)
6032 ds = DateSubclass(2011, 1, 1)
6033 dt = datetime(2011, 1, 1)
6034
6035 is_date = _testcapi.datetime_check_date
6036
6037 # Check the ones that should be valid
6038 self.assertTrue(is_date(d))
6039 self.assertTrue(is_date(dt))
6040 self.assertTrue(is_date(ds))
6041 self.assertTrue(is_date(d, True))
6042
6043 # Check that the subclasses do not match exactly
6044 self.assertFalse(is_date(dt, True))
6045 self.assertFalse(is_date(ds, True))
6046
6047 # Check that various other things are not dates at all
6048 args = [tuple(), list(), 1, '2011-01-01',
6049 timedelta(1), timezone.utc, time(12, 00)]
6050 for arg in args:
6051 for exact in (True, False):
6052 with self.subTest(arg=arg, exact=exact):
6053 self.assertFalse(is_date(arg, exact))
6054
6055 def test_check_time(self):
6056 class TimeSubclass(time):
6057 pass
6058
6059 t = time(12, 30)
6060 ts = TimeSubclass(12, 30)
6061
6062 is_time = _testcapi.datetime_check_time
6063
6064 # Check the ones that should be valid
6065 self.assertTrue(is_time(t))
6066 self.assertTrue(is_time(ts))
6067 self.assertTrue(is_time(t, True))
6068
6069 # Check that the subclass does not match exactly
6070 self.assertFalse(is_time(ts, True))
6071
6072 # Check that various other things are not times
6073 args = [tuple(), list(), 1, '2011-01-01',
6074 timedelta(1), timezone.utc, date(2011, 1, 1)]
6075
6076 for arg in args:
6077 for exact in (True, False):
6078 with self.subTest(arg=arg, exact=exact):
6079 self.assertFalse(is_time(arg, exact))
6080
6081 def test_check_datetime(self):
6082 class DateTimeSubclass(datetime):
6083 pass
6084
6085 dt = datetime(2011, 1, 1, 12, 30)
6086 dts = DateTimeSubclass(2011, 1, 1, 12, 30)
6087
6088 is_datetime = _testcapi.datetime_check_datetime
6089
6090 # Check the ones that should be valid
6091 self.assertTrue(is_datetime(dt))
6092 self.assertTrue(is_datetime(dts))
6093 self.assertTrue(is_datetime(dt, True))
6094
6095 # Check that the subclass does not match exactly
6096 self.assertFalse(is_datetime(dts, True))
6097
6098 # Check that various other things are not datetimes
6099 args = [tuple(), list(), 1, '2011-01-01',
6100 timedelta(1), timezone.utc, date(2011, 1, 1)]
6101
6102 for arg in args:
6103 for exact in (True, False):
6104 with self.subTest(arg=arg, exact=exact):
6105 self.assertFalse(is_datetime(arg, exact))
6106
6107 def test_check_delta(self):
6108 class TimeDeltaSubclass(timedelta):
6109 pass
6110
6111 td = timedelta(1)
6112 tds = TimeDeltaSubclass(1)
6113
6114 is_timedelta = _testcapi.datetime_check_delta
6115
6116 # Check the ones that should be valid
6117 self.assertTrue(is_timedelta(td))
6118 self.assertTrue(is_timedelta(tds))
6119 self.assertTrue(is_timedelta(td, True))
6120
6121 # Check that the subclass does not match exactly
6122 self.assertFalse(is_timedelta(tds, True))
6123
6124 # Check that various other things are not timedeltas
6125 args = [tuple(), list(), 1, '2011-01-01',
6126 timezone.utc, date(2011, 1, 1), datetime(2011, 1, 1)]
6127
6128 for arg in args:
6129 for exact in (True, False):
6130 with self.subTest(arg=arg, exact=exact):
6131 self.assertFalse(is_timedelta(arg, exact))
6132
6133 def test_check_tzinfo(self):
6134 class TZInfoSubclass(tzinfo):
6135 pass
6136
6137 tzi = tzinfo()
6138 tzis = TZInfoSubclass()
6139 tz = timezone(timedelta(hours=-5))
6140
6141 is_tzinfo = _testcapi.datetime_check_tzinfo
6142
6143 # Check the ones that should be valid
6144 self.assertTrue(is_tzinfo(tzi))
6145 self.assertTrue(is_tzinfo(tz))
6146 self.assertTrue(is_tzinfo(tzis))
6147 self.assertTrue(is_tzinfo(tzi, True))
6148
6149 # Check that the subclasses do not match exactly
6150 self.assertFalse(is_tzinfo(tz, True))
6151 self.assertFalse(is_tzinfo(tzis, True))
6152
6153 # Check that various other things are not tzinfos
6154 args = [tuple(), list(), 1, '2011-01-01',
6155 date(2011, 1, 1), datetime(2011, 1, 1)]
6156
6157 for arg in args:
6158 for exact in (True, False):
6159 with self.subTest(arg=arg, exact=exact):
6160 self.assertFalse(is_tzinfo(arg, exact))
6161
Edison A98ff4d52019-05-17 13:28:42 -07006162 def test_date_from_date(self):
6163 exp_date = date(1993, 8, 26)
6164
Serhiy Storchaka1f21eaa2019-09-01 12:16:51 +03006165 for macro in False, True:
Edison A98ff4d52019-05-17 13:28:42 -07006166 with self.subTest(macro=macro):
6167 c_api_date = _testcapi.get_date_fromdate(
6168 macro,
6169 exp_date.year,
6170 exp_date.month,
6171 exp_date.day)
6172
6173 self.assertEqual(c_api_date, exp_date)
6174
6175 def test_datetime_from_dateandtime(self):
6176 exp_date = datetime(1993, 8, 26, 22, 12, 55, 99999)
6177
Serhiy Storchaka1f21eaa2019-09-01 12:16:51 +03006178 for macro in False, True:
Edison A98ff4d52019-05-17 13:28:42 -07006179 with self.subTest(macro=macro):
6180 c_api_date = _testcapi.get_datetime_fromdateandtime(
6181 macro,
6182 exp_date.year,
6183 exp_date.month,
6184 exp_date.day,
6185 exp_date.hour,
6186 exp_date.minute,
6187 exp_date.second,
6188 exp_date.microsecond)
6189
6190 self.assertEqual(c_api_date, exp_date)
6191
6192 def test_datetime_from_dateandtimeandfold(self):
6193 exp_date = datetime(1993, 8, 26, 22, 12, 55, 99999)
6194
6195 for fold in [0, 1]:
Serhiy Storchaka1f21eaa2019-09-01 12:16:51 +03006196 for macro in False, True:
Edison A98ff4d52019-05-17 13:28:42 -07006197 with self.subTest(macro=macro, fold=fold):
6198 c_api_date = _testcapi.get_datetime_fromdateandtimeandfold(
6199 macro,
6200 exp_date.year,
6201 exp_date.month,
6202 exp_date.day,
6203 exp_date.hour,
6204 exp_date.minute,
6205 exp_date.second,
6206 exp_date.microsecond,
6207 exp_date.fold)
6208
6209 self.assertEqual(c_api_date, exp_date)
6210 self.assertEqual(c_api_date.fold, exp_date.fold)
6211
6212 def test_time_from_time(self):
6213 exp_time = time(22, 12, 55, 99999)
6214
Serhiy Storchaka1f21eaa2019-09-01 12:16:51 +03006215 for macro in False, True:
Edison A98ff4d52019-05-17 13:28:42 -07006216 with self.subTest(macro=macro):
6217 c_api_time = _testcapi.get_time_fromtime(
6218 macro,
6219 exp_time.hour,
6220 exp_time.minute,
6221 exp_time.second,
6222 exp_time.microsecond)
6223
6224 self.assertEqual(c_api_time, exp_time)
6225
6226 def test_time_from_timeandfold(self):
6227 exp_time = time(22, 12, 55, 99999)
6228
6229 for fold in [0, 1]:
Serhiy Storchaka1f21eaa2019-09-01 12:16:51 +03006230 for macro in False, True:
Edison A98ff4d52019-05-17 13:28:42 -07006231 with self.subTest(macro=macro, fold=fold):
6232 c_api_time = _testcapi.get_time_fromtimeandfold(
6233 macro,
6234 exp_time.hour,
6235 exp_time.minute,
6236 exp_time.second,
6237 exp_time.microsecond,
6238 exp_time.fold)
6239
6240 self.assertEqual(c_api_time, exp_time)
6241 self.assertEqual(c_api_time.fold, exp_time.fold)
6242
6243 def test_delta_from_dsu(self):
6244 exp_delta = timedelta(26, 55, 99999)
6245
Serhiy Storchaka1f21eaa2019-09-01 12:16:51 +03006246 for macro in False, True:
Edison A98ff4d52019-05-17 13:28:42 -07006247 with self.subTest(macro=macro):
6248 c_api_delta = _testcapi.get_delta_fromdsu(
6249 macro,
6250 exp_delta.days,
6251 exp_delta.seconds,
6252 exp_delta.microseconds)
6253
6254 self.assertEqual(c_api_delta, exp_delta)
6255
Paul Ganssle4d8c8c02019-04-27 15:39:40 -04006256 def test_date_from_timestamp(self):
6257 ts = datetime(1995, 4, 12).timestamp()
6258
Serhiy Storchaka1f21eaa2019-09-01 12:16:51 +03006259 for macro in False, True:
Paul Ganssle4d8c8c02019-04-27 15:39:40 -04006260 with self.subTest(macro=macro):
6261 d = _testcapi.get_date_fromtimestamp(int(ts), macro)
6262
6263 self.assertEqual(d, date(1995, 4, 12))
6264
6265 def test_datetime_from_timestamp(self):
Paul Ganssle4d8c8c02019-04-27 15:39:40 -04006266 cases = [
6267 ((1995, 4, 12), None, False),
6268 ((1995, 4, 12), None, True),
6269 ((1995, 4, 12), timezone(timedelta(hours=1)), True),
6270 ((1995, 4, 12, 14, 30), None, False),
6271 ((1995, 4, 12, 14, 30), None, True),
6272 ((1995, 4, 12, 14, 30), timezone(timedelta(hours=1)), True),
6273 ]
6274
6275 from_timestamp = _testcapi.get_datetime_fromtimestamp
6276 for case in cases:
Serhiy Storchaka1f21eaa2019-09-01 12:16:51 +03006277 for macro in False, True:
Paul Ganssle4d8c8c02019-04-27 15:39:40 -04006278 with self.subTest(case=case, macro=macro):
6279 dtup, tzinfo, usetz = case
6280 dt_orig = datetime(*dtup, tzinfo=tzinfo)
6281 ts = int(dt_orig.timestamp())
6282
6283 dt_rt = from_timestamp(ts, tzinfo, usetz, macro)
6284
6285 self.assertEqual(dt_orig, dt_rt)
6286
6287
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04006288def load_tests(loader, standard_tests, pattern):
6289 standard_tests.addTest(ZoneInfoCompleteTest())
6290 return standard_tests
6291
6292
Alexander Belopolskycf86e362010-07-23 19:25:47 +00006293if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -05006294 unittest.main()