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