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