blob: 58004870a00f17096eec9d86701d4da865cb3632 [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 = [
3327 b"cdatetime\ntime\n(S'\\x14;\\x10\\x00\\x10\\x00'\ntR.",
3328 b'cdatetime\ntime\n(U\x06\x14;\x10\x00\x10\x00tR.',
3329 b'\x80\x02cdatetime\ntime\nU\x06\x14;\x10\x00\x10\x00\x85R.',
3330 ]
3331 args = 20, 59, 16, 64**2
3332 expected = self.theclass(*args)
3333 for data in tests:
3334 for loads in pickle_loads:
3335 derived = loads(data, encoding='latin1')
3336 self.assertEqual(derived, expected)
3337
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003338 def test_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003339 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003340 cls = self.theclass
3341 self.assertTrue(cls(1))
3342 self.assertTrue(cls(0, 1))
3343 self.assertTrue(cls(0, 0, 1))
3344 self.assertTrue(cls(0, 0, 0, 1))
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003345 self.assertTrue(cls(0))
3346 self.assertTrue(cls())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003347
3348 def test_replace(self):
3349 cls = self.theclass
3350 args = [1, 2, 3, 4]
3351 base = cls(*args)
3352 self.assertEqual(base, base.replace())
3353
3354 i = 0
3355 for name, newval in (("hour", 5),
3356 ("minute", 6),
3357 ("second", 7),
3358 ("microsecond", 8)):
3359 newargs = args[:]
3360 newargs[i] = newval
3361 expected = cls(*newargs)
3362 got = base.replace(**{name: newval})
3363 self.assertEqual(expected, got)
3364 i += 1
3365
3366 # Out of bounds.
3367 base = cls(1)
3368 self.assertRaises(ValueError, base.replace, hour=24)
3369 self.assertRaises(ValueError, base.replace, minute=-1)
3370 self.assertRaises(ValueError, base.replace, second=100)
3371 self.assertRaises(ValueError, base.replace, microsecond=1000000)
3372
Paul Ganssle191e9932017-11-09 16:34:29 -05003373 def test_subclass_replace(self):
3374 class TimeSubclass(self.theclass):
3375 pass
3376
3377 ctime = TimeSubclass(12, 30)
3378 self.assertIs(type(ctime.replace(hour=10)), TimeSubclass)
3379
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003380 def test_subclass_time(self):
3381
3382 class C(self.theclass):
3383 theAnswer = 42
3384
3385 def __new__(cls, *args, **kws):
3386 temp = kws.copy()
3387 extra = temp.pop('extra')
3388 result = self.theclass.__new__(cls, *args, **temp)
3389 result.extra = extra
3390 return result
3391
3392 def newmeth(self, start):
3393 return start + self.hour + self.second
3394
3395 args = 4, 5, 6
3396
3397 dt1 = self.theclass(*args)
3398 dt2 = C(*args, **{'extra': 7})
3399
3400 self.assertEqual(dt2.__class__, C)
3401 self.assertEqual(dt2.theAnswer, 42)
3402 self.assertEqual(dt2.extra, 7)
3403 self.assertEqual(dt1.isoformat(), dt2.isoformat())
3404 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
3405
3406 def test_backdoor_resistance(self):
3407 # see TestDate.test_backdoor_resistance().
3408 base = '2:59.0'
3409 for hour_byte in ' ', '9', chr(24), '\xff':
3410 self.assertRaises(TypeError, self.theclass,
3411 hour_byte + base[1:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04003412 # Good bytes, but bad tzinfo:
3413 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
3414 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003415
3416# A mixin for classes with a tzinfo= argument. Subclasses must define
Martin Panter46f50722016-05-26 05:35:26 +00003417# theclass as a class attribute, and theclass(1, 1, 1, tzinfo=whatever)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003418# must be legit (which is true for time and datetime).
3419class TZInfoBase:
3420
3421 def test_argument_passing(self):
3422 cls = self.theclass
3423 # A datetime passes itself on, a time passes None.
3424 class introspective(tzinfo):
3425 def tzname(self, dt): return dt and "real" or "none"
3426 def utcoffset(self, dt):
3427 return timedelta(minutes = dt and 42 or -42)
3428 dst = utcoffset
3429
3430 obj = cls(1, 2, 3, tzinfo=introspective())
3431
3432 expected = cls is time and "none" or "real"
3433 self.assertEqual(obj.tzname(), expected)
3434
3435 expected = timedelta(minutes=(cls is time and -42 or 42))
3436 self.assertEqual(obj.utcoffset(), expected)
3437 self.assertEqual(obj.dst(), expected)
3438
3439 def test_bad_tzinfo_classes(self):
3440 cls = self.theclass
3441 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
3442
3443 class NiceTry(object):
3444 def __init__(self): pass
3445 def utcoffset(self, dt): pass
3446 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
3447
3448 class BetterTry(tzinfo):
3449 def __init__(self): pass
3450 def utcoffset(self, dt): pass
3451 b = BetterTry()
3452 t = cls(1, 1, 1, tzinfo=b)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003453 self.assertIs(t.tzinfo, b)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003454
3455 def test_utc_offset_out_of_bounds(self):
3456 class Edgy(tzinfo):
3457 def __init__(self, offset):
3458 self.offset = timedelta(minutes=offset)
3459 def utcoffset(self, dt):
3460 return self.offset
3461
3462 cls = self.theclass
3463 for offset, legit in ((-1440, False),
3464 (-1439, True),
3465 (1439, True),
3466 (1440, False)):
3467 if cls is time:
3468 t = cls(1, 2, 3, tzinfo=Edgy(offset))
3469 elif cls is datetime:
3470 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
3471 else:
3472 assert 0, "impossible"
3473 if legit:
3474 aofs = abs(offset)
3475 h, m = divmod(aofs, 60)
3476 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
3477 if isinstance(t, datetime):
3478 t = t.timetz()
3479 self.assertEqual(str(t), "01:02:03" + tag)
3480 else:
3481 self.assertRaises(ValueError, str, t)
3482
3483 def test_tzinfo_classes(self):
3484 cls = self.theclass
3485 class C1(tzinfo):
3486 def utcoffset(self, dt): return None
3487 def dst(self, dt): return None
3488 def tzname(self, dt): return None
3489 for t in (cls(1, 1, 1),
3490 cls(1, 1, 1, tzinfo=None),
3491 cls(1, 1, 1, tzinfo=C1())):
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003492 self.assertIsNone(t.utcoffset())
3493 self.assertIsNone(t.dst())
3494 self.assertIsNone(t.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003495
3496 class C3(tzinfo):
3497 def utcoffset(self, dt): return timedelta(minutes=-1439)
3498 def dst(self, dt): return timedelta(minutes=1439)
3499 def tzname(self, dt): return "aname"
3500 t = cls(1, 1, 1, tzinfo=C3())
3501 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
3502 self.assertEqual(t.dst(), timedelta(minutes=1439))
3503 self.assertEqual(t.tzname(), "aname")
3504
3505 # Wrong types.
3506 class C4(tzinfo):
3507 def utcoffset(self, dt): return "aname"
3508 def dst(self, dt): return 7
3509 def tzname(self, dt): return 0
3510 t = cls(1, 1, 1, tzinfo=C4())
3511 self.assertRaises(TypeError, t.utcoffset)
3512 self.assertRaises(TypeError, t.dst)
3513 self.assertRaises(TypeError, t.tzname)
3514
3515 # Offset out of range.
3516 class C6(tzinfo):
3517 def utcoffset(self, dt): return timedelta(hours=-24)
3518 def dst(self, dt): return timedelta(hours=24)
3519 t = cls(1, 1, 1, tzinfo=C6())
3520 self.assertRaises(ValueError, t.utcoffset)
3521 self.assertRaises(ValueError, t.dst)
3522
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04003523 # Not a whole number of seconds.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003524 class C7(tzinfo):
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04003525 def utcoffset(self, dt): return timedelta(microseconds=61)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003526 def dst(self, dt): return timedelta(microseconds=-81)
3527 t = cls(1, 1, 1, tzinfo=C7())
Alexander Belopolsky018d3532017-07-31 10:26:50 -04003528 self.assertEqual(t.utcoffset(), timedelta(microseconds=61))
3529 self.assertEqual(t.dst(), timedelta(microseconds=-81))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003530
3531 def test_aware_compare(self):
3532 cls = self.theclass
3533
3534 # Ensure that utcoffset() gets ignored if the comparands have
3535 # the same tzinfo member.
3536 class OperandDependentOffset(tzinfo):
3537 def utcoffset(self, t):
3538 if t.minute < 10:
3539 # d0 and d1 equal after adjustment
3540 return timedelta(minutes=t.minute)
3541 else:
3542 # d2 off in the weeds
3543 return timedelta(minutes=59)
3544
3545 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
3546 d0 = base.replace(minute=3)
3547 d1 = base.replace(minute=9)
3548 d2 = base.replace(minute=11)
3549 for x in d0, d1, d2:
3550 for y in d0, d1, d2:
3551 for op in lt, le, gt, ge, eq, ne:
3552 got = op(x, y)
3553 expected = op(x.minute, y.minute)
3554 self.assertEqual(got, expected)
3555
3556 # However, if they're different members, uctoffset is not ignored.
penguindustin96466302019-05-06 14:57:17 -04003557 # Note that a time can't actually have an operand-dependent offset,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003558 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
3559 # so skip this test for time.
3560 if cls is not time:
3561 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
3562 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
3563 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
3564 for x in d0, d1, d2:
3565 for y in d0, d1, d2:
3566 got = (x > y) - (x < y)
3567 if (x is d0 or x is d1) and (y is d0 or y is d1):
3568 expected = 0
3569 elif x is y is d2:
3570 expected = 0
3571 elif x is d2:
3572 expected = -1
3573 else:
3574 assert y is d2
3575 expected = 1
3576 self.assertEqual(got, expected)
3577
3578
3579# Testing time objects with a non-None tzinfo.
3580class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
3581 theclass = time
3582
3583 def test_empty(self):
3584 t = self.theclass()
3585 self.assertEqual(t.hour, 0)
3586 self.assertEqual(t.minute, 0)
3587 self.assertEqual(t.second, 0)
3588 self.assertEqual(t.microsecond, 0)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003589 self.assertIsNone(t.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003590
3591 def test_zones(self):
3592 est = FixedOffset(-300, "EST", 1)
3593 utc = FixedOffset(0, "UTC", -2)
3594 met = FixedOffset(60, "MET", 3)
3595 t1 = time( 7, 47, tzinfo=est)
3596 t2 = time(12, 47, tzinfo=utc)
3597 t3 = time(13, 47, tzinfo=met)
3598 t4 = time(microsecond=40)
3599 t5 = time(microsecond=40, tzinfo=utc)
3600
3601 self.assertEqual(t1.tzinfo, est)
3602 self.assertEqual(t2.tzinfo, utc)
3603 self.assertEqual(t3.tzinfo, met)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003604 self.assertIsNone(t4.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003605 self.assertEqual(t5.tzinfo, utc)
3606
3607 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
3608 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
3609 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003610 self.assertIsNone(t4.utcoffset())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003611 self.assertRaises(TypeError, t1.utcoffset, "no args")
3612
3613 self.assertEqual(t1.tzname(), "EST")
3614 self.assertEqual(t2.tzname(), "UTC")
3615 self.assertEqual(t3.tzname(), "MET")
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003616 self.assertIsNone(t4.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003617 self.assertRaises(TypeError, t1.tzname, "no args")
3618
3619 self.assertEqual(t1.dst(), timedelta(minutes=1))
3620 self.assertEqual(t2.dst(), timedelta(minutes=-2))
3621 self.assertEqual(t3.dst(), timedelta(minutes=3))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003622 self.assertIsNone(t4.dst())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003623 self.assertRaises(TypeError, t1.dst, "no args")
3624
3625 self.assertEqual(hash(t1), hash(t2))
3626 self.assertEqual(hash(t1), hash(t3))
3627 self.assertEqual(hash(t2), hash(t3))
3628
3629 self.assertEqual(t1, t2)
3630 self.assertEqual(t1, t3)
3631 self.assertEqual(t2, t3)
Alexander Belopolsky08313822012-06-15 20:19:47 -04003632 self.assertNotEqual(t4, t5) # mixed tz-aware & naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003633 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
3634 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
3635
3636 self.assertEqual(str(t1), "07:47:00-05:00")
3637 self.assertEqual(str(t2), "12:47:00+00:00")
3638 self.assertEqual(str(t3), "13:47:00+01:00")
3639 self.assertEqual(str(t4), "00:00:00.000040")
3640 self.assertEqual(str(t5), "00:00:00.000040+00:00")
3641
3642 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
3643 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
3644 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
3645 self.assertEqual(t4.isoformat(), "00:00:00.000040")
3646 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
3647
3648 d = 'datetime.time'
3649 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
3650 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
3651 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
3652 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
3653 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
3654
3655 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
3656 "07:47:00 %Z=EST %z=-0500")
3657 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
3658 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
3659
3660 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
3661 t1 = time(23, 59, tzinfo=yuck)
3662 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
3663 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
3664
3665 # Check that an invalid tzname result raises an exception.
3666 class Badtzname(tzinfo):
Alexander Belopolskye239d232010-12-08 23:31:48 +00003667 tz = 42
3668 def tzname(self, dt): return self.tz
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003669 t = time(2, 3, 4, tzinfo=Badtzname())
3670 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
3671 self.assertRaises(TypeError, t.strftime, "%Z")
3672
Alexander Belopolskye239d232010-12-08 23:31:48 +00003673 # Issue #6697:
Utkarsh Upadhyay287c5592017-07-21 02:14:54 +02003674 if '_Fast' in self.__class__.__name__:
Alexander Belopolskye239d232010-12-08 23:31:48 +00003675 Badtzname.tz = '\ud800'
3676 self.assertRaises(ValueError, t.strftime, "%Z")
3677
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003678 def test_hash_edge_cases(self):
3679 # Offsets that overflow a basic time.
3680 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
3681 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
3682 self.assertEqual(hash(t1), hash(t2))
3683
3684 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
3685 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
3686 self.assertEqual(hash(t1), hash(t2))
3687
3688 def test_pickling(self):
3689 # Try one without a tzinfo.
3690 args = 20, 59, 16, 64**2
3691 orig = self.theclass(*args)
3692 for pickler, unpickler, proto in pickle_choices:
3693 green = pickler.dumps(orig, proto)
3694 derived = unpickler.loads(green)
3695 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003696 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003697
3698 # Try one with a tzinfo.
3699 tinfo = PicklableFixedOffset(-300, 'cookie')
3700 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
3701 for pickler, unpickler, proto in pickle_choices:
3702 green = pickler.dumps(orig, proto)
3703 derived = unpickler.loads(green)
3704 self.assertEqual(orig, derived)
3705 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3706 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3707 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003708 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003709
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02003710 def test_compat_unpickle(self):
3711 tests = [
3712 b"cdatetime\ntime\n(S'\\x05\\x06\\x07\\x01\\xe2@'\n"
3713 b"ctest.datetimetester\nPicklableFixedOffset\n(tR"
3714 b"(dS'_FixedOffset__offset'\ncdatetime\ntimedelta\n"
3715 b"(I-1\nI68400\nI0\ntRs"
3716 b"S'_FixedOffset__dstoffset'\nNs"
3717 b"S'_FixedOffset__name'\nS'cookie'\nsbtR.",
3718
3719 b'cdatetime\ntime\n(U\x06\x05\x06\x07\x01\xe2@'
3720 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
3721 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
3722 b'(J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00tR'
3723 b'U\x17_FixedOffset__dstoffsetN'
3724 b'U\x12_FixedOffset__nameU\x06cookieubtR.',
3725
3726 b'\x80\x02cdatetime\ntime\nU\x06\x05\x06\x07\x01\xe2@'
3727 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
3728 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
3729 b'J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00\x87R'
3730 b'U\x17_FixedOffset__dstoffsetN'
3731 b'U\x12_FixedOffset__nameU\x06cookieub\x86R.',
3732 ]
3733
3734 tinfo = PicklableFixedOffset(-300, 'cookie')
3735 expected = self.theclass(5, 6, 7, 123456, tzinfo=tinfo)
3736 for data in tests:
3737 for loads in pickle_loads:
3738 derived = loads(data, encoding='latin1')
3739 self.assertEqual(derived, expected, repr(data))
3740 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3741 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3742 self.assertEqual(derived.tzname(), 'cookie')
3743
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003744 def test_more_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003745 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003746 cls = self.theclass
3747
3748 t = cls(0, tzinfo=FixedOffset(-300, ""))
3749 self.assertTrue(t)
3750
3751 t = cls(5, tzinfo=FixedOffset(-300, ""))
3752 self.assertTrue(t)
3753
3754 t = cls(5, tzinfo=FixedOffset(300, ""))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003755 self.assertTrue(t)
3756
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003757 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
3758 self.assertTrue(t)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003759
3760 def test_replace(self):
3761 cls = self.theclass
3762 z100 = FixedOffset(100, "+100")
3763 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3764 args = [1, 2, 3, 4, z100]
3765 base = cls(*args)
3766 self.assertEqual(base, base.replace())
3767
3768 i = 0
3769 for name, newval in (("hour", 5),
3770 ("minute", 6),
3771 ("second", 7),
3772 ("microsecond", 8),
3773 ("tzinfo", zm200)):
3774 newargs = args[:]
3775 newargs[i] = newval
3776 expected = cls(*newargs)
3777 got = base.replace(**{name: newval})
3778 self.assertEqual(expected, got)
3779 i += 1
3780
3781 # Ensure we can get rid of a tzinfo.
3782 self.assertEqual(base.tzname(), "+100")
3783 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003784 self.assertIsNone(base2.tzinfo)
3785 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003786
3787 # Ensure we can add one.
3788 base3 = base2.replace(tzinfo=z100)
3789 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003790 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003791
3792 # Out of bounds.
3793 base = cls(1)
3794 self.assertRaises(ValueError, base.replace, hour=24)
3795 self.assertRaises(ValueError, base.replace, minute=-1)
3796 self.assertRaises(ValueError, base.replace, second=100)
3797 self.assertRaises(ValueError, base.replace, microsecond=1000000)
3798
3799 def test_mixed_compare(self):
Serhiy Storchaka17e52642019-08-04 12:38:46 +03003800 t1 = self.theclass(1, 2, 3)
3801 t2 = self.theclass(1, 2, 3)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003802 self.assertEqual(t1, t2)
3803 t2 = t2.replace(tzinfo=None)
3804 self.assertEqual(t1, t2)
3805 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3806 self.assertEqual(t1, t2)
3807 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04003808 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003809
3810 # In time w/ identical tzinfo objects, utcoffset is ignored.
3811 class Varies(tzinfo):
3812 def __init__(self):
3813 self.offset = timedelta(minutes=22)
3814 def utcoffset(self, t):
3815 self.offset += timedelta(minutes=1)
3816 return self.offset
3817
3818 v = Varies()
3819 t1 = t2.replace(tzinfo=v)
3820 t2 = t2.replace(tzinfo=v)
3821 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3822 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3823 self.assertEqual(t1, t2)
3824
3825 # But if they're not identical, it isn't ignored.
3826 t2 = t2.replace(tzinfo=Varies())
3827 self.assertTrue(t1 < t2) # t1's offset counter still going up
3828
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003829 def test_fromisoformat(self):
3830 time_examples = [
3831 (0, 0, 0, 0),
3832 (23, 59, 59, 999999),
3833 ]
3834
3835 hh = (9, 12, 20)
3836 mm = (5, 30)
3837 ss = (4, 45)
3838 usec = (0, 245000, 678901)
3839
3840 time_examples += list(itertools.product(hh, mm, ss, usec))
3841
3842 tzinfos = [None, timezone.utc,
3843 timezone(timedelta(hours=2)),
3844 timezone(timedelta(hours=6, minutes=27))]
3845
3846 for ttup in time_examples:
3847 for tzi in tzinfos:
3848 t = self.theclass(*ttup, tzinfo=tzi)
3849 tstr = t.isoformat()
3850
3851 with self.subTest(tstr=tstr):
3852 t_rt = self.theclass.fromisoformat(tstr)
3853 self.assertEqual(t, t_rt)
3854
3855 def test_fromisoformat_timezone(self):
3856 base_time = self.theclass(12, 30, 45, 217456)
3857
3858 tzoffsets = [
3859 timedelta(hours=5), timedelta(hours=2),
3860 timedelta(hours=6, minutes=27),
3861 timedelta(hours=12, minutes=32, seconds=30),
3862 timedelta(hours=2, minutes=4, seconds=9, microseconds=123456)
3863 ]
3864
3865 tzoffsets += [-1 * td for td in tzoffsets]
3866
3867 tzinfos = [None, timezone.utc,
3868 timezone(timedelta(hours=0))]
3869
3870 tzinfos += [timezone(td) for td in tzoffsets]
3871
3872 for tzi in tzinfos:
3873 t = base_time.replace(tzinfo=tzi)
3874 tstr = t.isoformat()
3875
3876 with self.subTest(tstr=tstr):
3877 t_rt = self.theclass.fromisoformat(tstr)
3878 assert t == t_rt, t_rt
3879
3880 def test_fromisoformat_timespecs(self):
3881 time_bases = [
3882 (8, 17, 45, 123456),
3883 (8, 17, 45, 0)
3884 ]
3885
3886 tzinfos = [None, timezone.utc,
3887 timezone(timedelta(hours=-5)),
3888 timezone(timedelta(hours=2)),
3889 timezone(timedelta(hours=6, minutes=27))]
3890
3891 timespecs = ['hours', 'minutes', 'seconds',
3892 'milliseconds', 'microseconds']
3893
3894 for ip, ts in enumerate(timespecs):
3895 for tzi in tzinfos:
3896 for t_tuple in time_bases:
3897 if ts == 'milliseconds':
3898 new_microseconds = 1000 * (t_tuple[-1] // 1000)
3899 t_tuple = t_tuple[0:-1] + (new_microseconds,)
3900
3901 t = self.theclass(*(t_tuple[0:(1 + ip)]), tzinfo=tzi)
3902 tstr = t.isoformat(timespec=ts)
3903 with self.subTest(tstr=tstr):
3904 t_rt = self.theclass.fromisoformat(tstr)
3905 self.assertEqual(t, t_rt)
3906
3907 def test_fromisoformat_fails(self):
3908 bad_strs = [
3909 '', # Empty string
Paul Ganssle096329f2018-08-23 11:06:20 -04003910 '12\ud80000', # Invalid separator - surrogate char
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003911 '12:', # Ends on a separator
3912 '12:30:', # Ends on a separator
3913 '12:30:15.', # Ends on a separator
3914 '1', # Incomplete hours
3915 '12:3', # Incomplete minutes
3916 '12:30:1', # Incomplete seconds
3917 '1a:30:45.334034', # Invalid character in hours
3918 '12:a0:45.334034', # Invalid character in minutes
3919 '12:30:a5.334034', # Invalid character in seconds
3920 '12:30:45.1234', # Too many digits for milliseconds
3921 '12:30:45.1234567', # Too many digits for microseconds
3922 '12:30:45.123456+24:30', # Invalid time zone offset
3923 '12:30:45.123456-24:30', # Invalid negative offset
3924 '12:30:45', # Uses full-width unicode colons
3925 '12:30:45․123456', # Uses \u2024 in place of decimal point
3926 '12:30:45a', # Extra at tend of basic time
3927 '12:30:45.123a', # Extra at end of millisecond time
3928 '12:30:45.123456a', # Extra at end of microsecond time
3929 '12:30:45.123456+12:00:30a', # Extra at end of full time
3930 ]
3931
3932 for bad_str in bad_strs:
3933 with self.subTest(bad_str=bad_str):
3934 with self.assertRaises(ValueError):
3935 self.theclass.fromisoformat(bad_str)
3936
3937 def test_fromisoformat_fails_typeerror(self):
3938 # Test the fromisoformat fails when passed the wrong type
3939 import io
3940
3941 bad_types = [b'12:30:45', None, io.StringIO('12:30:45')]
3942
3943 for bad_type in bad_types:
3944 with self.assertRaises(TypeError):
3945 self.theclass.fromisoformat(bad_type)
3946
3947 def test_fromisoformat_subclass(self):
3948 class TimeSubclass(self.theclass):
3949 pass
3950
3951 tsc = TimeSubclass(12, 14, 45, 203745, tzinfo=timezone.utc)
3952 tsc_rt = TimeSubclass.fromisoformat(tsc.isoformat())
3953
3954 self.assertEqual(tsc, tsc_rt)
3955 self.assertIsInstance(tsc_rt, TimeSubclass)
3956
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003957 def test_subclass_timetz(self):
3958
3959 class C(self.theclass):
3960 theAnswer = 42
3961
3962 def __new__(cls, *args, **kws):
3963 temp = kws.copy()
3964 extra = temp.pop('extra')
3965 result = self.theclass.__new__(cls, *args, **temp)
3966 result.extra = extra
3967 return result
3968
3969 def newmeth(self, start):
3970 return start + self.hour + self.second
3971
3972 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3973
3974 dt1 = self.theclass(*args)
3975 dt2 = C(*args, **{'extra': 7})
3976
3977 self.assertEqual(dt2.__class__, C)
3978 self.assertEqual(dt2.theAnswer, 42)
3979 self.assertEqual(dt2.extra, 7)
3980 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3981 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
3982
3983
3984# Testing datetime objects with a non-None tzinfo.
3985
3986class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
3987 theclass = datetime
3988
3989 def test_trivial(self):
3990 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
3991 self.assertEqual(dt.year, 1)
3992 self.assertEqual(dt.month, 2)
3993 self.assertEqual(dt.day, 3)
3994 self.assertEqual(dt.hour, 4)
3995 self.assertEqual(dt.minute, 5)
3996 self.assertEqual(dt.second, 6)
3997 self.assertEqual(dt.microsecond, 7)
3998 self.assertEqual(dt.tzinfo, None)
3999
4000 def test_even_more_compare(self):
4001 # The test_compare() and test_more_compare() inherited from TestDate
4002 # and TestDateTime covered non-tzinfo cases.
4003
4004 # Smallest possible after UTC adjustment.
4005 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
4006 # Largest possible after UTC adjustment.
4007 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
4008 tzinfo=FixedOffset(-1439, ""))
4009
4010 # Make sure those compare correctly, and w/o overflow.
4011 self.assertTrue(t1 < t2)
4012 self.assertTrue(t1 != t2)
4013 self.assertTrue(t2 > t1)
4014
4015 self.assertEqual(t1, t1)
4016 self.assertEqual(t2, t2)
4017
4018 # Equal afer adjustment.
4019 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
4020 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
4021 self.assertEqual(t1, t2)
4022
4023 # Change t1 not to subtract a minute, and t1 should be larger.
4024 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
4025 self.assertTrue(t1 > t2)
4026
4027 # Change t1 to subtract 2 minutes, and t1 should be smaller.
4028 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
4029 self.assertTrue(t1 < t2)
4030
4031 # Back to the original t1, but make seconds resolve it.
4032 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
4033 second=1)
4034 self.assertTrue(t1 > t2)
4035
4036 # Likewise, but make microseconds resolve it.
4037 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
4038 microsecond=1)
4039 self.assertTrue(t1 > t2)
4040
Alexander Belopolsky08313822012-06-15 20:19:47 -04004041 # Make t2 naive and it should differ.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004042 t2 = self.theclass.min
Alexander Belopolsky08313822012-06-15 20:19:47 -04004043 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004044 self.assertEqual(t2, t2)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04004045 # and > comparison should fail
4046 with self.assertRaises(TypeError):
4047 t1 > t2
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004048
4049 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
4050 class Naive(tzinfo):
4051 def utcoffset(self, dt): return None
4052 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
Alexander Belopolsky08313822012-06-15 20:19:47 -04004053 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004054 self.assertEqual(t2, t2)
4055
4056 # OTOH, it's OK to compare two of these mixing the two ways of being
4057 # naive.
4058 t1 = self.theclass(5, 6, 7)
4059 self.assertEqual(t1, t2)
4060
4061 # Try a bogus uctoffset.
4062 class Bogus(tzinfo):
4063 def utcoffset(self, dt):
4064 return timedelta(minutes=1440) # out of bounds
4065 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
4066 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
4067 self.assertRaises(ValueError, lambda: t1 == t2)
4068
4069 def test_pickling(self):
4070 # Try one without a tzinfo.
4071 args = 6, 7, 23, 20, 59, 1, 64**2
4072 orig = self.theclass(*args)
4073 for pickler, unpickler, proto in pickle_choices:
4074 green = pickler.dumps(orig, proto)
4075 derived = unpickler.loads(green)
4076 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02004077 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004078
4079 # Try one with a tzinfo.
4080 tinfo = PicklableFixedOffset(-300, 'cookie')
4081 orig = self.theclass(*args, **{'tzinfo': tinfo})
4082 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
4083 for pickler, unpickler, proto in pickle_choices:
4084 green = pickler.dumps(orig, proto)
4085 derived = unpickler.loads(green)
4086 self.assertEqual(orig, derived)
4087 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
4088 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
4089 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02004090 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004091
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02004092 def test_compat_unpickle(self):
4093 tests = [
4094 b'cdatetime\ndatetime\n'
4095 b"(S'\\x07\\xdf\\x0b\\x1b\\x14;\\x01\\x01\\xe2@'\n"
4096 b'ctest.datetimetester\nPicklableFixedOffset\n(tR'
4097 b"(dS'_FixedOffset__offset'\ncdatetime\ntimedelta\n"
4098 b'(I-1\nI68400\nI0\ntRs'
4099 b"S'_FixedOffset__dstoffset'\nNs"
4100 b"S'_FixedOffset__name'\nS'cookie'\nsbtR.",
4101
4102 b'cdatetime\ndatetime\n'
4103 b'(U\n\x07\xdf\x0b\x1b\x14;\x01\x01\xe2@'
4104 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
4105 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
4106 b'(J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00tR'
4107 b'U\x17_FixedOffset__dstoffsetN'
4108 b'U\x12_FixedOffset__nameU\x06cookieubtR.',
4109
4110 b'\x80\x02cdatetime\ndatetime\n'
4111 b'U\n\x07\xdf\x0b\x1b\x14;\x01\x01\xe2@'
4112 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
4113 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
4114 b'J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00\x87R'
4115 b'U\x17_FixedOffset__dstoffsetN'
4116 b'U\x12_FixedOffset__nameU\x06cookieub\x86R.',
4117 ]
4118 args = 2015, 11, 27, 20, 59, 1, 123456
4119 tinfo = PicklableFixedOffset(-300, 'cookie')
4120 expected = self.theclass(*args, **{'tzinfo': tinfo})
4121 for data in tests:
4122 for loads in pickle_loads:
4123 derived = loads(data, encoding='latin1')
4124 self.assertEqual(derived, expected)
4125 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
4126 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
4127 self.assertEqual(derived.tzname(), 'cookie')
4128
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004129 def test_extreme_hashes(self):
4130 # If an attempt is made to hash these via subtracting the offset
4131 # then hashing a datetime object, OverflowError results. The
4132 # Python implementation used to blow up here.
4133 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
4134 hash(t)
4135 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
4136 tzinfo=FixedOffset(-1439, ""))
4137 hash(t)
4138
4139 # OTOH, an OOB offset should blow up.
4140 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
4141 self.assertRaises(ValueError, hash, t)
4142
4143 def test_zones(self):
4144 est = FixedOffset(-300, "EST")
4145 utc = FixedOffset(0, "UTC")
4146 met = FixedOffset(60, "MET")
4147 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
4148 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
4149 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
4150 self.assertEqual(t1.tzinfo, est)
4151 self.assertEqual(t2.tzinfo, utc)
4152 self.assertEqual(t3.tzinfo, met)
4153 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
4154 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
4155 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
4156 self.assertEqual(t1.tzname(), "EST")
4157 self.assertEqual(t2.tzname(), "UTC")
4158 self.assertEqual(t3.tzname(), "MET")
4159 self.assertEqual(hash(t1), hash(t2))
4160 self.assertEqual(hash(t1), hash(t3))
4161 self.assertEqual(hash(t2), hash(t3))
4162 self.assertEqual(t1, t2)
4163 self.assertEqual(t1, t3)
4164 self.assertEqual(t2, t3)
4165 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
4166 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
4167 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
4168 d = 'datetime.datetime(2002, 3, 19, '
4169 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
4170 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
4171 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
4172
4173 def test_combine(self):
4174 met = FixedOffset(60, "MET")
4175 d = date(2002, 3, 4)
4176 tz = time(18, 45, 3, 1234, tzinfo=met)
4177 dt = datetime.combine(d, tz)
4178 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
4179 tzinfo=met))
4180
4181 def test_extract(self):
4182 met = FixedOffset(60, "MET")
4183 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
4184 self.assertEqual(dt.date(), date(2002, 3, 4))
4185 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
4186 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
4187
4188 def test_tz_aware_arithmetic(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004189 now = self.theclass.now()
4190 tz55 = FixedOffset(-330, "west 5:30")
4191 timeaware = now.time().replace(tzinfo=tz55)
4192 nowaware = self.theclass.combine(now.date(), timeaware)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004193 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004194 self.assertEqual(nowaware.timetz(), timeaware)
4195
4196 # Can't mix aware and non-aware.
4197 self.assertRaises(TypeError, lambda: now - nowaware)
4198 self.assertRaises(TypeError, lambda: nowaware - now)
4199
4200 # And adding datetime's doesn't make sense, aware or not.
4201 self.assertRaises(TypeError, lambda: now + nowaware)
4202 self.assertRaises(TypeError, lambda: nowaware + now)
4203 self.assertRaises(TypeError, lambda: nowaware + nowaware)
4204
4205 # Subtracting should yield 0.
4206 self.assertEqual(now - now, timedelta(0))
4207 self.assertEqual(nowaware - nowaware, timedelta(0))
4208
4209 # Adding a delta should preserve tzinfo.
4210 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
4211 nowawareplus = nowaware + delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004212 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004213 nowawareplus2 = delta + nowaware
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004214 self.assertIs(nowawareplus2.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004215 self.assertEqual(nowawareplus, nowawareplus2)
4216
4217 # that - delta should be what we started with, and that - what we
4218 # started with should be delta.
4219 diff = nowawareplus - delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004220 self.assertIs(diff.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004221 self.assertEqual(nowaware, diff)
4222 self.assertRaises(TypeError, lambda: delta - nowawareplus)
4223 self.assertEqual(nowawareplus - nowaware, delta)
4224
4225 # Make up a random timezone.
4226 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
4227 # Attach it to nowawareplus.
4228 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004229 self.assertIs(nowawareplus.tzinfo, tzr)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004230 # Make sure the difference takes the timezone adjustments into account.
4231 got = nowaware - nowawareplus
4232 # Expected: (nowaware base - nowaware offset) -
4233 # (nowawareplus base - nowawareplus offset) =
4234 # (nowaware base - nowawareplus base) +
4235 # (nowawareplus offset - nowaware offset) =
4236 # -delta + nowawareplus offset - nowaware offset
4237 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
4238 self.assertEqual(got, expected)
4239
4240 # Try max possible difference.
4241 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
4242 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
4243 tzinfo=FixedOffset(-1439, "max"))
4244 maxdiff = max - min
4245 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
4246 timedelta(minutes=2*1439))
4247 # Different tzinfo, but the same offset
4248 tza = timezone(HOUR, 'A')
4249 tzb = timezone(HOUR, 'B')
4250 delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
4251 self.assertEqual(delta, self.theclass.min - self.theclass.max)
4252
4253 def test_tzinfo_now(self):
4254 meth = self.theclass.now
4255 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4256 base = meth()
4257 # Try with and without naming the keyword.
4258 off42 = FixedOffset(42, "42")
4259 another = meth(off42)
4260 again = meth(tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004261 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004262 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
4263 # Bad argument with and w/o naming the keyword.
4264 self.assertRaises(TypeError, meth, 16)
4265 self.assertRaises(TypeError, meth, tzinfo=16)
4266 # Bad keyword name.
4267 self.assertRaises(TypeError, meth, tinfo=off42)
4268 # Too many args.
4269 self.assertRaises(TypeError, meth, off42, off42)
4270
4271 # We don't know which time zone we're in, and don't have a tzinfo
4272 # class to represent it, so seeing whether a tz argument actually
4273 # does a conversion is tricky.
4274 utc = FixedOffset(0, "utc", 0)
4275 for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
4276 timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
4277 for dummy in range(3):
4278 now = datetime.now(weirdtz)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004279 self.assertIs(now.tzinfo, weirdtz)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004280 utcnow = datetime.utcnow().replace(tzinfo=utc)
4281 now2 = utcnow.astimezone(weirdtz)
4282 if abs(now - now2) < timedelta(seconds=30):
4283 break
4284 # Else the code is broken, or more than 30 seconds passed between
4285 # calls; assuming the latter, just try again.
4286 else:
4287 # Three strikes and we're out.
4288 self.fail("utcnow(), now(tz), or astimezone() may be broken")
4289
4290 def test_tzinfo_fromtimestamp(self):
4291 import time
4292 meth = self.theclass.fromtimestamp
4293 ts = time.time()
4294 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4295 base = meth(ts)
4296 # Try with and without naming the keyword.
4297 off42 = FixedOffset(42, "42")
4298 another = meth(ts, off42)
4299 again = meth(ts, tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004300 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004301 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
4302 # Bad argument with and w/o naming the keyword.
4303 self.assertRaises(TypeError, meth, ts, 16)
4304 self.assertRaises(TypeError, meth, ts, tzinfo=16)
4305 # Bad keyword name.
4306 self.assertRaises(TypeError, meth, ts, tinfo=off42)
4307 # Too many args.
4308 self.assertRaises(TypeError, meth, ts, off42, off42)
4309 # Too few args.
4310 self.assertRaises(TypeError, meth)
4311
4312 # Try to make sure tz= actually does some conversion.
4313 timestamp = 1000000000
4314 utcdatetime = datetime.utcfromtimestamp(timestamp)
4315 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
4316 # But on some flavor of Mac, it's nowhere near that. So we can't have
4317 # any idea here what time that actually is, we can only test that
4318 # relative changes match.
4319 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
4320 tz = FixedOffset(utcoffset, "tz", 0)
4321 expected = utcdatetime + utcoffset
4322 got = datetime.fromtimestamp(timestamp, tz)
4323 self.assertEqual(expected, got.replace(tzinfo=None))
4324
4325 def test_tzinfo_utcnow(self):
4326 meth = self.theclass.utcnow
4327 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4328 base = meth()
4329 # Try with and without naming the keyword; for whatever reason,
4330 # utcnow() doesn't accept a tzinfo argument.
4331 off42 = FixedOffset(42, "42")
4332 self.assertRaises(TypeError, meth, off42)
4333 self.assertRaises(TypeError, meth, tzinfo=off42)
4334
4335 def test_tzinfo_utcfromtimestamp(self):
4336 import time
4337 meth = self.theclass.utcfromtimestamp
4338 ts = time.time()
4339 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4340 base = meth(ts)
4341 # Try with and without naming the keyword; for whatever reason,
4342 # utcfromtimestamp() doesn't accept a tzinfo argument.
4343 off42 = FixedOffset(42, "42")
4344 self.assertRaises(TypeError, meth, ts, off42)
4345 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
4346
4347 def test_tzinfo_timetuple(self):
4348 # TestDateTime tested most of this. datetime adds a twist to the
4349 # DST flag.
4350 class DST(tzinfo):
4351 def __init__(self, dstvalue):
4352 if isinstance(dstvalue, int):
4353 dstvalue = timedelta(minutes=dstvalue)
4354 self.dstvalue = dstvalue
4355 def dst(self, dt):
4356 return self.dstvalue
4357
4358 cls = self.theclass
4359 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
4360 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
4361 t = d.timetuple()
4362 self.assertEqual(1, t.tm_year)
4363 self.assertEqual(1, t.tm_mon)
4364 self.assertEqual(1, t.tm_mday)
4365 self.assertEqual(10, t.tm_hour)
4366 self.assertEqual(20, t.tm_min)
4367 self.assertEqual(30, t.tm_sec)
4368 self.assertEqual(0, t.tm_wday)
4369 self.assertEqual(1, t.tm_yday)
4370 self.assertEqual(flag, t.tm_isdst)
4371
4372 # dst() returns wrong type.
4373 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
4374
4375 # dst() at the edge.
4376 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
4377 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
4378
4379 # dst() out of range.
4380 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
4381 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
4382
4383 def test_utctimetuple(self):
4384 class DST(tzinfo):
4385 def __init__(self, dstvalue=0):
4386 if isinstance(dstvalue, int):
4387 dstvalue = timedelta(minutes=dstvalue)
4388 self.dstvalue = dstvalue
4389 def dst(self, dt):
4390 return self.dstvalue
4391
4392 cls = self.theclass
4393 # This can't work: DST didn't implement utcoffset.
4394 self.assertRaises(NotImplementedError,
4395 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
4396
4397 class UOFS(DST):
4398 def __init__(self, uofs, dofs=None):
4399 DST.__init__(self, dofs)
4400 self.uofs = timedelta(minutes=uofs)
4401 def utcoffset(self, dt):
4402 return self.uofs
4403
4404 for dstvalue in -33, 33, 0, None:
4405 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
4406 t = d.utctimetuple()
4407 self.assertEqual(d.year, t.tm_year)
4408 self.assertEqual(d.month, t.tm_mon)
4409 self.assertEqual(d.day, t.tm_mday)
4410 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
4411 self.assertEqual(13, t.tm_min)
4412 self.assertEqual(d.second, t.tm_sec)
4413 self.assertEqual(d.weekday(), t.tm_wday)
4414 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
4415 t.tm_yday)
4416 # Ensure tm_isdst is 0 regardless of what dst() says: DST
4417 # is never in effect for a UTC time.
4418 self.assertEqual(0, t.tm_isdst)
4419
4420 # For naive datetime, utctimetuple == timetuple except for isdst
4421 d = cls(1, 2, 3, 10, 20, 30, 40)
4422 t = d.utctimetuple()
4423 self.assertEqual(t[:-1], d.timetuple()[:-1])
4424 self.assertEqual(0, t.tm_isdst)
4425 # Same if utcoffset is None
4426 class NOFS(DST):
4427 def utcoffset(self, dt):
4428 return None
4429 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
4430 t = d.utctimetuple()
4431 self.assertEqual(t[:-1], d.timetuple()[:-1])
4432 self.assertEqual(0, t.tm_isdst)
4433 # Check that bad tzinfo is detected
4434 class BOFS(DST):
4435 def utcoffset(self, dt):
4436 return "EST"
4437 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
4438 self.assertRaises(TypeError, d.utctimetuple)
4439
4440 # Check that utctimetuple() is the same as
4441 # astimezone(utc).timetuple()
4442 d = cls(2010, 11, 13, 14, 15, 16, 171819)
4443 for tz in [timezone.min, timezone.utc, timezone.max]:
4444 dtz = d.replace(tzinfo=tz)
4445 self.assertEqual(dtz.utctimetuple()[:-1],
4446 dtz.astimezone(timezone.utc).timetuple()[:-1])
4447 # At the edges, UTC adjustment can produce years out-of-range
4448 # for a datetime object. Ensure that an OverflowError is
4449 # raised.
4450 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
4451 # That goes back 1 minute less than a full day.
4452 self.assertRaises(OverflowError, tiny.utctimetuple)
4453
4454 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
4455 # That goes forward 1 minute less than a full day.
4456 self.assertRaises(OverflowError, huge.utctimetuple)
4457 # More overflow cases
4458 tiny = cls.min.replace(tzinfo=timezone(MINUTE))
4459 self.assertRaises(OverflowError, tiny.utctimetuple)
4460 huge = cls.max.replace(tzinfo=timezone(-MINUTE))
4461 self.assertRaises(OverflowError, huge.utctimetuple)
4462
4463 def test_tzinfo_isoformat(self):
4464 zero = FixedOffset(0, "+00:00")
4465 plus = FixedOffset(220, "+03:40")
4466 minus = FixedOffset(-231, "-03:51")
4467 unknown = FixedOffset(None, "")
4468
4469 cls = self.theclass
4470 datestr = '0001-02-03'
4471 for ofs in None, zero, plus, minus, unknown:
4472 for us in 0, 987001:
4473 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
4474 timestr = '04:05:59' + (us and '.987001' or '')
4475 ofsstr = ofs is not None and d.tzname() or ''
4476 tailstr = timestr + ofsstr
4477 iso = d.isoformat()
4478 self.assertEqual(iso, datestr + 'T' + tailstr)
4479 self.assertEqual(iso, d.isoformat('T'))
4480 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
4481 self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
4482 self.assertEqual(str(d), datestr + ' ' + tailstr)
4483
4484 def test_replace(self):
4485 cls = self.theclass
4486 z100 = FixedOffset(100, "+100")
4487 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
4488 args = [1, 2, 3, 4, 5, 6, 7, z100]
4489 base = cls(*args)
4490 self.assertEqual(base, base.replace())
4491
4492 i = 0
4493 for name, newval in (("year", 2),
4494 ("month", 3),
4495 ("day", 4),
4496 ("hour", 5),
4497 ("minute", 6),
4498 ("second", 7),
4499 ("microsecond", 8),
4500 ("tzinfo", zm200)):
4501 newargs = args[:]
4502 newargs[i] = newval
4503 expected = cls(*newargs)
4504 got = base.replace(**{name: newval})
4505 self.assertEqual(expected, got)
4506 i += 1
4507
4508 # Ensure we can get rid of a tzinfo.
4509 self.assertEqual(base.tzname(), "+100")
4510 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004511 self.assertIsNone(base2.tzinfo)
4512 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004513
4514 # Ensure we can add one.
4515 base3 = base2.replace(tzinfo=z100)
4516 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004517 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004518
4519 # Out of bounds.
4520 base = cls(2000, 2, 29)
4521 self.assertRaises(ValueError, base.replace, year=2001)
4522
4523 def test_more_astimezone(self):
4524 # The inherited test_astimezone covered some trivial and error cases.
4525 fnone = FixedOffset(None, "None")
4526 f44m = FixedOffset(44, "44")
4527 fm5h = FixedOffset(-timedelta(hours=5), "m300")
4528
4529 dt = self.theclass.now(tz=f44m)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004530 self.assertIs(dt.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004531 # Replacing with degenerate tzinfo raises an exception.
4532 self.assertRaises(ValueError, dt.astimezone, fnone)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004533 # Replacing with same tzinfo makes no change.
4534 x = dt.astimezone(dt.tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004535 self.assertIs(x.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004536 self.assertEqual(x.date(), dt.date())
4537 self.assertEqual(x.time(), dt.time())
4538
4539 # Replacing with different tzinfo does adjust.
4540 got = dt.astimezone(fm5h)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004541 self.assertIs(got.tzinfo, fm5h)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004542 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
4543 expected = dt - dt.utcoffset() # in effect, convert to UTC
4544 expected += fm5h.utcoffset(dt) # and from there to local time
4545 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
4546 self.assertEqual(got.date(), expected.date())
4547 self.assertEqual(got.time(), expected.time())
4548 self.assertEqual(got.timetz(), expected.timetz())
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004549 self.assertIs(got.tzinfo, expected.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004550 self.assertEqual(got, expected)
4551
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004552 @support.run_with_tz('UTC')
4553 def test_astimezone_default_utc(self):
4554 dt = self.theclass.now(timezone.utc)
4555 self.assertEqual(dt.astimezone(None), dt)
4556 self.assertEqual(dt.astimezone(), dt)
4557
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004558 # Note that offset in TZ variable has the opposite sign to that
4559 # produced by %z directive.
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004560 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4561 def test_astimezone_default_eastern(self):
4562 dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
4563 local = dt.astimezone()
4564 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004565 self.assertEqual(local.strftime("%z %Z"), "-0500 EST")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004566 dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc)
4567 local = dt.astimezone()
4568 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004569 self.assertEqual(local.strftime("%z %Z"), "-0400 EDT")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004570
Alexander Belopolsky1dcf4f92016-03-25 15:42:59 -04004571 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4572 def test_astimezone_default_near_fold(self):
4573 # Issue #26616.
4574 u = datetime(2015, 11, 1, 5, tzinfo=timezone.utc)
4575 t = u.astimezone()
4576 s = t.astimezone()
4577 self.assertEqual(t.tzinfo, s.tzinfo)
4578
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004579 def test_aware_subtract(self):
4580 cls = self.theclass
4581
4582 # Ensure that utcoffset() is ignored when the operands have the
4583 # same tzinfo member.
4584 class OperandDependentOffset(tzinfo):
4585 def utcoffset(self, t):
4586 if t.minute < 10:
4587 # d0 and d1 equal after adjustment
4588 return timedelta(minutes=t.minute)
4589 else:
4590 # d2 off in the weeds
4591 return timedelta(minutes=59)
4592
4593 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
4594 d0 = base.replace(minute=3)
4595 d1 = base.replace(minute=9)
4596 d2 = base.replace(minute=11)
4597 for x in d0, d1, d2:
4598 for y in d0, d1, d2:
4599 got = x - y
4600 expected = timedelta(minutes=x.minute - y.minute)
4601 self.assertEqual(got, expected)
4602
4603 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
4604 # ignored.
4605 base = cls(8, 9, 10, 11, 12, 13, 14)
4606 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
4607 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
4608 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
4609 for x in d0, d1, d2:
4610 for y in d0, d1, d2:
4611 got = x - y
4612 if (x is d0 or x is d1) and (y is d0 or y is d1):
4613 expected = timedelta(0)
4614 elif x is y is d2:
4615 expected = timedelta(0)
4616 elif x is d2:
4617 expected = timedelta(minutes=(11-59)-0)
4618 else:
4619 assert y is d2
4620 expected = timedelta(minutes=0-(11-59))
4621 self.assertEqual(got, expected)
4622
4623 def test_mixed_compare(self):
4624 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
4625 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
4626 self.assertEqual(t1, t2)
4627 t2 = t2.replace(tzinfo=None)
4628 self.assertEqual(t1, t2)
4629 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
4630 self.assertEqual(t1, t2)
4631 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04004632 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004633
4634 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
4635 class Varies(tzinfo):
4636 def __init__(self):
4637 self.offset = timedelta(minutes=22)
4638 def utcoffset(self, t):
4639 self.offset += timedelta(minutes=1)
4640 return self.offset
4641
4642 v = Varies()
4643 t1 = t2.replace(tzinfo=v)
4644 t2 = t2.replace(tzinfo=v)
4645 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
4646 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
4647 self.assertEqual(t1, t2)
4648
4649 # But if they're not identical, it isn't ignored.
4650 t2 = t2.replace(tzinfo=Varies())
4651 self.assertTrue(t1 < t2) # t1's offset counter still going up
4652
4653 def test_subclass_datetimetz(self):
4654
4655 class C(self.theclass):
4656 theAnswer = 42
4657
4658 def __new__(cls, *args, **kws):
4659 temp = kws.copy()
4660 extra = temp.pop('extra')
4661 result = self.theclass.__new__(cls, *args, **temp)
4662 result.extra = extra
4663 return result
4664
4665 def newmeth(self, start):
4666 return start + self.hour + self.year
4667
4668 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
4669
4670 dt1 = self.theclass(*args)
4671 dt2 = C(*args, **{'extra': 7})
4672
4673 self.assertEqual(dt2.__class__, C)
4674 self.assertEqual(dt2.theAnswer, 42)
4675 self.assertEqual(dt2.extra, 7)
4676 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
4677 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
4678
4679# Pain to set up DST-aware tzinfo classes.
4680
4681def first_sunday_on_or_after(dt):
4682 days_to_go = 6 - dt.weekday()
4683 if days_to_go:
4684 dt += timedelta(days_to_go)
4685 return dt
4686
4687ZERO = timedelta(0)
4688MINUTE = timedelta(minutes=1)
4689HOUR = timedelta(hours=1)
4690DAY = timedelta(days=1)
4691# In the US, DST starts at 2am (standard time) on the first Sunday in April.
4692DSTSTART = datetime(1, 4, 1, 2)
4693# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
4694# which is the first Sunday on or after Oct 25. Because we view 1:MM as
4695# being standard time on that day, there is no spelling in local time of
4696# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
4697DSTEND = datetime(1, 10, 25, 1)
4698
4699class USTimeZone(tzinfo):
4700
4701 def __init__(self, hours, reprname, stdname, dstname):
4702 self.stdoffset = timedelta(hours=hours)
4703 self.reprname = reprname
4704 self.stdname = stdname
4705 self.dstname = dstname
4706
4707 def __repr__(self):
4708 return self.reprname
4709
4710 def tzname(self, dt):
4711 if self.dst(dt):
4712 return self.dstname
4713 else:
4714 return self.stdname
4715
4716 def utcoffset(self, dt):
4717 return self.stdoffset + self.dst(dt)
4718
4719 def dst(self, dt):
4720 if dt is None or dt.tzinfo is None:
4721 # An exception instead may be sensible here, in one or more of
4722 # the cases.
4723 return ZERO
4724 assert dt.tzinfo is self
4725
4726 # Find first Sunday in April.
4727 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
4728 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
4729
4730 # Find last Sunday in October.
4731 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
4732 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
4733
4734 # Can't compare naive to aware objects, so strip the timezone from
4735 # dt first.
4736 if start <= dt.replace(tzinfo=None) < end:
4737 return HOUR
4738 else:
4739 return ZERO
4740
4741Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
4742Central = USTimeZone(-6, "Central", "CST", "CDT")
4743Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
4744Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
4745utc_real = FixedOffset(0, "UTC", 0)
4746# For better test coverage, we want another flavor of UTC that's west of
4747# the Eastern and Pacific timezones.
4748utc_fake = FixedOffset(-12*60, "UTCfake", 0)
4749
4750class TestTimezoneConversions(unittest.TestCase):
4751 # The DST switch times for 2002, in std time.
4752 dston = datetime(2002, 4, 7, 2)
4753 dstoff = datetime(2002, 10, 27, 1)
4754
4755 theclass = datetime
4756
4757 # Check a time that's inside DST.
4758 def checkinside(self, dt, tz, utc, dston, dstoff):
4759 self.assertEqual(dt.dst(), HOUR)
4760
4761 # Conversion to our own timezone is always an identity.
4762 self.assertEqual(dt.astimezone(tz), dt)
4763
4764 asutc = dt.astimezone(utc)
4765 there_and_back = asutc.astimezone(tz)
4766
4767 # Conversion to UTC and back isn't always an identity here,
4768 # because there are redundant spellings (in local time) of
4769 # UTC time when DST begins: the clock jumps from 1:59:59
4770 # to 3:00:00, and a local time of 2:MM:SS doesn't really
4771 # make sense then. The classes above treat 2:MM:SS as
4772 # daylight time then (it's "after 2am"), really an alias
4773 # for 1:MM:SS standard time. The latter form is what
4774 # conversion back from UTC produces.
4775 if dt.date() == dston.date() and dt.hour == 2:
4776 # We're in the redundant hour, and coming back from
4777 # UTC gives the 1:MM:SS standard-time spelling.
4778 self.assertEqual(there_and_back + HOUR, dt)
4779 # Although during was considered to be in daylight
4780 # time, there_and_back is not.
4781 self.assertEqual(there_and_back.dst(), ZERO)
4782 # They're the same times in UTC.
4783 self.assertEqual(there_and_back.astimezone(utc),
4784 dt.astimezone(utc))
4785 else:
4786 # We're not in the redundant hour.
4787 self.assertEqual(dt, there_and_back)
4788
4789 # Because we have a redundant spelling when DST begins, there is
Ezio Melotti3b3499b2011-03-16 11:35:38 +02004790 # (unfortunately) an hour when DST ends that can't be spelled at all in
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004791 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
4792 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
4793 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
4794 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
4795 # expressed in local time. Nevertheless, we want conversion back
4796 # from UTC to mimic the local clock's "repeat an hour" behavior.
4797 nexthour_utc = asutc + HOUR
4798 nexthour_tz = nexthour_utc.astimezone(tz)
4799 if dt.date() == dstoff.date() and dt.hour == 0:
4800 # We're in the hour before the last DST hour. The last DST hour
4801 # is ineffable. We want the conversion back to repeat 1:MM.
4802 self.assertEqual(nexthour_tz, dt.replace(hour=1))
4803 nexthour_utc += HOUR
4804 nexthour_tz = nexthour_utc.astimezone(tz)
4805 self.assertEqual(nexthour_tz, dt.replace(hour=1))
4806 else:
4807 self.assertEqual(nexthour_tz - dt, HOUR)
4808
4809 # Check a time that's outside DST.
4810 def checkoutside(self, dt, tz, utc):
4811 self.assertEqual(dt.dst(), ZERO)
4812
4813 # Conversion to our own timezone is always an identity.
4814 self.assertEqual(dt.astimezone(tz), dt)
4815
4816 # Converting to UTC and back is an identity too.
4817 asutc = dt.astimezone(utc)
4818 there_and_back = asutc.astimezone(tz)
4819 self.assertEqual(dt, there_and_back)
4820
4821 def convert_between_tz_and_utc(self, tz, utc):
4822 dston = self.dston.replace(tzinfo=tz)
4823 # Because 1:MM on the day DST ends is taken as being standard time,
4824 # there is no spelling in tz for the last hour of daylight time.
4825 # For purposes of the test, the last hour of DST is 0:MM, which is
4826 # taken as being daylight time (and 1:MM is taken as being standard
4827 # time).
4828 dstoff = self.dstoff.replace(tzinfo=tz)
4829 for delta in (timedelta(weeks=13),
4830 DAY,
4831 HOUR,
4832 timedelta(minutes=1),
4833 timedelta(microseconds=1)):
4834
4835 self.checkinside(dston, tz, utc, dston, dstoff)
4836 for during in dston + delta, dstoff - delta:
4837 self.checkinside(during, tz, utc, dston, dstoff)
4838
4839 self.checkoutside(dstoff, tz, utc)
4840 for outside in dston - delta, dstoff + delta:
4841 self.checkoutside(outside, tz, utc)
4842
4843 def test_easy(self):
4844 # Despite the name of this test, the endcases are excruciating.
4845 self.convert_between_tz_and_utc(Eastern, utc_real)
4846 self.convert_between_tz_and_utc(Pacific, utc_real)
4847 self.convert_between_tz_and_utc(Eastern, utc_fake)
4848 self.convert_between_tz_and_utc(Pacific, utc_fake)
4849 # The next is really dancing near the edge. It works because
4850 # Pacific and Eastern are far enough apart that their "problem
4851 # hours" don't overlap.
4852 self.convert_between_tz_and_utc(Eastern, Pacific)
4853 self.convert_between_tz_and_utc(Pacific, Eastern)
4854 # OTOH, these fail! Don't enable them. The difficulty is that
4855 # the edge case tests assume that every hour is representable in
4856 # the "utc" class. This is always true for a fixed-offset tzinfo
4857 # class (lke utc_real and utc_fake), but not for Eastern or Central.
4858 # For these adjacent DST-aware time zones, the range of time offsets
4859 # tested ends up creating hours in the one that aren't representable
4860 # in the other. For the same reason, we would see failures in the
4861 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
4862 # offset deltas in convert_between_tz_and_utc().
4863 #
4864 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
4865 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
4866
4867 def test_tricky(self):
4868 # 22:00 on day before daylight starts.
4869 fourback = self.dston - timedelta(hours=4)
4870 ninewest = FixedOffset(-9*60, "-0900", 0)
4871 fourback = fourback.replace(tzinfo=ninewest)
4872 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
4873 # 2", we should get the 3 spelling.
4874 # If we plug 22:00 the day before into Eastern, it "looks like std
4875 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
4876 # to 22:00 lands on 2:00, which makes no sense in local time (the
4877 # local clock jumps from 1 to 3). The point here is to make sure we
4878 # get the 3 spelling.
4879 expected = self.dston.replace(hour=3)
4880 got = fourback.astimezone(Eastern).replace(tzinfo=None)
4881 self.assertEqual(expected, got)
4882
4883 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
4884 # case we want the 1:00 spelling.
4885 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
4886 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
4887 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
4888 # spelling.
4889 expected = self.dston.replace(hour=1)
4890 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
4891 self.assertEqual(expected, got)
4892
4893 # Now on the day DST ends, we want "repeat an hour" behavior.
4894 # UTC 4:MM 5:MM 6:MM 7:MM checking these
4895 # EST 23:MM 0:MM 1:MM 2:MM
4896 # EDT 0:MM 1:MM 2:MM 3:MM
4897 # wall 0:MM 1:MM 1:MM 2:MM against these
4898 for utc in utc_real, utc_fake:
4899 for tz in Eastern, Pacific:
4900 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
4901 # Convert that to UTC.
4902 first_std_hour -= tz.utcoffset(None)
4903 # Adjust for possibly fake UTC.
4904 asutc = first_std_hour + utc.utcoffset(None)
4905 # First UTC hour to convert; this is 4:00 when utc=utc_real &
4906 # tz=Eastern.
4907 asutcbase = asutc.replace(tzinfo=utc)
4908 for tzhour in (0, 1, 1, 2):
4909 expectedbase = self.dstoff.replace(hour=tzhour)
4910 for minute in 0, 30, 59:
4911 expected = expectedbase.replace(minute=minute)
4912 asutc = asutcbase.replace(minute=minute)
4913 astz = asutc.astimezone(tz)
4914 self.assertEqual(astz.replace(tzinfo=None), expected)
4915 asutcbase += HOUR
4916
4917
4918 def test_bogus_dst(self):
4919 class ok(tzinfo):
4920 def utcoffset(self, dt): return HOUR
4921 def dst(self, dt): return HOUR
4922
4923 now = self.theclass.now().replace(tzinfo=utc_real)
4924 # Doesn't blow up.
4925 now.astimezone(ok())
4926
4927 # Does blow up.
4928 class notok(ok):
4929 def dst(self, dt): return None
4930 self.assertRaises(ValueError, now.astimezone, notok())
4931
4932 # Sometimes blow up. In the following, tzinfo.dst()
4933 # implementation may return None or not None depending on
4934 # whether DST is assumed to be in effect. In this situation,
4935 # a ValueError should be raised by astimezone().
4936 class tricky_notok(ok):
4937 def dst(self, dt):
4938 if dt.year == 2000:
4939 return None
4940 else:
4941 return 10*HOUR
4942 dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
4943 self.assertRaises(ValueError, dt.astimezone, tricky_notok())
4944
4945 def test_fromutc(self):
4946 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
4947 now = datetime.utcnow().replace(tzinfo=utc_real)
4948 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
4949 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
4950 enow = Eastern.fromutc(now) # doesn't blow up
4951 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
4952 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
4953 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
4954
4955 # Always converts UTC to standard time.
4956 class FauxUSTimeZone(USTimeZone):
4957 def fromutc(self, dt):
4958 return dt + self.stdoffset
4959 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
4960
4961 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
4962 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
4963 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
4964
4965 # Check around DST start.
4966 start = self.dston.replace(hour=4, tzinfo=Eastern)
4967 fstart = start.replace(tzinfo=FEastern)
4968 for wall in 23, 0, 1, 3, 4, 5:
4969 expected = start.replace(hour=wall)
4970 if wall == 23:
4971 expected -= timedelta(days=1)
4972 got = Eastern.fromutc(start)
4973 self.assertEqual(expected, got)
4974
4975 expected = fstart + FEastern.stdoffset
4976 got = FEastern.fromutc(fstart)
4977 self.assertEqual(expected, got)
4978
4979 # Ensure astimezone() calls fromutc() too.
4980 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
4981 self.assertEqual(expected, got)
4982
4983 start += HOUR
4984 fstart += HOUR
4985
4986 # Check around DST end.
4987 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
4988 fstart = start.replace(tzinfo=FEastern)
4989 for wall in 0, 1, 1, 2, 3, 4:
4990 expected = start.replace(hour=wall)
4991 got = Eastern.fromutc(start)
4992 self.assertEqual(expected, got)
4993
4994 expected = fstart + FEastern.stdoffset
4995 got = FEastern.fromutc(fstart)
4996 self.assertEqual(expected, got)
4997
4998 # Ensure astimezone() calls fromutc() too.
4999 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
5000 self.assertEqual(expected, got)
5001
5002 start += HOUR
5003 fstart += HOUR
5004
5005
5006#############################################################################
5007# oddballs
5008
5009class Oddballs(unittest.TestCase):
5010
5011 def test_bug_1028306(self):
5012 # Trying to compare a date to a datetime should act like a mixed-
5013 # type comparison, despite that datetime is a subclass of date.
5014 as_date = date.today()
5015 as_datetime = datetime.combine(as_date, time())
5016 self.assertTrue(as_date != as_datetime)
5017 self.assertTrue(as_datetime != as_date)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02005018 self.assertFalse(as_date == as_datetime)
5019 self.assertFalse(as_datetime == as_date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00005020 self.assertRaises(TypeError, lambda: as_date < as_datetime)
5021 self.assertRaises(TypeError, lambda: as_datetime < as_date)
5022 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
5023 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
5024 self.assertRaises(TypeError, lambda: as_date > as_datetime)
5025 self.assertRaises(TypeError, lambda: as_datetime > as_date)
5026 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
5027 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
5028
Raymond Hettinger15f44ab2016-08-30 10:47:49 -07005029 # Nevertheless, comparison should work with the base-class (date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00005030 # projection if use of a date method is forced.
5031 self.assertEqual(as_date.__eq__(as_datetime), True)
5032 different_day = (as_date.day + 1) % 20 + 1
5033 as_different = as_datetime.replace(day= different_day)
5034 self.assertEqual(as_date.__eq__(as_different), False)
5035
5036 # And date should compare with other subclasses of date. If a
5037 # subclass wants to stop this, it's up to the subclass to do so.
5038 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
5039 self.assertEqual(as_date, date_sc)
5040 self.assertEqual(date_sc, as_date)
5041
5042 # Ditto for datetimes.
5043 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
5044 as_date.day, 0, 0, 0)
5045 self.assertEqual(as_datetime, datetime_sc)
5046 self.assertEqual(datetime_sc, as_datetime)
5047
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04005048 def test_extra_attributes(self):
5049 for x in [date.today(),
5050 time(),
5051 datetime.utcnow(),
5052 timedelta(),
5053 tzinfo(),
5054 timezone(timedelta())]:
5055 with self.assertRaises(AttributeError):
5056 x.abc = 1
5057
5058 def test_check_arg_types(self):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04005059 class Number:
5060 def __init__(self, value):
5061 self.value = value
5062 def __int__(self):
5063 return self.value
5064
5065 for xx in [decimal.Decimal(10),
5066 decimal.Decimal('10.9'),
5067 Number(10)]:
Serhiy Storchaka6a44f6e2019-02-25 17:57:58 +02005068 with self.assertWarns(DeprecationWarning):
5069 self.assertEqual(datetime(10, 10, 10, 10, 10, 10, 10),
5070 datetime(xx, xx, xx, xx, xx, xx, xx))
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04005071
5072 with self.assertRaisesRegex(TypeError, '^an integer is required '
R David Murray44b548d2016-09-08 13:59:53 -04005073 r'\(got type str\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04005074 datetime(10, 10, '10')
5075
5076 f10 = Number(10.9)
5077 with self.assertRaisesRegex(TypeError, '^__int__ returned non-int '
Serhiy Storchaka6a44f6e2019-02-25 17:57:58 +02005078 r'\(type float\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04005079 datetime(10, 10, f10)
5080
5081 class Float(float):
5082 pass
5083 s10 = Float(10.9)
5084 with self.assertRaisesRegex(TypeError, '^integer argument expected, '
5085 'got float$'):
5086 datetime(10, 10, s10)
5087
5088 with self.assertRaises(TypeError):
5089 datetime(10., 10, 10)
5090 with self.assertRaises(TypeError):
5091 datetime(10, 10., 10)
5092 with self.assertRaises(TypeError):
5093 datetime(10, 10, 10.)
5094 with self.assertRaises(TypeError):
5095 datetime(10, 10, 10, 10.)
5096 with self.assertRaises(TypeError):
5097 datetime(10, 10, 10, 10, 10.)
5098 with self.assertRaises(TypeError):
5099 datetime(10, 10, 10, 10, 10, 10.)
5100 with self.assertRaises(TypeError):
5101 datetime(10, 10, 10, 10, 10, 10, 10.)
5102
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005103#############################################################################
5104# Local Time Disambiguation
5105
5106# An experimental reimplementation of fromutc that respects the "fold" flag.
5107
5108class tzinfo2(tzinfo):
5109
5110 def fromutc(self, dt):
5111 "datetime in UTC -> datetime in local time."
5112
5113 if not isinstance(dt, datetime):
5114 raise TypeError("fromutc() requires a datetime argument")
5115 if dt.tzinfo is not self:
5116 raise ValueError("dt.tzinfo is not self")
5117 # Returned value satisfies
5118 # dt + ldt.utcoffset() = ldt
5119 off0 = dt.replace(fold=0).utcoffset()
5120 off1 = dt.replace(fold=1).utcoffset()
5121 if off0 is None or off1 is None or dt.dst() is None:
5122 raise ValueError
5123 if off0 == off1:
5124 ldt = dt + off0
5125 off1 = ldt.utcoffset()
5126 if off0 == off1:
5127 return ldt
5128 # Now, we discovered both possible offsets, so
5129 # we can just try four possible solutions:
5130 for off in [off0, off1]:
5131 ldt = dt + off
5132 if ldt.utcoffset() == off:
5133 return ldt
5134 ldt = ldt.replace(fold=1)
5135 if ldt.utcoffset() == off:
5136 return ldt
5137
5138 raise ValueError("No suitable local time found")
5139
5140# Reimplementing simplified US timezones to respect the "fold" flag:
5141
5142class USTimeZone2(tzinfo2):
5143
5144 def __init__(self, hours, reprname, stdname, dstname):
5145 self.stdoffset = timedelta(hours=hours)
5146 self.reprname = reprname
5147 self.stdname = stdname
5148 self.dstname = dstname
5149
5150 def __repr__(self):
5151 return self.reprname
5152
5153 def tzname(self, dt):
5154 if self.dst(dt):
5155 return self.dstname
5156 else:
5157 return self.stdname
5158
5159 def utcoffset(self, dt):
5160 return self.stdoffset + self.dst(dt)
5161
5162 def dst(self, dt):
5163 if dt is None or dt.tzinfo is None:
5164 # An exception instead may be sensible here, in one or more of
5165 # the cases.
5166 return ZERO
5167 assert dt.tzinfo is self
5168
5169 # Find first Sunday in April.
5170 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
5171 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
5172
5173 # Find last Sunday in October.
5174 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
5175 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
5176
5177 # Can't compare naive to aware objects, so strip the timezone from
5178 # dt first.
5179 dt = dt.replace(tzinfo=None)
5180 if start + HOUR <= dt < end:
5181 # DST is in effect.
5182 return HOUR
5183 elif end <= dt < end + HOUR:
5184 # Fold (an ambiguous hour): use dt.fold to disambiguate.
5185 return ZERO if dt.fold else HOUR
5186 elif start <= dt < start + HOUR:
5187 # Gap (a non-existent hour): reverse the fold rule.
5188 return HOUR if dt.fold else ZERO
5189 else:
5190 # DST is off.
5191 return ZERO
5192
5193Eastern2 = USTimeZone2(-5, "Eastern2", "EST", "EDT")
5194Central2 = USTimeZone2(-6, "Central2", "CST", "CDT")
5195Mountain2 = USTimeZone2(-7, "Mountain2", "MST", "MDT")
5196Pacific2 = USTimeZone2(-8, "Pacific2", "PST", "PDT")
5197
5198# Europe_Vilnius_1941 tzinfo implementation reproduces the following
5199# 1941 transition from Olson's tzdist:
5200#
5201# Zone NAME GMTOFF RULES FORMAT [UNTIL]
5202# ZoneEurope/Vilnius 1:00 - CET 1940 Aug 3
5203# 3:00 - MSK 1941 Jun 24
5204# 1:00 C-Eur CE%sT 1944 Aug
5205#
5206# $ zdump -v Europe/Vilnius | grep 1941
5207# Europe/Vilnius Mon Jun 23 20:59:59 1941 UTC = Mon Jun 23 23:59:59 1941 MSK isdst=0 gmtoff=10800
5208# Europe/Vilnius Mon Jun 23 21:00:00 1941 UTC = Mon Jun 23 23:00:00 1941 CEST isdst=1 gmtoff=7200
5209
5210class Europe_Vilnius_1941(tzinfo):
5211 def _utc_fold(self):
5212 return [datetime(1941, 6, 23, 21, tzinfo=self), # Mon Jun 23 21:00:00 1941 UTC
5213 datetime(1941, 6, 23, 22, tzinfo=self)] # Mon Jun 23 22:00:00 1941 UTC
5214
5215 def _loc_fold(self):
5216 return [datetime(1941, 6, 23, 23, tzinfo=self), # Mon Jun 23 23:00:00 1941 MSK / CEST
5217 datetime(1941, 6, 24, 0, tzinfo=self)] # Mon Jun 24 00:00:00 1941 CEST
5218
5219 def utcoffset(self, dt):
5220 fold_start, fold_stop = self._loc_fold()
5221 if dt < fold_start:
5222 return 3 * HOUR
5223 if dt < fold_stop:
5224 return (2 if dt.fold else 3) * HOUR
5225 # if dt >= fold_stop
5226 return 2 * HOUR
5227
5228 def dst(self, dt):
5229 fold_start, fold_stop = self._loc_fold()
5230 if dt < fold_start:
5231 return 0 * HOUR
5232 if dt < fold_stop:
5233 return (1 if dt.fold else 0) * HOUR
5234 # if dt >= fold_stop
5235 return 1 * HOUR
5236
5237 def tzname(self, dt):
5238 fold_start, fold_stop = self._loc_fold()
5239 if dt < fold_start:
5240 return 'MSK'
5241 if dt < fold_stop:
5242 return ('MSK', 'CEST')[dt.fold]
5243 # if dt >= fold_stop
5244 return 'CEST'
5245
5246 def fromutc(self, dt):
5247 assert dt.fold == 0
5248 assert dt.tzinfo is self
5249 if dt.year != 1941:
5250 raise NotImplementedError
5251 fold_start, fold_stop = self._utc_fold()
5252 if dt < fold_start:
5253 return dt + 3 * HOUR
5254 if dt < fold_stop:
5255 return (dt + 2 * HOUR).replace(fold=1)
5256 # if dt >= fold_stop
5257 return dt + 2 * HOUR
5258
5259
5260class TestLocalTimeDisambiguation(unittest.TestCase):
5261
5262 def test_vilnius_1941_fromutc(self):
5263 Vilnius = Europe_Vilnius_1941()
5264
5265 gdt = datetime(1941, 6, 23, 20, 59, 59, tzinfo=timezone.utc)
5266 ldt = gdt.astimezone(Vilnius)
5267 self.assertEqual(ldt.strftime("%c %Z%z"),
5268 'Mon Jun 23 23:59:59 1941 MSK+0300')
5269 self.assertEqual(ldt.fold, 0)
5270 self.assertFalse(ldt.dst())
5271
5272 gdt = datetime(1941, 6, 23, 21, tzinfo=timezone.utc)
5273 ldt = gdt.astimezone(Vilnius)
5274 self.assertEqual(ldt.strftime("%c %Z%z"),
5275 'Mon Jun 23 23:00:00 1941 CEST+0200')
5276 self.assertEqual(ldt.fold, 1)
5277 self.assertTrue(ldt.dst())
5278
5279 gdt = datetime(1941, 6, 23, 22, tzinfo=timezone.utc)
5280 ldt = gdt.astimezone(Vilnius)
5281 self.assertEqual(ldt.strftime("%c %Z%z"),
5282 'Tue Jun 24 00:00:00 1941 CEST+0200')
5283 self.assertEqual(ldt.fold, 0)
5284 self.assertTrue(ldt.dst())
5285
5286 def test_vilnius_1941_toutc(self):
5287 Vilnius = Europe_Vilnius_1941()
5288
5289 ldt = datetime(1941, 6, 23, 22, 59, 59, tzinfo=Vilnius)
5290 gdt = ldt.astimezone(timezone.utc)
5291 self.assertEqual(gdt.strftime("%c %Z"),
5292 'Mon Jun 23 19:59:59 1941 UTC')
5293
5294 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius)
5295 gdt = ldt.astimezone(timezone.utc)
5296 self.assertEqual(gdt.strftime("%c %Z"),
5297 'Mon Jun 23 20:59:59 1941 UTC')
5298
5299 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius, fold=1)
5300 gdt = ldt.astimezone(timezone.utc)
5301 self.assertEqual(gdt.strftime("%c %Z"),
5302 'Mon Jun 23 21:59:59 1941 UTC')
5303
5304 ldt = datetime(1941, 6, 24, 0, tzinfo=Vilnius)
5305 gdt = ldt.astimezone(timezone.utc)
5306 self.assertEqual(gdt.strftime("%c %Z"),
5307 'Mon Jun 23 22:00:00 1941 UTC')
5308
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005309 def test_constructors(self):
5310 t = time(0, fold=1)
5311 dt = datetime(1, 1, 1, fold=1)
5312 self.assertEqual(t.fold, 1)
5313 self.assertEqual(dt.fold, 1)
5314 with self.assertRaises(TypeError):
5315 time(0, 0, 0, 0, None, 0)
5316
5317 def test_member(self):
5318 dt = datetime(1, 1, 1, fold=1)
5319 t = dt.time()
5320 self.assertEqual(t.fold, 1)
5321 t = dt.timetz()
5322 self.assertEqual(t.fold, 1)
5323
5324 def test_replace(self):
5325 t = time(0)
5326 dt = datetime(1, 1, 1)
5327 self.assertEqual(t.replace(fold=1).fold, 1)
5328 self.assertEqual(dt.replace(fold=1).fold, 1)
5329 self.assertEqual(t.replace(fold=0).fold, 0)
5330 self.assertEqual(dt.replace(fold=0).fold, 0)
5331 # Check that replacement of other fields does not change "fold".
5332 t = t.replace(fold=1, tzinfo=Eastern)
5333 dt = dt.replace(fold=1, tzinfo=Eastern)
5334 self.assertEqual(t.replace(tzinfo=None).fold, 1)
5335 self.assertEqual(dt.replace(tzinfo=None).fold, 1)
Serhiy Storchaka314d6fc2017-03-31 22:48:16 +03005336 # Out of bounds.
5337 with self.assertRaises(ValueError):
5338 t.replace(fold=2)
5339 with self.assertRaises(ValueError):
5340 dt.replace(fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005341 # Check that fold is a keyword-only argument
5342 with self.assertRaises(TypeError):
5343 t.replace(1, 1, 1, None, 1)
5344 with self.assertRaises(TypeError):
5345 dt.replace(1, 1, 1, 1, 1, 1, 1, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04005346
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005347 def test_comparison(self):
5348 t = time(0)
5349 dt = datetime(1, 1, 1)
5350 self.assertEqual(t, t.replace(fold=1))
5351 self.assertEqual(dt, dt.replace(fold=1))
5352
5353 def test_hash(self):
5354 t = time(0)
5355 dt = datetime(1, 1, 1)
5356 self.assertEqual(hash(t), hash(t.replace(fold=1)))
5357 self.assertEqual(hash(dt), hash(dt.replace(fold=1)))
5358
5359 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5360 def test_fromtimestamp(self):
5361 s = 1414906200
5362 dt0 = datetime.fromtimestamp(s)
5363 dt1 = datetime.fromtimestamp(s + 3600)
5364 self.assertEqual(dt0.fold, 0)
5365 self.assertEqual(dt1.fold, 1)
5366
5367 @support.run_with_tz('Australia/Lord_Howe')
5368 def test_fromtimestamp_lord_howe(self):
5369 tm = _time.localtime(1.4e9)
5370 if _time.strftime('%Z%z', tm) != 'LHST+1030':
5371 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
5372 # $ TZ=Australia/Lord_Howe date -r 1428158700
5373 # Sun Apr 5 01:45:00 LHDT 2015
5374 # $ TZ=Australia/Lord_Howe date -r 1428160500
5375 # Sun Apr 5 01:45:00 LHST 2015
5376 s = 1428158700
5377 t0 = datetime.fromtimestamp(s)
5378 t1 = datetime.fromtimestamp(s + 1800)
5379 self.assertEqual(t0, t1)
5380 self.assertEqual(t0.fold, 0)
5381 self.assertEqual(t1.fold, 1)
5382
Ammar Askar96d1e692018-07-25 09:54:58 -07005383 def test_fromtimestamp_low_fold_detection(self):
5384 # Ensure that fold detection doesn't cause an
5385 # OSError for really low values, see bpo-29097
5386 self.assertEqual(datetime.fromtimestamp(0).fold, 0)
5387
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005388 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5389 def test_timestamp(self):
5390 dt0 = datetime(2014, 11, 2, 1, 30)
5391 dt1 = dt0.replace(fold=1)
5392 self.assertEqual(dt0.timestamp() + 3600,
5393 dt1.timestamp())
5394
5395 @support.run_with_tz('Australia/Lord_Howe')
5396 def test_timestamp_lord_howe(self):
5397 tm = _time.localtime(1.4e9)
5398 if _time.strftime('%Z%z', tm) != 'LHST+1030':
5399 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
5400 t = datetime(2015, 4, 5, 1, 45)
5401 s0 = t.replace(fold=0).timestamp()
5402 s1 = t.replace(fold=1).timestamp()
5403 self.assertEqual(s0 + 1800, s1)
5404
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005405 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5406 def test_astimezone(self):
5407 dt0 = datetime(2014, 11, 2, 1, 30)
5408 dt1 = dt0.replace(fold=1)
5409 # Convert both naive instances to aware.
5410 adt0 = dt0.astimezone()
5411 adt1 = dt1.astimezone()
5412 # Check that the first instance in DST zone and the second in STD
5413 self.assertEqual(adt0.tzname(), 'EDT')
5414 self.assertEqual(adt1.tzname(), 'EST')
5415 self.assertEqual(adt0 + HOUR, adt1)
5416 # Aware instances with fixed offset tzinfo's always have fold=0
5417 self.assertEqual(adt0.fold, 0)
5418 self.assertEqual(adt1.fold, 0)
5419
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005420 def test_pickle_fold(self):
5421 t = time(fold=1)
5422 dt = datetime(1, 1, 1, fold=1)
5423 for pickler, unpickler, proto in pickle_choices:
5424 for x in [t, dt]:
5425 s = pickler.dumps(x, proto)
5426 y = unpickler.loads(s)
5427 self.assertEqual(x, y)
5428 self.assertEqual((0 if proto < 4 else x.fold), y.fold)
5429
5430 def test_repr(self):
5431 t = time(fold=1)
5432 dt = datetime(1, 1, 1, fold=1)
5433 self.assertEqual(repr(t), 'datetime.time(0, 0, fold=1)')
5434 self.assertEqual(repr(dt),
5435 'datetime.datetime(1, 1, 1, 0, 0, fold=1)')
5436
5437 def test_dst(self):
5438 # Let's first establish that things work in regular times.
5439 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
5440 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
5441 self.assertEqual(dt_summer.dst(), HOUR)
5442 self.assertEqual(dt_winter.dst(), ZERO)
5443 # The disambiguation flag is ignored
5444 self.assertEqual(dt_summer.replace(fold=1).dst(), HOUR)
5445 self.assertEqual(dt_winter.replace(fold=1).dst(), ZERO)
5446
5447 # Pick local time in the fold.
5448 for minute in [0, 30, 59]:
5449 dt = datetime(2002, 10, 27, 1, minute, tzinfo=Eastern2)
5450 # With fold=0 (the default) it is in DST.
5451 self.assertEqual(dt.dst(), HOUR)
5452 # With fold=1 it is in STD.
5453 self.assertEqual(dt.replace(fold=1).dst(), ZERO)
5454
5455 # Pick local time in the gap.
5456 for minute in [0, 30, 59]:
5457 dt = datetime(2002, 4, 7, 2, minute, tzinfo=Eastern2)
5458 # With fold=0 (the default) it is in STD.
5459 self.assertEqual(dt.dst(), ZERO)
5460 # With fold=1 it is in DST.
5461 self.assertEqual(dt.replace(fold=1).dst(), HOUR)
5462
5463
5464 def test_utcoffset(self):
5465 # Let's first establish that things work in regular times.
5466 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
5467 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
5468 self.assertEqual(dt_summer.utcoffset(), -4 * HOUR)
5469 self.assertEqual(dt_winter.utcoffset(), -5 * HOUR)
5470 # The disambiguation flag is ignored
5471 self.assertEqual(dt_summer.replace(fold=1).utcoffset(), -4 * HOUR)
5472 self.assertEqual(dt_winter.replace(fold=1).utcoffset(), -5 * HOUR)
5473
5474 def test_fromutc(self):
5475 # Let's first establish that things work in regular times.
5476 u_summer = datetime(2002, 10, 27, 6, tzinfo=Eastern2) - timedelta.resolution
5477 u_winter = datetime(2002, 10, 27, 7, tzinfo=Eastern2)
5478 t_summer = Eastern2.fromutc(u_summer)
5479 t_winter = Eastern2.fromutc(u_winter)
5480 self.assertEqual(t_summer, u_summer - 4 * HOUR)
5481 self.assertEqual(t_winter, u_winter - 5 * HOUR)
5482 self.assertEqual(t_summer.fold, 0)
5483 self.assertEqual(t_winter.fold, 0)
5484
5485 # What happens in the fall-back fold?
5486 u = datetime(2002, 10, 27, 5, 30, tzinfo=Eastern2)
5487 t0 = Eastern2.fromutc(u)
5488 u += HOUR
5489 t1 = Eastern2.fromutc(u)
5490 self.assertEqual(t0, t1)
5491 self.assertEqual(t0.fold, 0)
5492 self.assertEqual(t1.fold, 1)
5493 # The tricky part is when u is in the local fold:
5494 u = datetime(2002, 10, 27, 1, 30, tzinfo=Eastern2)
5495 t = Eastern2.fromutc(u)
5496 self.assertEqual((t.day, t.hour), (26, 21))
5497 # .. or gets into the local fold after a standard time adjustment
5498 u = datetime(2002, 10, 27, 6, 30, tzinfo=Eastern2)
5499 t = Eastern2.fromutc(u)
5500 self.assertEqual((t.day, t.hour), (27, 1))
5501
5502 # What happens in the spring-forward gap?
5503 u = datetime(2002, 4, 7, 2, 0, tzinfo=Eastern2)
5504 t = Eastern2.fromutc(u)
5505 self.assertEqual((t.day, t.hour), (6, 21))
5506
5507 def test_mixed_compare_regular(self):
5508 t = datetime(2000, 1, 1, tzinfo=Eastern2)
5509 self.assertEqual(t, t.astimezone(timezone.utc))
5510 t = datetime(2000, 6, 1, tzinfo=Eastern2)
5511 self.assertEqual(t, t.astimezone(timezone.utc))
5512
5513 def test_mixed_compare_fold(self):
5514 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
5515 t_fold_utc = t_fold.astimezone(timezone.utc)
5516 self.assertNotEqual(t_fold, t_fold_utc)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04005517 self.assertNotEqual(t_fold_utc, t_fold)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005518
5519 def test_mixed_compare_gap(self):
5520 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
5521 t_gap_utc = t_gap.astimezone(timezone.utc)
5522 self.assertNotEqual(t_gap, t_gap_utc)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04005523 self.assertNotEqual(t_gap_utc, t_gap)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005524
5525 def test_hash_aware(self):
5526 t = datetime(2000, 1, 1, tzinfo=Eastern2)
5527 self.assertEqual(hash(t), hash(t.replace(fold=1)))
5528 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
5529 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
5530 self.assertEqual(hash(t_fold), hash(t_fold.replace(fold=1)))
5531 self.assertEqual(hash(t_gap), hash(t_gap.replace(fold=1)))
5532
5533SEC = timedelta(0, 1)
5534
5535def pairs(iterable):
5536 a, b = itertools.tee(iterable)
5537 next(b, None)
5538 return zip(a, b)
5539
5540class ZoneInfo(tzinfo):
5541 zoneroot = '/usr/share/zoneinfo'
5542 def __init__(self, ut, ti):
5543 """
5544
5545 :param ut: array
5546 Array of transition point timestamps
5547 :param ti: list
5548 A list of (offset, isdst, abbr) tuples
5549 :return: None
5550 """
5551 self.ut = ut
5552 self.ti = ti
5553 self.lt = self.invert(ut, ti)
5554
5555 @staticmethod
5556 def invert(ut, ti):
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04005557 lt = (array('q', ut), array('q', ut))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005558 if ut:
5559 offset = ti[0][0] // SEC
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04005560 lt[0][0] += offset
5561 lt[1][0] += offset
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005562 for i in range(1, len(ut)):
5563 lt[0][i] += ti[i-1][0] // SEC
5564 lt[1][i] += ti[i][0] // SEC
5565 return lt
5566
5567 @classmethod
5568 def fromfile(cls, fileobj):
5569 if fileobj.read(4).decode() != "TZif":
5570 raise ValueError("not a zoneinfo file")
5571 fileobj.seek(32)
5572 counts = array('i')
5573 counts.fromfile(fileobj, 3)
5574 if sys.byteorder != 'big':
5575 counts.byteswap()
5576
5577 ut = array('i')
5578 ut.fromfile(fileobj, counts[0])
5579 if sys.byteorder != 'big':
5580 ut.byteswap()
5581
5582 type_indices = array('B')
5583 type_indices.fromfile(fileobj, counts[0])
5584
5585 ttis = []
5586 for i in range(counts[1]):
5587 ttis.append(struct.unpack(">lbb", fileobj.read(6)))
5588
5589 abbrs = fileobj.read(counts[2])
5590
5591 # Convert ttis
5592 for i, (gmtoff, isdst, abbrind) in enumerate(ttis):
5593 abbr = abbrs[abbrind:abbrs.find(0, abbrind)].decode()
5594 ttis[i] = (timedelta(0, gmtoff), isdst, abbr)
5595
5596 ti = [None] * len(ut)
5597 for i, idx in enumerate(type_indices):
5598 ti[i] = ttis[idx]
5599
5600 self = cls(ut, ti)
5601
5602 return self
5603
5604 @classmethod
5605 def fromname(cls, name):
5606 path = os.path.join(cls.zoneroot, name)
5607 with open(path, 'rb') as f:
5608 return cls.fromfile(f)
5609
5610 EPOCHORDINAL = date(1970, 1, 1).toordinal()
5611
5612 def fromutc(self, dt):
5613 """datetime in UTC -> datetime in local time."""
5614
5615 if not isinstance(dt, datetime):
5616 raise TypeError("fromutc() requires a datetime argument")
5617 if dt.tzinfo is not self:
5618 raise ValueError("dt.tzinfo is not self")
5619
5620 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
5621 + dt.hour * 3600
5622 + dt.minute * 60
5623 + dt.second)
5624
5625 if timestamp < self.ut[1]:
5626 tti = self.ti[0]
5627 fold = 0
5628 else:
5629 idx = bisect.bisect_right(self.ut, timestamp)
5630 assert self.ut[idx-1] <= timestamp
5631 assert idx == len(self.ut) or timestamp < self.ut[idx]
5632 tti_prev, tti = self.ti[idx-2:idx]
5633 # Detect fold
5634 shift = tti_prev[0] - tti[0]
5635 fold = (shift > timedelta(0, timestamp - self.ut[idx-1]))
5636 dt += tti[0]
5637 if fold:
5638 return dt.replace(fold=1)
5639 else:
5640 return dt
5641
5642 def _find_ti(self, dt, i):
5643 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
5644 + dt.hour * 3600
5645 + dt.minute * 60
5646 + dt.second)
5647 lt = self.lt[dt.fold]
5648 idx = bisect.bisect_right(lt, timestamp)
5649
5650 return self.ti[max(0, idx - 1)][i]
5651
5652 def utcoffset(self, dt):
5653 return self._find_ti(dt, 0)
5654
5655 def dst(self, dt):
5656 isdst = self._find_ti(dt, 1)
5657 # XXX: We cannot accurately determine the "save" value,
5658 # so let's return 1h whenever DST is in effect. Since
5659 # we don't use dst() in fromutc(), it is unlikely that
5660 # it will be needed for anything more than bool(dst()).
5661 return ZERO if isdst else HOUR
5662
5663 def tzname(self, dt):
5664 return self._find_ti(dt, 2)
5665
5666 @classmethod
5667 def zonenames(cls, zonedir=None):
5668 if zonedir is None:
5669 zonedir = cls.zoneroot
Alexander Belopolsky1b8f26c2016-08-11 11:01:45 -04005670 zone_tab = os.path.join(zonedir, 'zone.tab')
5671 try:
5672 f = open(zone_tab)
5673 except OSError:
5674 return
5675 with f:
5676 for line in f:
5677 line = line.strip()
5678 if line and not line.startswith('#'):
5679 yield line.split()[2]
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005680
5681 @classmethod
5682 def stats(cls, start_year=1):
5683 count = gap_count = fold_count = zeros_count = 0
5684 min_gap = min_fold = timedelta.max
5685 max_gap = max_fold = ZERO
5686 min_gap_datetime = max_gap_datetime = datetime.min
5687 min_gap_zone = max_gap_zone = None
5688 min_fold_datetime = max_fold_datetime = datetime.min
5689 min_fold_zone = max_fold_zone = None
5690 stats_since = datetime(start_year, 1, 1) # Starting from 1970 eliminates a lot of noise
5691 for zonename in cls.zonenames():
5692 count += 1
5693 tz = cls.fromname(zonename)
5694 for dt, shift in tz.transitions():
5695 if dt < stats_since:
5696 continue
5697 if shift > ZERO:
5698 gap_count += 1
5699 if (shift, dt) > (max_gap, max_gap_datetime):
5700 max_gap = shift
5701 max_gap_zone = zonename
5702 max_gap_datetime = dt
5703 if (shift, datetime.max - dt) < (min_gap, datetime.max - min_gap_datetime):
5704 min_gap = shift
5705 min_gap_zone = zonename
5706 min_gap_datetime = dt
5707 elif shift < ZERO:
5708 fold_count += 1
5709 shift = -shift
5710 if (shift, dt) > (max_fold, max_fold_datetime):
5711 max_fold = shift
5712 max_fold_zone = zonename
5713 max_fold_datetime = dt
5714 if (shift, datetime.max - dt) < (min_fold, datetime.max - min_fold_datetime):
5715 min_fold = shift
5716 min_fold_zone = zonename
5717 min_fold_datetime = dt
5718 else:
5719 zeros_count += 1
5720 trans_counts = (gap_count, fold_count, zeros_count)
5721 print("Number of zones: %5d" % count)
5722 print("Number of transitions: %5d = %d (gaps) + %d (folds) + %d (zeros)" %
5723 ((sum(trans_counts),) + trans_counts))
5724 print("Min gap: %16s at %s in %s" % (min_gap, min_gap_datetime, min_gap_zone))
5725 print("Max gap: %16s at %s in %s" % (max_gap, max_gap_datetime, max_gap_zone))
5726 print("Min fold: %16s at %s in %s" % (min_fold, min_fold_datetime, min_fold_zone))
5727 print("Max fold: %16s at %s in %s" % (max_fold, max_fold_datetime, max_fold_zone))
5728
5729
5730 def transitions(self):
5731 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
5732 shift = ti[0] - prev_ti[0]
5733 yield datetime.utcfromtimestamp(t), shift
5734
5735 def nondst_folds(self):
5736 """Find all folds with the same value of isdst on both sides of the transition."""
5737 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
5738 shift = ti[0] - prev_ti[0]
5739 if shift < ZERO and ti[1] == prev_ti[1]:
5740 yield datetime.utcfromtimestamp(t), -shift, prev_ti[2], ti[2]
5741
5742 @classmethod
5743 def print_all_nondst_folds(cls, same_abbr=False, start_year=1):
5744 count = 0
5745 for zonename in cls.zonenames():
5746 tz = cls.fromname(zonename)
5747 for dt, shift, prev_abbr, abbr in tz.nondst_folds():
5748 if dt.year < start_year or same_abbr and prev_abbr != abbr:
5749 continue
5750 count += 1
5751 print("%3d) %-30s %s %10s %5s -> %s" %
5752 (count, zonename, dt, shift, prev_abbr, abbr))
5753
5754 def folds(self):
5755 for t, shift in self.transitions():
5756 if shift < ZERO:
5757 yield t, -shift
5758
5759 def gaps(self):
5760 for t, shift in self.transitions():
5761 if shift > ZERO:
5762 yield t, shift
5763
5764 def zeros(self):
5765 for t, shift in self.transitions():
5766 if not shift:
5767 yield t
5768
5769
5770class ZoneInfoTest(unittest.TestCase):
5771 zonename = 'America/New_York'
5772
5773 def setUp(self):
5774 if sys.platform == "win32":
5775 self.skipTest("Skipping zoneinfo tests on Windows")
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04005776 try:
5777 self.tz = ZoneInfo.fromname(self.zonename)
5778 except FileNotFoundError as err:
5779 self.skipTest("Skipping %s: %s" % (self.zonename, err))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005780
5781 def assertEquivDatetimes(self, a, b):
5782 self.assertEqual((a.replace(tzinfo=None), a.fold, id(a.tzinfo)),
5783 (b.replace(tzinfo=None), b.fold, id(b.tzinfo)))
5784
5785 def test_folds(self):
5786 tz = self.tz
5787 for dt, shift in tz.folds():
5788 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
5789 udt = dt + x
5790 ldt = tz.fromutc(udt.replace(tzinfo=tz))
5791 self.assertEqual(ldt.fold, 1)
5792 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
5793 self.assertEquivDatetimes(adt, ldt)
5794 utcoffset = ldt.utcoffset()
5795 self.assertEqual(ldt.replace(tzinfo=None), udt + utcoffset)
5796 # Round trip
5797 self.assertEquivDatetimes(ldt.astimezone(timezone.utc),
5798 udt.replace(tzinfo=timezone.utc))
5799
5800
5801 for x in [-timedelta.resolution, shift]:
5802 udt = dt + x
5803 udt = udt.replace(tzinfo=tz)
5804 ldt = tz.fromutc(udt)
5805 self.assertEqual(ldt.fold, 0)
5806
5807 def test_gaps(self):
5808 tz = self.tz
5809 for dt, shift in tz.gaps():
5810 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
5811 udt = dt + x
5812 udt = udt.replace(tzinfo=tz)
5813 ldt = tz.fromutc(udt)
5814 self.assertEqual(ldt.fold, 0)
5815 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
5816 self.assertEquivDatetimes(adt, ldt)
5817 utcoffset = ldt.utcoffset()
5818 self.assertEqual(ldt.replace(tzinfo=None), udt.replace(tzinfo=None) + utcoffset)
5819 # Create a local time inside the gap
5820 ldt = tz.fromutc(dt.replace(tzinfo=tz)) - shift + x
5821 self.assertLess(ldt.replace(fold=1).utcoffset(),
5822 ldt.replace(fold=0).utcoffset(),
5823 "At %s." % ldt)
5824
5825 for x in [-timedelta.resolution, shift]:
5826 udt = dt + x
5827 ldt = tz.fromutc(udt.replace(tzinfo=tz))
5828 self.assertEqual(ldt.fold, 0)
5829
5830 def test_system_transitions(self):
5831 if ('Riyadh8' in self.zonename or
5832 # From tzdata NEWS file:
5833 # The files solar87, solar88, and solar89 are no longer distributed.
5834 # They were a negative experiment - that is, a demonstration that
5835 # tz data can represent solar time only with some difficulty and error.
5836 # Their presence in the distribution caused confusion, as Riyadh
5837 # civil time was generally not solar time in those years.
5838 self.zonename.startswith('right/')):
5839 self.skipTest("Skipping %s" % self.zonename)
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04005840 tz = self.tz
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005841 TZ = os.environ.get('TZ')
5842 os.environ['TZ'] = self.zonename
5843 try:
5844 _time.tzset()
5845 for udt, shift in tz.transitions():
Alexander Belopolsky10c2dd22016-08-12 19:08:15 -04005846 if udt.year >= 2037:
5847 # System support for times around the end of 32-bit time_t
5848 # and later is flaky on many systems.
5849 break
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005850 s0 = (udt - datetime(1970, 1, 1)) // SEC
5851 ss = shift // SEC # shift seconds
5852 for x in [-40 * 3600, -20*3600, -1, 0,
5853 ss - 1, ss + 20 * 3600, ss + 40 * 3600]:
5854 s = s0 + x
5855 sdt = datetime.fromtimestamp(s)
5856 tzdt = datetime.fromtimestamp(s, tz).replace(tzinfo=None)
5857 self.assertEquivDatetimes(sdt, tzdt)
5858 s1 = sdt.timestamp()
5859 self.assertEqual(s, s1)
5860 if ss > 0: # gap
5861 # Create local time inside the gap
5862 dt = datetime.fromtimestamp(s0) - shift / 2
5863 ts0 = dt.timestamp()
5864 ts1 = dt.replace(fold=1).timestamp()
5865 self.assertEqual(ts0, s0 + ss / 2)
5866 self.assertEqual(ts1, s0 - ss / 2)
5867 finally:
5868 if TZ is None:
5869 del os.environ['TZ']
5870 else:
5871 os.environ['TZ'] = TZ
5872 _time.tzset()
5873
5874
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04005875class ZoneInfoCompleteTest(unittest.TestSuite):
5876 def __init__(self):
5877 tests = []
5878 if is_resource_enabled('tzdata'):
5879 for name in ZoneInfo.zonenames():
5880 Test = type('ZoneInfoTest[%s]' % name, (ZoneInfoTest,), {})
5881 Test.zonename = name
5882 for method in dir(Test):
5883 if method.startswith('test_'):
5884 tests.append(Test(method))
5885 super().__init__(tests)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005886
5887# Iran had a sub-minute UTC offset before 1946.
5888class IranTest(ZoneInfoTest):
Alexander Belopolsky07e2a0a2016-07-30 11:41:02 -04005889 zonename = 'Asia/Tehran'
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005890
Paul Ganssle04af5b12018-01-24 17:29:30 -05005891
5892class CapiTest(unittest.TestCase):
5893 def setUp(self):
5894 # Since the C API is not present in the _Pure tests, skip all tests
5895 if self.__class__.__name__.endswith('Pure'):
5896 self.skipTest('Not relevant in pure Python')
5897
5898 # This *must* be called, and it must be called first, so until either
5899 # restriction is loosened, we'll call it as part of test setup
5900 _testcapi.test_datetime_capi()
5901
5902 def test_utc_capi(self):
5903 for use_macro in (True, False):
5904 capi_utc = _testcapi.get_timezone_utc_capi(use_macro)
5905
5906 with self.subTest(use_macro=use_macro):
5907 self.assertIs(capi_utc, timezone.utc)
5908
5909 def test_timezones_capi(self):
5910 est_capi, est_macro, est_macro_nn = _testcapi.make_timezones_capi()
5911
5912 exp_named = timezone(timedelta(hours=-5), "EST")
5913 exp_unnamed = timezone(timedelta(hours=-5))
5914
5915 cases = [
5916 ('est_capi', est_capi, exp_named),
5917 ('est_macro', est_macro, exp_named),
5918 ('est_macro_nn', est_macro_nn, exp_unnamed)
5919 ]
5920
5921 for name, tz_act, tz_exp in cases:
5922 with self.subTest(name=name):
5923 self.assertEqual(tz_act, tz_exp)
5924
5925 dt1 = datetime(2000, 2, 4, tzinfo=tz_act)
5926 dt2 = datetime(2000, 2, 4, tzinfo=tz_exp)
5927
5928 self.assertEqual(dt1, dt2)
5929 self.assertEqual(dt1.tzname(), dt2.tzname())
5930
5931 dt_utc = datetime(2000, 2, 4, 5, tzinfo=timezone.utc)
5932
5933 self.assertEqual(dt1.astimezone(timezone.utc), dt_utc)
5934
Paul Gansslea049f572018-02-22 15:15:32 -05005935 def test_timezones_offset_zero(self):
5936 utc0, utc1, non_utc = _testcapi.get_timezones_offset_zero()
5937
5938 with self.subTest(testname="utc0"):
5939 self.assertIs(utc0, timezone.utc)
5940
5941 with self.subTest(testname="utc1"):
5942 self.assertIs(utc1, timezone.utc)
5943
5944 with self.subTest(testname="non_utc"):
5945 self.assertIsNot(non_utc, timezone.utc)
5946
5947 non_utc_exp = timezone(timedelta(hours=0), "")
5948
5949 self.assertEqual(non_utc, non_utc_exp)
5950
5951 dt1 = datetime(2000, 2, 4, tzinfo=non_utc)
5952 dt2 = datetime(2000, 2, 4, tzinfo=non_utc_exp)
5953
5954 self.assertEqual(dt1, dt2)
5955 self.assertEqual(dt1.tzname(), dt2.tzname())
5956
Paul Ganssle04af5b12018-01-24 17:29:30 -05005957 def test_check_date(self):
5958 class DateSubclass(date):
5959 pass
5960
5961 d = date(2011, 1, 1)
5962 ds = DateSubclass(2011, 1, 1)
5963 dt = datetime(2011, 1, 1)
5964
5965 is_date = _testcapi.datetime_check_date
5966
5967 # Check the ones that should be valid
5968 self.assertTrue(is_date(d))
5969 self.assertTrue(is_date(dt))
5970 self.assertTrue(is_date(ds))
5971 self.assertTrue(is_date(d, True))
5972
5973 # Check that the subclasses do not match exactly
5974 self.assertFalse(is_date(dt, True))
5975 self.assertFalse(is_date(ds, True))
5976
5977 # Check that various other things are not dates at all
5978 args = [tuple(), list(), 1, '2011-01-01',
5979 timedelta(1), timezone.utc, time(12, 00)]
5980 for arg in args:
5981 for exact in (True, False):
5982 with self.subTest(arg=arg, exact=exact):
5983 self.assertFalse(is_date(arg, exact))
5984
5985 def test_check_time(self):
5986 class TimeSubclass(time):
5987 pass
5988
5989 t = time(12, 30)
5990 ts = TimeSubclass(12, 30)
5991
5992 is_time = _testcapi.datetime_check_time
5993
5994 # Check the ones that should be valid
5995 self.assertTrue(is_time(t))
5996 self.assertTrue(is_time(ts))
5997 self.assertTrue(is_time(t, True))
5998
5999 # Check that the subclass does not match exactly
6000 self.assertFalse(is_time(ts, True))
6001
6002 # Check that various other things are not times
6003 args = [tuple(), list(), 1, '2011-01-01',
6004 timedelta(1), timezone.utc, date(2011, 1, 1)]
6005
6006 for arg in args:
6007 for exact in (True, False):
6008 with self.subTest(arg=arg, exact=exact):
6009 self.assertFalse(is_time(arg, exact))
6010
6011 def test_check_datetime(self):
6012 class DateTimeSubclass(datetime):
6013 pass
6014
6015 dt = datetime(2011, 1, 1, 12, 30)
6016 dts = DateTimeSubclass(2011, 1, 1, 12, 30)
6017
6018 is_datetime = _testcapi.datetime_check_datetime
6019
6020 # Check the ones that should be valid
6021 self.assertTrue(is_datetime(dt))
6022 self.assertTrue(is_datetime(dts))
6023 self.assertTrue(is_datetime(dt, True))
6024
6025 # Check that the subclass does not match exactly
6026 self.assertFalse(is_datetime(dts, True))
6027
6028 # Check that various other things are not datetimes
6029 args = [tuple(), list(), 1, '2011-01-01',
6030 timedelta(1), timezone.utc, date(2011, 1, 1)]
6031
6032 for arg in args:
6033 for exact in (True, False):
6034 with self.subTest(arg=arg, exact=exact):
6035 self.assertFalse(is_datetime(arg, exact))
6036
6037 def test_check_delta(self):
6038 class TimeDeltaSubclass(timedelta):
6039 pass
6040
6041 td = timedelta(1)
6042 tds = TimeDeltaSubclass(1)
6043
6044 is_timedelta = _testcapi.datetime_check_delta
6045
6046 # Check the ones that should be valid
6047 self.assertTrue(is_timedelta(td))
6048 self.assertTrue(is_timedelta(tds))
6049 self.assertTrue(is_timedelta(td, True))
6050
6051 # Check that the subclass does not match exactly
6052 self.assertFalse(is_timedelta(tds, True))
6053
6054 # Check that various other things are not timedeltas
6055 args = [tuple(), list(), 1, '2011-01-01',
6056 timezone.utc, date(2011, 1, 1), datetime(2011, 1, 1)]
6057
6058 for arg in args:
6059 for exact in (True, False):
6060 with self.subTest(arg=arg, exact=exact):
6061 self.assertFalse(is_timedelta(arg, exact))
6062
6063 def test_check_tzinfo(self):
6064 class TZInfoSubclass(tzinfo):
6065 pass
6066
6067 tzi = tzinfo()
6068 tzis = TZInfoSubclass()
6069 tz = timezone(timedelta(hours=-5))
6070
6071 is_tzinfo = _testcapi.datetime_check_tzinfo
6072
6073 # Check the ones that should be valid
6074 self.assertTrue(is_tzinfo(tzi))
6075 self.assertTrue(is_tzinfo(tz))
6076 self.assertTrue(is_tzinfo(tzis))
6077 self.assertTrue(is_tzinfo(tzi, True))
6078
6079 # Check that the subclasses do not match exactly
6080 self.assertFalse(is_tzinfo(tz, True))
6081 self.assertFalse(is_tzinfo(tzis, True))
6082
6083 # Check that various other things are not tzinfos
6084 args = [tuple(), list(), 1, '2011-01-01',
6085 date(2011, 1, 1), datetime(2011, 1, 1)]
6086
6087 for arg in args:
6088 for exact in (True, False):
6089 with self.subTest(arg=arg, exact=exact):
6090 self.assertFalse(is_tzinfo(arg, exact))
6091
Edison A98ff4d52019-05-17 13:28:42 -07006092 def test_date_from_date(self):
6093 exp_date = date(1993, 8, 26)
6094
6095 for macro in [0, 1]:
6096 with self.subTest(macro=macro):
6097 c_api_date = _testcapi.get_date_fromdate(
6098 macro,
6099 exp_date.year,
6100 exp_date.month,
6101 exp_date.day)
6102
6103 self.assertEqual(c_api_date, exp_date)
6104
6105 def test_datetime_from_dateandtime(self):
6106 exp_date = datetime(1993, 8, 26, 22, 12, 55, 99999)
6107
6108 for macro in [0, 1]:
6109 with self.subTest(macro=macro):
6110 c_api_date = _testcapi.get_datetime_fromdateandtime(
6111 macro,
6112 exp_date.year,
6113 exp_date.month,
6114 exp_date.day,
6115 exp_date.hour,
6116 exp_date.minute,
6117 exp_date.second,
6118 exp_date.microsecond)
6119
6120 self.assertEqual(c_api_date, exp_date)
6121
6122 def test_datetime_from_dateandtimeandfold(self):
6123 exp_date = datetime(1993, 8, 26, 22, 12, 55, 99999)
6124
6125 for fold in [0, 1]:
6126 for macro in [0, 1]:
6127 with self.subTest(macro=macro, fold=fold):
6128 c_api_date = _testcapi.get_datetime_fromdateandtimeandfold(
6129 macro,
6130 exp_date.year,
6131 exp_date.month,
6132 exp_date.day,
6133 exp_date.hour,
6134 exp_date.minute,
6135 exp_date.second,
6136 exp_date.microsecond,
6137 exp_date.fold)
6138
6139 self.assertEqual(c_api_date, exp_date)
6140 self.assertEqual(c_api_date.fold, exp_date.fold)
6141
6142 def test_time_from_time(self):
6143 exp_time = time(22, 12, 55, 99999)
6144
6145 for macro in [0, 1]:
6146 with self.subTest(macro=macro):
6147 c_api_time = _testcapi.get_time_fromtime(
6148 macro,
6149 exp_time.hour,
6150 exp_time.minute,
6151 exp_time.second,
6152 exp_time.microsecond)
6153
6154 self.assertEqual(c_api_time, exp_time)
6155
6156 def test_time_from_timeandfold(self):
6157 exp_time = time(22, 12, 55, 99999)
6158
6159 for fold in [0, 1]:
6160 for macro in [0, 1]:
6161 with self.subTest(macro=macro, fold=fold):
6162 c_api_time = _testcapi.get_time_fromtimeandfold(
6163 macro,
6164 exp_time.hour,
6165 exp_time.minute,
6166 exp_time.second,
6167 exp_time.microsecond,
6168 exp_time.fold)
6169
6170 self.assertEqual(c_api_time, exp_time)
6171 self.assertEqual(c_api_time.fold, exp_time.fold)
6172
6173 def test_delta_from_dsu(self):
6174 exp_delta = timedelta(26, 55, 99999)
6175
6176 for macro in [0, 1]:
6177 with self.subTest(macro=macro):
6178 c_api_delta = _testcapi.get_delta_fromdsu(
6179 macro,
6180 exp_delta.days,
6181 exp_delta.seconds,
6182 exp_delta.microseconds)
6183
6184 self.assertEqual(c_api_delta, exp_delta)
6185
Paul Ganssle4d8c8c02019-04-27 15:39:40 -04006186 def test_date_from_timestamp(self):
6187 ts = datetime(1995, 4, 12).timestamp()
6188
6189 for macro in [0, 1]:
6190 with self.subTest(macro=macro):
6191 d = _testcapi.get_date_fromtimestamp(int(ts), macro)
6192
6193 self.assertEqual(d, date(1995, 4, 12))
6194
6195 def test_datetime_from_timestamp(self):
Paul Ganssle4d8c8c02019-04-27 15:39:40 -04006196 cases = [
6197 ((1995, 4, 12), None, False),
6198 ((1995, 4, 12), None, True),
6199 ((1995, 4, 12), timezone(timedelta(hours=1)), True),
6200 ((1995, 4, 12, 14, 30), None, False),
6201 ((1995, 4, 12, 14, 30), None, True),
6202 ((1995, 4, 12, 14, 30), timezone(timedelta(hours=1)), True),
6203 ]
6204
6205 from_timestamp = _testcapi.get_datetime_fromtimestamp
6206 for case in cases:
6207 for macro in [0, 1]:
6208 with self.subTest(case=case, macro=macro):
6209 dtup, tzinfo, usetz = case
6210 dt_orig = datetime(*dtup, tzinfo=tzinfo)
6211 ts = int(dt_orig.timestamp())
6212
6213 dt_rt = from_timestamp(ts, tzinfo, usetz, macro)
6214
6215 self.assertEqual(dt_orig, dt_rt)
6216
6217
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04006218def load_tests(loader, standard_tests, pattern):
6219 standard_tests.addTest(ZoneInfoCompleteTest())
6220 return standard_tests
6221
6222
Alexander Belopolskycf86e362010-07-23 19:25:47 +00006223if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -05006224 unittest.main()