blob: 99b620ce2f41112950fe70b200f827ba6131faec [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
391
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000392#############################################################################
Ezio Melotti85a86292013-08-17 16:57:41 +0300393# Base class for testing a particular aspect of timedelta, time, date and
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000394# datetime comparisons.
395
396class HarmlessMixedComparison:
397 # Test that __eq__ and __ne__ don't complain for mixed-type comparisons.
398
399 # Subclasses must define 'theclass', and theclass(1, 1, 1) must be a
400 # legit constructor.
401
402 def test_harmless_mixed_comparison(self):
403 me = self.theclass(1, 1, 1)
404
405 self.assertFalse(me == ())
406 self.assertTrue(me != ())
407 self.assertFalse(() == me)
408 self.assertTrue(() != me)
409
410 self.assertIn(me, [1, 20, [], me])
411 self.assertIn([], [me, 1, 20, []])
412
Xtreake6b46aa2019-07-13 18:52:21 +0530413 # Comparison to objects of unsupported types should return
414 # NotImplemented which falls back to the right hand side's __eq__
Serhiy Storchaka17e52642019-08-04 12:38:46 +0300415 # method. In this case, ALWAYS_EQ.__eq__ always returns True.
416 # ALWAYS_EQ.__ne__ always returns False.
417 self.assertTrue(me == ALWAYS_EQ)
418 self.assertFalse(me != ALWAYS_EQ)
419
420 # If the other class explicitly defines ordering
421 # relative to our class, it is allowed to do so
422 self.assertTrue(me < LARGEST)
423 self.assertFalse(me > LARGEST)
424 self.assertTrue(me <= LARGEST)
425 self.assertFalse(me >= LARGEST)
426 self.assertFalse(me < SMALLEST)
427 self.assertTrue(me > SMALLEST)
428 self.assertFalse(me <= SMALLEST)
429 self.assertTrue(me >= SMALLEST)
Xtreake6b46aa2019-07-13 18:52:21 +0530430
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000431 def test_harmful_mixed_comparison(self):
432 me = self.theclass(1, 1, 1)
433
434 self.assertRaises(TypeError, lambda: me < ())
435 self.assertRaises(TypeError, lambda: me <= ())
436 self.assertRaises(TypeError, lambda: me > ())
437 self.assertRaises(TypeError, lambda: me >= ())
438
439 self.assertRaises(TypeError, lambda: () < me)
440 self.assertRaises(TypeError, lambda: () <= me)
441 self.assertRaises(TypeError, lambda: () > me)
442 self.assertRaises(TypeError, lambda: () >= me)
443
444#############################################################################
445# timedelta tests
446
447class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
448
449 theclass = timedelta
450
451 def test_constructor(self):
452 eq = self.assertEqual
453 td = timedelta
454
455 # Check keyword args to constructor
456 eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0,
457 milliseconds=0, microseconds=0))
458 eq(td(1), td(days=1))
459 eq(td(0, 1), td(seconds=1))
460 eq(td(0, 0, 1), td(microseconds=1))
461 eq(td(weeks=1), td(days=7))
462 eq(td(days=1), td(hours=24))
463 eq(td(hours=1), td(minutes=60))
464 eq(td(minutes=1), td(seconds=60))
465 eq(td(seconds=1), td(milliseconds=1000))
466 eq(td(milliseconds=1), td(microseconds=1000))
467
468 # Check float args to constructor
469 eq(td(weeks=1.0/7), td(days=1))
470 eq(td(days=1.0/24), td(hours=1))
471 eq(td(hours=1.0/60), td(minutes=1))
472 eq(td(minutes=1.0/60), td(seconds=1))
473 eq(td(seconds=0.001), td(milliseconds=1))
474 eq(td(milliseconds=0.001), td(microseconds=1))
475
476 def test_computations(self):
477 eq = self.assertEqual
478 td = timedelta
479
480 a = td(7) # One week
481 b = td(0, 60) # One minute
482 c = td(0, 0, 1000) # One millisecond
483 eq(a+b+c, td(7, 60, 1000))
484 eq(a-b, td(6, 24*3600 - 60))
485 eq(b.__rsub__(a), td(6, 24*3600 - 60))
486 eq(-a, td(-7))
487 eq(+a, td(7))
488 eq(-b, td(-1, 24*3600 - 60))
489 eq(-c, td(-1, 24*3600 - 1, 999000))
490 eq(abs(a), a)
491 eq(abs(-a), a)
492 eq(td(6, 24*3600), a)
493 eq(td(0, 0, 60*1000000), b)
494 eq(a*10, td(70))
495 eq(a*10, 10*a)
496 eq(a*10, 10*a)
497 eq(b*10, td(0, 600))
498 eq(10*b, td(0, 600))
499 eq(b*10, td(0, 600))
500 eq(c*10, td(0, 0, 10000))
501 eq(10*c, td(0, 0, 10000))
502 eq(c*10, td(0, 0, 10000))
503 eq(a*-1, -a)
504 eq(b*-2, -b-b)
505 eq(c*-2, -c+-c)
506 eq(b*(60*24), (b*60)*24)
507 eq(b*(60*24), (60*b)*24)
508 eq(c*1000, td(0, 1))
509 eq(1000*c, td(0, 1))
510 eq(a//7, td(1))
511 eq(b//10, td(0, 6))
512 eq(c//1000, td(0, 0, 1))
513 eq(a//10, td(0, 7*24*360))
514 eq(a//3600000, td(0, 0, 7*24*1000))
515 eq(a/0.5, td(14))
516 eq(b/0.5, td(0, 120))
517 eq(a/7, td(1))
518 eq(b/10, td(0, 6))
519 eq(c/1000, td(0, 0, 1))
520 eq(a/10, td(0, 7*24*360))
521 eq(a/3600000, td(0, 0, 7*24*1000))
522
523 # Multiplication by float
524 us = td(microseconds=1)
525 eq((3*us) * 0.5, 2*us)
526 eq((5*us) * 0.5, 2*us)
527 eq(0.5 * (3*us), 2*us)
528 eq(0.5 * (5*us), 2*us)
529 eq((-3*us) * 0.5, -2*us)
530 eq((-5*us) * 0.5, -2*us)
531
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -0500532 # Issue #23521
533 eq(td(seconds=1) * 0.123456, td(microseconds=123456))
534 eq(td(seconds=1) * 0.6112295, td(microseconds=611229))
535
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000536 # Division by int and float
537 eq((3*us) / 2, 2*us)
538 eq((5*us) / 2, 2*us)
539 eq((-3*us) / 2.0, -2*us)
540 eq((-5*us) / 2.0, -2*us)
541 eq((3*us) / -2, -2*us)
542 eq((5*us) / -2, -2*us)
543 eq((3*us) / -2.0, -2*us)
544 eq((5*us) / -2.0, -2*us)
545 for i in range(-10, 10):
546 eq((i*us/3)//us, round(i/3))
547 for i in range(-10, 10):
548 eq((i*us/-3)//us, round(i/-3))
549
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -0500550 # Issue #23521
551 eq(td(seconds=1) / (1 / 0.6112295), td(microseconds=611229))
552
Alexander Belopolskyb6f5ec72011-04-05 20:07:38 -0400553 # Issue #11576
554 eq(td(999999999, 86399, 999999) - td(999999999, 86399, 999998),
555 td(0, 0, 1))
556 eq(td(999999999, 1, 1) - td(999999999, 1, 0),
557 td(0, 0, 1))
558
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000559 def test_disallowed_computations(self):
560 a = timedelta(42)
561
562 # Add/sub ints or floats should be illegal
563 for i in 1, 1.0:
564 self.assertRaises(TypeError, lambda: a+i)
565 self.assertRaises(TypeError, lambda: a-i)
566 self.assertRaises(TypeError, lambda: i+a)
567 self.assertRaises(TypeError, lambda: i-a)
568
569 # Division of int by timedelta doesn't make sense.
570 # Division by zero doesn't make sense.
571 zero = 0
572 self.assertRaises(TypeError, lambda: zero // a)
573 self.assertRaises(ZeroDivisionError, lambda: a // zero)
574 self.assertRaises(ZeroDivisionError, lambda: a / zero)
575 self.assertRaises(ZeroDivisionError, lambda: a / 0.0)
576 self.assertRaises(TypeError, lambda: a / '')
577
Eric Smith3ab08ca2010-12-04 15:17:38 +0000578 @support.requires_IEEE_754
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000579 def test_disallowed_special(self):
580 a = timedelta(42)
581 self.assertRaises(ValueError, a.__mul__, NAN)
582 self.assertRaises(ValueError, a.__truediv__, NAN)
583
584 def test_basic_attributes(self):
585 days, seconds, us = 1, 7, 31
586 td = timedelta(days, seconds, us)
587 self.assertEqual(td.days, days)
588 self.assertEqual(td.seconds, seconds)
589 self.assertEqual(td.microseconds, us)
590
591 def test_total_seconds(self):
592 td = timedelta(days=365)
593 self.assertEqual(td.total_seconds(), 31536000.0)
594 for total_seconds in [123456.789012, -123456.789012, 0.123456, 0, 1e6]:
595 td = timedelta(seconds=total_seconds)
596 self.assertEqual(td.total_seconds(), total_seconds)
597 # Issue8644: Test that td.total_seconds() has the same
598 # accuracy as td / timedelta(seconds=1).
599 for ms in [-1, -2, -123]:
600 td = timedelta(microseconds=ms)
601 self.assertEqual(td.total_seconds(), td / timedelta(seconds=1))
602
603 def test_carries(self):
604 t1 = timedelta(days=100,
605 weeks=-7,
606 hours=-24*(100-49),
607 minutes=-3,
608 seconds=12,
609 microseconds=(3*60 - 12) * 1e6 + 1)
610 t2 = timedelta(microseconds=1)
611 self.assertEqual(t1, t2)
612
613 def test_hash_equality(self):
614 t1 = timedelta(days=100,
615 weeks=-7,
616 hours=-24*(100-49),
617 minutes=-3,
618 seconds=12,
619 microseconds=(3*60 - 12) * 1000000)
620 t2 = timedelta()
621 self.assertEqual(hash(t1), hash(t2))
622
623 t1 += timedelta(weeks=7)
624 t2 += timedelta(days=7*7)
625 self.assertEqual(t1, t2)
626 self.assertEqual(hash(t1), hash(t2))
627
628 d = {t1: 1}
629 d[t2] = 2
630 self.assertEqual(len(d), 1)
631 self.assertEqual(d[t1], 2)
632
633 def test_pickling(self):
634 args = 12, 34, 56
635 orig = timedelta(*args)
636 for pickler, unpickler, proto in pickle_choices:
637 green = pickler.dumps(orig, proto)
638 derived = unpickler.loads(green)
639 self.assertEqual(orig, derived)
640
641 def test_compare(self):
642 t1 = timedelta(2, 3, 4)
643 t2 = timedelta(2, 3, 4)
644 self.assertEqual(t1, t2)
645 self.assertTrue(t1 <= t2)
646 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200647 self.assertFalse(t1 != t2)
648 self.assertFalse(t1 < t2)
649 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000650
651 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
652 t2 = timedelta(*args) # this is larger than t1
653 self.assertTrue(t1 < t2)
654 self.assertTrue(t2 > t1)
655 self.assertTrue(t1 <= t2)
656 self.assertTrue(t2 >= t1)
657 self.assertTrue(t1 != t2)
658 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200659 self.assertFalse(t1 == t2)
660 self.assertFalse(t2 == t1)
661 self.assertFalse(t1 > t2)
662 self.assertFalse(t2 < t1)
663 self.assertFalse(t1 >= t2)
664 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000665
666 for badarg in OTHERSTUFF:
667 self.assertEqual(t1 == badarg, False)
668 self.assertEqual(t1 != badarg, True)
669 self.assertEqual(badarg == t1, False)
670 self.assertEqual(badarg != t1, True)
671
672 self.assertRaises(TypeError, lambda: t1 <= badarg)
673 self.assertRaises(TypeError, lambda: t1 < badarg)
674 self.assertRaises(TypeError, lambda: t1 > badarg)
675 self.assertRaises(TypeError, lambda: t1 >= badarg)
676 self.assertRaises(TypeError, lambda: badarg <= t1)
677 self.assertRaises(TypeError, lambda: badarg < t1)
678 self.assertRaises(TypeError, lambda: badarg > t1)
679 self.assertRaises(TypeError, lambda: badarg >= t1)
680
681 def test_str(self):
682 td = timedelta
683 eq = self.assertEqual
684
685 eq(str(td(1)), "1 day, 0:00:00")
686 eq(str(td(-1)), "-1 day, 0:00:00")
687 eq(str(td(2)), "2 days, 0:00:00")
688 eq(str(td(-2)), "-2 days, 0:00:00")
689
690 eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
691 eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
692 eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
693 "-210 days, 23:12:34")
694
695 eq(str(td(milliseconds=1)), "0:00:00.001000")
696 eq(str(td(microseconds=3)), "0:00:00.000003")
697
698 eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
699 microseconds=999999)),
700 "999999999 days, 23:59:59.999999")
701
702 def test_repr(self):
703 name = 'datetime.' + self.theclass.__name__
704 self.assertEqual(repr(self.theclass(1)),
Utkarsh Upadhyaycc5a65c2017-07-25 23:51:33 +0200705 "%s(days=1)" % name)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000706 self.assertEqual(repr(self.theclass(10, 2)),
Utkarsh Upadhyaycc5a65c2017-07-25 23:51:33 +0200707 "%s(days=10, seconds=2)" % name)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000708 self.assertEqual(repr(self.theclass(-10, 2, 400000)),
Utkarsh Upadhyaycc5a65c2017-07-25 23:51:33 +0200709 "%s(days=-10, seconds=2, microseconds=400000)" % name)
710 self.assertEqual(repr(self.theclass(seconds=60)),
711 "%s(seconds=60)" % name)
712 self.assertEqual(repr(self.theclass()),
713 "%s(0)" % name)
714 self.assertEqual(repr(self.theclass(microseconds=100)),
715 "%s(microseconds=100)" % name)
716 self.assertEqual(repr(self.theclass(days=1, microseconds=100)),
717 "%s(days=1, microseconds=100)" % name)
718 self.assertEqual(repr(self.theclass(seconds=1, microseconds=100)),
719 "%s(seconds=1, microseconds=100)" % name)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000720
721 def test_roundtrip(self):
722 for td in (timedelta(days=999999999, hours=23, minutes=59,
723 seconds=59, microseconds=999999),
724 timedelta(days=-999999999),
725 timedelta(days=-999999999, seconds=1),
726 timedelta(days=1, seconds=2, microseconds=3)):
727
728 # Verify td -> string -> td identity.
729 s = repr(td)
730 self.assertTrue(s.startswith('datetime.'))
731 s = s[9:]
732 td2 = eval(s)
733 self.assertEqual(td, td2)
734
735 # Verify identity via reconstructing from pieces.
736 td2 = timedelta(td.days, td.seconds, td.microseconds)
737 self.assertEqual(td, td2)
738
739 def test_resolution_info(self):
740 self.assertIsInstance(timedelta.min, timedelta)
741 self.assertIsInstance(timedelta.max, timedelta)
742 self.assertIsInstance(timedelta.resolution, timedelta)
743 self.assertTrue(timedelta.max > timedelta.min)
744 self.assertEqual(timedelta.min, timedelta(-999999999))
745 self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
746 self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
747
748 def test_overflow(self):
749 tiny = timedelta.resolution
750
751 td = timedelta.min + tiny
752 td -= tiny # no problem
753 self.assertRaises(OverflowError, td.__sub__, tiny)
754 self.assertRaises(OverflowError, td.__add__, -tiny)
755
756 td = timedelta.max - tiny
757 td += tiny # no problem
758 self.assertRaises(OverflowError, td.__add__, tiny)
759 self.assertRaises(OverflowError, td.__sub__, -tiny)
760
761 self.assertRaises(OverflowError, lambda: -timedelta.max)
762
763 day = timedelta(1)
764 self.assertRaises(OverflowError, day.__mul__, 10**9)
765 self.assertRaises(OverflowError, day.__mul__, 1e9)
766 self.assertRaises(OverflowError, day.__truediv__, 1e-20)
767 self.assertRaises(OverflowError, day.__truediv__, 1e-10)
768 self.assertRaises(OverflowError, day.__truediv__, 9e-10)
769
Eric Smith3ab08ca2010-12-04 15:17:38 +0000770 @support.requires_IEEE_754
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000771 def _test_overflow_special(self):
772 day = timedelta(1)
773 self.assertRaises(OverflowError, day.__mul__, INF)
774 self.assertRaises(OverflowError, day.__mul__, -INF)
775
776 def test_microsecond_rounding(self):
777 td = timedelta
778 eq = self.assertEqual
779
780 # Single-field rounding.
781 eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
782 eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
Victor Stinner69cc4872015-09-08 23:58:54 +0200783 eq(td(milliseconds=0.5/1000), td(microseconds=0))
784 eq(td(milliseconds=-0.5/1000), td(microseconds=-0))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000785 eq(td(milliseconds=0.6/1000), td(microseconds=1))
786 eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
Victor Stinner69cc4872015-09-08 23:58:54 +0200787 eq(td(milliseconds=1.5/1000), td(microseconds=2))
788 eq(td(milliseconds=-1.5/1000), td(microseconds=-2))
789 eq(td(seconds=0.5/10**6), td(microseconds=0))
790 eq(td(seconds=-0.5/10**6), td(microseconds=-0))
791 eq(td(seconds=1/2**7), td(microseconds=7812))
792 eq(td(seconds=-1/2**7), td(microseconds=-7812))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000793
794 # Rounding due to contributions from more than one field.
795 us_per_hour = 3600e6
796 us_per_day = us_per_hour * 24
797 eq(td(days=.4/us_per_day), td(0))
798 eq(td(hours=.2/us_per_hour), td(0))
Victor Stinnercd5d7652015-09-09 01:09:21 +0200799 eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000800
801 eq(td(days=-.4/us_per_day), td(0))
802 eq(td(hours=-.2/us_per_hour), td(0))
803 eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
804
Victor Stinner69cc4872015-09-08 23:58:54 +0200805 # Test for a patch in Issue 8860
806 eq(td(microseconds=0.5), 0.5*td(microseconds=1.0))
807 eq(td(microseconds=0.5)//td.resolution, 0.5*td.resolution//td.resolution)
808
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000809 def test_massive_normalization(self):
810 td = timedelta(microseconds=-1)
811 self.assertEqual((td.days, td.seconds, td.microseconds),
812 (-1, 24*3600-1, 999999))
813
814 def test_bool(self):
815 self.assertTrue(timedelta(1))
816 self.assertTrue(timedelta(0, 1))
817 self.assertTrue(timedelta(0, 0, 1))
818 self.assertTrue(timedelta(microseconds=1))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200819 self.assertFalse(timedelta(0))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000820
821 def test_subclass_timedelta(self):
822
823 class T(timedelta):
824 @staticmethod
825 def from_td(td):
826 return T(td.days, td.seconds, td.microseconds)
827
828 def as_hours(self):
829 sum = (self.days * 24 +
830 self.seconds / 3600.0 +
831 self.microseconds / 3600e6)
832 return round(sum)
833
834 t1 = T(days=1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200835 self.assertIs(type(t1), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000836 self.assertEqual(t1.as_hours(), 24)
837
838 t2 = T(days=-1, seconds=-3600)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200839 self.assertIs(type(t2), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000840 self.assertEqual(t2.as_hours(), -25)
841
842 t3 = t1 + t2
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200843 self.assertIs(type(t3), timedelta)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000844 t4 = T.from_td(t3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200845 self.assertIs(type(t4), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000846 self.assertEqual(t3.days, t4.days)
847 self.assertEqual(t3.seconds, t4.seconds)
848 self.assertEqual(t3.microseconds, t4.microseconds)
849 self.assertEqual(str(t3), str(t4))
850 self.assertEqual(t4.as_hours(), -1)
851
Paul Ganssle89427cd2019-02-04 14:42:04 -0500852 def test_subclass_date(self):
853 class DateSubclass(date):
854 pass
855
856 d1 = DateSubclass(2018, 1, 5)
857 td = timedelta(days=1)
858
859 tests = [
860 ('add', lambda d, t: d + t, DateSubclass(2018, 1, 6)),
861 ('radd', lambda d, t: t + d, DateSubclass(2018, 1, 6)),
862 ('sub', lambda d, t: d - t, DateSubclass(2018, 1, 4)),
863 ]
864
865 for name, func, expected in tests:
866 with self.subTest(name):
867 act = func(d1, td)
868 self.assertEqual(act, expected)
869 self.assertIsInstance(act, DateSubclass)
870
871 def test_subclass_datetime(self):
872 class DateTimeSubclass(datetime):
873 pass
874
875 d1 = DateTimeSubclass(2018, 1, 5, 12, 30)
876 td = timedelta(days=1, minutes=30)
877
878 tests = [
879 ('add', lambda d, t: d + t, DateTimeSubclass(2018, 1, 6, 13)),
880 ('radd', lambda d, t: t + d, DateTimeSubclass(2018, 1, 6, 13)),
881 ('sub', lambda d, t: d - t, DateTimeSubclass(2018, 1, 4, 12)),
882 ]
883
884 for name, func, expected in tests:
885 with self.subTest(name):
886 act = func(d1, td)
887 self.assertEqual(act, expected)
888 self.assertIsInstance(act, DateTimeSubclass)
889
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000890 def test_division(self):
891 t = timedelta(hours=1, minutes=24, seconds=19)
892 second = timedelta(seconds=1)
893 self.assertEqual(t / second, 5059.0)
894 self.assertEqual(t // second, 5059)
895
896 t = timedelta(minutes=2, seconds=30)
897 minute = timedelta(minutes=1)
898 self.assertEqual(t / minute, 2.5)
899 self.assertEqual(t // minute, 2)
900
901 zerotd = timedelta(0)
902 self.assertRaises(ZeroDivisionError, truediv, t, zerotd)
903 self.assertRaises(ZeroDivisionError, floordiv, t, zerotd)
904
905 # self.assertRaises(TypeError, truediv, t, 2)
906 # note: floor division of a timedelta by an integer *is*
907 # currently permitted.
908
909 def test_remainder(self):
910 t = timedelta(minutes=2, seconds=30)
911 minute = timedelta(minutes=1)
912 r = t % minute
913 self.assertEqual(r, timedelta(seconds=30))
914
915 t = timedelta(minutes=-2, seconds=30)
916 r = t % minute
917 self.assertEqual(r, timedelta(seconds=30))
918
919 zerotd = timedelta(0)
920 self.assertRaises(ZeroDivisionError, mod, t, zerotd)
921
922 self.assertRaises(TypeError, mod, t, 10)
923
924 def test_divmod(self):
925 t = timedelta(minutes=2, seconds=30)
926 minute = timedelta(minutes=1)
927 q, r = divmod(t, minute)
928 self.assertEqual(q, 2)
929 self.assertEqual(r, timedelta(seconds=30))
930
931 t = timedelta(minutes=-2, seconds=30)
932 q, r = divmod(t, minute)
933 self.assertEqual(q, -2)
934 self.assertEqual(r, timedelta(seconds=30))
935
936 zerotd = timedelta(0)
937 self.assertRaises(ZeroDivisionError, divmod, t, zerotd)
938
939 self.assertRaises(TypeError, divmod, t, 10)
940
Oren Milman865e4b42017-09-19 15:58:11 +0300941 def test_issue31293(self):
942 # The interpreter shouldn't crash in case a timedelta is divided or
943 # multiplied by a float with a bad as_integer_ratio() method.
944 def get_bad_float(bad_ratio):
945 class BadFloat(float):
946 def as_integer_ratio(self):
947 return bad_ratio
948 return BadFloat()
949
950 with self.assertRaises(TypeError):
951 timedelta() / get_bad_float(1 << 1000)
952 with self.assertRaises(TypeError):
953 timedelta() * get_bad_float(1 << 1000)
954
955 for bad_ratio in [(), (42, ), (1, 2, 3)]:
956 with self.assertRaises(ValueError):
957 timedelta() / get_bad_float(bad_ratio)
958 with self.assertRaises(ValueError):
959 timedelta() * get_bad_float(bad_ratio)
960
Serhiy Storchaka4ffd4652017-10-23 17:12:28 +0300961 def test_issue31752(self):
962 # The interpreter shouldn't crash because divmod() returns negative
963 # remainder.
964 class BadInt(int):
965 def __mul__(self, other):
966 return Prod()
Serhiy Storchaka3ec0f492018-11-20 20:41:09 +0200967 def __rmul__(self, other):
968 return Prod()
969 def __floordiv__(self, other):
970 return Prod()
971 def __rfloordiv__(self, other):
972 return Prod()
Serhiy Storchaka4ffd4652017-10-23 17:12:28 +0300973
974 class Prod:
Serhiy Storchaka3ec0f492018-11-20 20:41:09 +0200975 def __add__(self, other):
976 return Sum()
Serhiy Storchaka4ffd4652017-10-23 17:12:28 +0300977 def __radd__(self, other):
978 return Sum()
979
980 class Sum(int):
981 def __divmod__(self, other):
Serhiy Storchaka3ec0f492018-11-20 20:41:09 +0200982 return divmodresult
Serhiy Storchaka4ffd4652017-10-23 17:12:28 +0300983
Serhiy Storchaka3ec0f492018-11-20 20:41:09 +0200984 for divmodresult in [None, (), (0, 1, 2), (0, -1)]:
985 with self.subTest(divmodresult=divmodresult):
986 # The following examples should not crash.
987 try:
988 timedelta(microseconds=BadInt(1))
989 except TypeError:
990 pass
991 try:
992 timedelta(hours=BadInt(1))
993 except TypeError:
994 pass
995 try:
996 timedelta(weeks=BadInt(1))
997 except (TypeError, ValueError):
998 pass
999 try:
1000 timedelta(1) * BadInt(1)
1001 except (TypeError, ValueError):
1002 pass
1003 try:
1004 BadInt(1) * timedelta(1)
1005 except TypeError:
1006 pass
1007 try:
1008 timedelta(1) // BadInt(1)
1009 except TypeError:
1010 pass
Serhiy Storchaka4ffd4652017-10-23 17:12:28 +03001011
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001012
1013#############################################################################
1014# date tests
1015
1016class TestDateOnly(unittest.TestCase):
1017 # Tests here won't pass if also run on datetime objects, so don't
1018 # subclass this to test datetimes too.
1019
1020 def test_delta_non_days_ignored(self):
1021 dt = date(2000, 1, 2)
1022 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
1023 microseconds=5)
1024 days = timedelta(delta.days)
1025 self.assertEqual(days, timedelta(1))
1026
1027 dt2 = dt + delta
1028 self.assertEqual(dt2, dt + days)
1029
1030 dt2 = delta + dt
1031 self.assertEqual(dt2, dt + days)
1032
1033 dt2 = dt - delta
1034 self.assertEqual(dt2, dt - days)
1035
1036 delta = -delta
1037 days = timedelta(delta.days)
1038 self.assertEqual(days, timedelta(-2))
1039
1040 dt2 = dt + delta
1041 self.assertEqual(dt2, dt + days)
1042
1043 dt2 = delta + dt
1044 self.assertEqual(dt2, dt + days)
1045
1046 dt2 = dt - delta
1047 self.assertEqual(dt2, dt - days)
1048
1049class SubclassDate(date):
1050 sub_var = 1
1051
1052class TestDate(HarmlessMixedComparison, unittest.TestCase):
1053 # Tests here should pass for both dates and datetimes, except for a
1054 # few tests that TestDateTime overrides.
1055
1056 theclass = date
1057
1058 def test_basic_attributes(self):
1059 dt = self.theclass(2002, 3, 1)
1060 self.assertEqual(dt.year, 2002)
1061 self.assertEqual(dt.month, 3)
1062 self.assertEqual(dt.day, 1)
1063
1064 def test_roundtrip(self):
1065 for dt in (self.theclass(1, 2, 3),
1066 self.theclass.today()):
1067 # Verify dt -> string -> date identity.
1068 s = repr(dt)
1069 self.assertTrue(s.startswith('datetime.'))
1070 s = s[9:]
1071 dt2 = eval(s)
1072 self.assertEqual(dt, dt2)
1073
1074 # Verify identity via reconstructing from pieces.
1075 dt2 = self.theclass(dt.year, dt.month, dt.day)
1076 self.assertEqual(dt, dt2)
1077
1078 def test_ordinal_conversions(self):
1079 # Check some fixed values.
1080 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
1081 (1, 12, 31, 365),
1082 (2, 1, 1, 366),
1083 # first example from "Calendrical Calculations"
1084 (1945, 11, 12, 710347)]:
1085 d = self.theclass(y, m, d)
1086 self.assertEqual(n, d.toordinal())
1087 fromord = self.theclass.fromordinal(n)
1088 self.assertEqual(d, fromord)
1089 if hasattr(fromord, "hour"):
1090 # if we're checking something fancier than a date, verify
1091 # the extra fields have been zeroed out
1092 self.assertEqual(fromord.hour, 0)
1093 self.assertEqual(fromord.minute, 0)
1094 self.assertEqual(fromord.second, 0)
1095 self.assertEqual(fromord.microsecond, 0)
1096
1097 # Check first and last days of year spottily across the whole
1098 # range of years supported.
1099 for year in range(MINYEAR, MAXYEAR+1, 7):
1100 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
1101 d = self.theclass(year, 1, 1)
1102 n = d.toordinal()
1103 d2 = self.theclass.fromordinal(n)
1104 self.assertEqual(d, d2)
1105 # Verify that moving back a day gets to the end of year-1.
1106 if year > 1:
1107 d = self.theclass.fromordinal(n-1)
1108 d2 = self.theclass(year-1, 12, 31)
1109 self.assertEqual(d, d2)
1110 self.assertEqual(d2.toordinal(), n-1)
1111
1112 # Test every day in a leap-year and a non-leap year.
1113 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
1114 for year, isleap in (2000, True), (2002, False):
1115 n = self.theclass(year, 1, 1).toordinal()
1116 for month, maxday in zip(range(1, 13), dim):
1117 if month == 2 and isleap:
1118 maxday += 1
1119 for day in range(1, maxday+1):
1120 d = self.theclass(year, month, day)
1121 self.assertEqual(d.toordinal(), n)
1122 self.assertEqual(d, self.theclass.fromordinal(n))
1123 n += 1
1124
1125 def test_extreme_ordinals(self):
1126 a = self.theclass.min
1127 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
1128 aord = a.toordinal()
1129 b = a.fromordinal(aord)
1130 self.assertEqual(a, b)
1131
1132 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
1133
1134 b = a + timedelta(days=1)
1135 self.assertEqual(b.toordinal(), aord + 1)
1136 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
1137
1138 a = self.theclass.max
1139 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
1140 aord = a.toordinal()
1141 b = a.fromordinal(aord)
1142 self.assertEqual(a, b)
1143
1144 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
1145
1146 b = a - timedelta(days=1)
1147 self.assertEqual(b.toordinal(), aord - 1)
1148 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
1149
1150 def test_bad_constructor_arguments(self):
1151 # bad years
1152 self.theclass(MINYEAR, 1, 1) # no exception
1153 self.theclass(MAXYEAR, 1, 1) # no exception
1154 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1155 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1156 # bad months
1157 self.theclass(2000, 1, 1) # no exception
1158 self.theclass(2000, 12, 1) # no exception
1159 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1160 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1161 # bad days
1162 self.theclass(2000, 2, 29) # no exception
1163 self.theclass(2004, 2, 29) # no exception
1164 self.theclass(2400, 2, 29) # no exception
1165 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1166 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1167 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1168 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1169 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1170 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1171
1172 def test_hash_equality(self):
1173 d = self.theclass(2000, 12, 31)
1174 # same thing
1175 e = self.theclass(2000, 12, 31)
1176 self.assertEqual(d, e)
1177 self.assertEqual(hash(d), hash(e))
1178
1179 dic = {d: 1}
1180 dic[e] = 2
1181 self.assertEqual(len(dic), 1)
1182 self.assertEqual(dic[d], 2)
1183 self.assertEqual(dic[e], 2)
1184
1185 d = self.theclass(2001, 1, 1)
1186 # same thing
1187 e = self.theclass(2001, 1, 1)
1188 self.assertEqual(d, e)
1189 self.assertEqual(hash(d), hash(e))
1190
1191 dic = {d: 1}
1192 dic[e] = 2
1193 self.assertEqual(len(dic), 1)
1194 self.assertEqual(dic[d], 2)
1195 self.assertEqual(dic[e], 2)
1196
1197 def test_computations(self):
1198 a = self.theclass(2002, 1, 31)
1199 b = self.theclass(1956, 1, 31)
1200 c = self.theclass(2001,2,1)
1201
1202 diff = a-b
1203 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1204 self.assertEqual(diff.seconds, 0)
1205 self.assertEqual(diff.microseconds, 0)
1206
1207 day = timedelta(1)
1208 week = timedelta(7)
1209 a = self.theclass(2002, 3, 2)
1210 self.assertEqual(a + day, self.theclass(2002, 3, 3))
1211 self.assertEqual(day + a, self.theclass(2002, 3, 3))
1212 self.assertEqual(a - day, self.theclass(2002, 3, 1))
1213 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
1214 self.assertEqual(a + week, self.theclass(2002, 3, 9))
1215 self.assertEqual(a - week, self.theclass(2002, 2, 23))
1216 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
1217 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
1218 self.assertEqual((a + week) - a, week)
1219 self.assertEqual((a + day) - a, day)
1220 self.assertEqual((a - week) - a, -week)
1221 self.assertEqual((a - day) - a, -day)
1222 self.assertEqual(a - (a + week), -week)
1223 self.assertEqual(a - (a + day), -day)
1224 self.assertEqual(a - (a - week), week)
1225 self.assertEqual(a - (a - day), day)
1226 self.assertEqual(c - (c - day), day)
1227
1228 # Add/sub ints or floats should be illegal
1229 for i in 1, 1.0:
1230 self.assertRaises(TypeError, lambda: a+i)
1231 self.assertRaises(TypeError, lambda: a-i)
1232 self.assertRaises(TypeError, lambda: i+a)
1233 self.assertRaises(TypeError, lambda: i-a)
1234
1235 # delta - date is senseless.
1236 self.assertRaises(TypeError, lambda: day - a)
1237 # mixing date and (delta or date) via * or // is senseless
1238 self.assertRaises(TypeError, lambda: day * a)
1239 self.assertRaises(TypeError, lambda: a * day)
1240 self.assertRaises(TypeError, lambda: day // a)
1241 self.assertRaises(TypeError, lambda: a // day)
1242 self.assertRaises(TypeError, lambda: a * a)
1243 self.assertRaises(TypeError, lambda: a // a)
1244 # date + date is senseless
1245 self.assertRaises(TypeError, lambda: a + a)
1246
1247 def test_overflow(self):
1248 tiny = self.theclass.resolution
1249
1250 for delta in [tiny, timedelta(1), timedelta(2)]:
1251 dt = self.theclass.min + delta
1252 dt -= delta # no problem
1253 self.assertRaises(OverflowError, dt.__sub__, delta)
1254 self.assertRaises(OverflowError, dt.__add__, -delta)
1255
1256 dt = self.theclass.max - delta
1257 dt += delta # no problem
1258 self.assertRaises(OverflowError, dt.__add__, delta)
1259 self.assertRaises(OverflowError, dt.__sub__, -delta)
1260
1261 def test_fromtimestamp(self):
1262 import time
1263
1264 # Try an arbitrary fixed value.
1265 year, month, day = 1999, 9, 19
1266 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
1267 d = self.theclass.fromtimestamp(ts)
1268 self.assertEqual(d.year, year)
1269 self.assertEqual(d.month, month)
1270 self.assertEqual(d.day, day)
1271
1272 def test_insane_fromtimestamp(self):
1273 # It's possible that some platform maps time_t to double,
1274 # and that this test will fail there. This test should
1275 # exempt such platforms (provided they return reasonable
1276 # results!).
1277 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001278 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001279 insane)
1280
1281 def test_today(self):
1282 import time
1283
1284 # We claim that today() is like fromtimestamp(time.time()), so
1285 # prove it.
1286 for dummy in range(3):
1287 today = self.theclass.today()
1288 ts = time.time()
1289 todayagain = self.theclass.fromtimestamp(ts)
1290 if today == todayagain:
1291 break
1292 # There are several legit reasons that could fail:
1293 # 1. It recently became midnight, between the today() and the
1294 # time() calls.
1295 # 2. The platform time() has such fine resolution that we'll
1296 # never get the same value twice.
1297 # 3. The platform time() has poor resolution, and we just
1298 # happened to call today() right before a resolution quantum
1299 # boundary.
1300 # 4. The system clock got fiddled between calls.
1301 # In any case, wait a little while and try again.
1302 time.sleep(0.1)
1303
1304 # It worked or it didn't. If it didn't, assume it's reason #2, and
1305 # let the test pass if they're within half a second of each other.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001306 if today != todayagain:
1307 self.assertAlmostEqual(todayagain, today,
1308 delta=timedelta(seconds=0.5))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001309
1310 def test_weekday(self):
1311 for i in range(7):
1312 # March 4, 2002 is a Monday
1313 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
1314 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
1315 # January 2, 1956 is a Monday
1316 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
1317 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
1318
1319 def test_isocalendar(self):
1320 # Check examples from
1321 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1322 for i in range(7):
1323 d = self.theclass(2003, 12, 22+i)
1324 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
1325 d = self.theclass(2003, 12, 29) + timedelta(i)
1326 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
1327 d = self.theclass(2004, 1, 5+i)
1328 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
1329 d = self.theclass(2009, 12, 21+i)
1330 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
1331 d = self.theclass(2009, 12, 28) + timedelta(i)
1332 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
1333 d = self.theclass(2010, 1, 4+i)
1334 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
1335
1336 def test_iso_long_years(self):
1337 # Calculate long ISO years and compare to table from
1338 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1339 ISO_LONG_YEARS_TABLE = """
1340 4 32 60 88
1341 9 37 65 93
1342 15 43 71 99
1343 20 48 76
1344 26 54 82
1345
1346 105 133 161 189
1347 111 139 167 195
1348 116 144 172
1349 122 150 178
1350 128 156 184
1351
1352 201 229 257 285
1353 207 235 263 291
1354 212 240 268 296
1355 218 246 274
1356 224 252 280
1357
1358 303 331 359 387
1359 308 336 364 392
1360 314 342 370 398
1361 320 348 376
1362 325 353 381
1363 """
1364 iso_long_years = sorted(map(int, ISO_LONG_YEARS_TABLE.split()))
1365 L = []
1366 for i in range(400):
1367 d = self.theclass(2000+i, 12, 31)
1368 d1 = self.theclass(1600+i, 12, 31)
1369 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
1370 if d.isocalendar()[1] == 53:
1371 L.append(i)
1372 self.assertEqual(L, iso_long_years)
1373
1374 def test_isoformat(self):
1375 t = self.theclass(2, 3, 2)
1376 self.assertEqual(t.isoformat(), "0002-03-02")
1377
1378 def test_ctime(self):
1379 t = self.theclass(2002, 3, 2)
1380 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
1381
1382 def test_strftime(self):
1383 t = self.theclass(2005, 3, 2)
1384 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
1385 self.assertEqual(t.strftime(""), "") # SF bug #761337
1386 self.assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784
1387
1388 self.assertRaises(TypeError, t.strftime) # needs an arg
1389 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
1390 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
1391
1392 # test that unicode input is allowed (issue 2782)
1393 self.assertEqual(t.strftime("%m"), "03")
1394
1395 # A naive object replaces %z and %Z w/ empty strings.
1396 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1397
1398 #make sure that invalid format specifiers are handled correctly
1399 #self.assertRaises(ValueError, t.strftime, "%e")
1400 #self.assertRaises(ValueError, t.strftime, "%")
1401 #self.assertRaises(ValueError, t.strftime, "%#")
1402
1403 #oh well, some systems just ignore those invalid ones.
Martin Panter46f50722016-05-26 05:35:26 +00001404 #at least, exercise them to make sure that no crashes
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001405 #are generated
1406 for f in ["%e", "%", "%#"]:
1407 try:
1408 t.strftime(f)
1409 except ValueError:
1410 pass
1411
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03001412 # bpo-34482: Check that surrogates don't cause a crash.
1413 try:
1414 t.strftime('%y\ud800%m')
1415 except UnicodeEncodeError:
1416 pass
1417
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001418 #check that this standard extension works
1419 t.strftime("%f")
1420
MichaelSaah454b3d42019-01-14 05:23:39 -05001421 def test_strftime_trailing_percent(self):
1422 # bpo-35066: make sure trailing '%' doesn't cause
1423 # datetime's strftime to complain
1424 t = self.theclass(2005, 3, 2)
1425 try:
1426 _time.strftime('%')
1427 except ValueError:
1428 self.skipTest('time module does not support trailing %')
1429 self.assertEqual(t.strftime('%'), '%')
1430 self.assertEqual(t.strftime("m:%m d:%d y:%y %"), "m:03 d:02 y:05 %")
1431
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001432 def test_format(self):
1433 dt = self.theclass(2007, 9, 10)
1434 self.assertEqual(dt.__format__(''), str(dt))
1435
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02001436 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001437 dt.__format__(123)
1438
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001439 # check that a derived class's __str__() gets called
1440 class A(self.theclass):
1441 def __str__(self):
1442 return 'A'
1443 a = A(2007, 9, 10)
1444 self.assertEqual(a.__format__(''), 'A')
1445
1446 # check that a derived class's strftime gets called
1447 class B(self.theclass):
1448 def strftime(self, format_spec):
1449 return 'B'
1450 b = B(2007, 9, 10)
1451 self.assertEqual(b.__format__(''), str(dt))
1452
1453 for fmt in ["m:%m d:%d y:%y",
1454 "m:%m d:%d y:%y H:%H M:%M S:%S",
1455 "%z %Z",
1456 ]:
1457 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1458 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1459 self.assertEqual(b.__format__(fmt), 'B')
1460
1461 def test_resolution_info(self):
1462 # XXX: Should min and max respect subclassing?
1463 if issubclass(self.theclass, datetime):
1464 expected_class = datetime
1465 else:
1466 expected_class = date
1467 self.assertIsInstance(self.theclass.min, expected_class)
1468 self.assertIsInstance(self.theclass.max, expected_class)
1469 self.assertIsInstance(self.theclass.resolution, timedelta)
1470 self.assertTrue(self.theclass.max > self.theclass.min)
1471
1472 def test_extreme_timedelta(self):
1473 big = self.theclass.max - self.theclass.min
1474 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
1475 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
1476 # n == 315537897599999999 ~= 2**58.13
1477 justasbig = timedelta(0, 0, n)
1478 self.assertEqual(big, justasbig)
1479 self.assertEqual(self.theclass.min + big, self.theclass.max)
1480 self.assertEqual(self.theclass.max - big, self.theclass.min)
1481
1482 def test_timetuple(self):
1483 for i in range(7):
1484 # January 2, 1956 is a Monday (0)
1485 d = self.theclass(1956, 1, 2+i)
1486 t = d.timetuple()
1487 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
1488 # February 1, 1956 is a Wednesday (2)
1489 d = self.theclass(1956, 2, 1+i)
1490 t = d.timetuple()
1491 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
1492 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
1493 # of the year.
1494 d = self.theclass(1956, 3, 1+i)
1495 t = d.timetuple()
1496 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
1497 self.assertEqual(t.tm_year, 1956)
1498 self.assertEqual(t.tm_mon, 3)
1499 self.assertEqual(t.tm_mday, 1+i)
1500 self.assertEqual(t.tm_hour, 0)
1501 self.assertEqual(t.tm_min, 0)
1502 self.assertEqual(t.tm_sec, 0)
1503 self.assertEqual(t.tm_wday, (3+i)%7)
1504 self.assertEqual(t.tm_yday, 61+i)
1505 self.assertEqual(t.tm_isdst, -1)
1506
1507 def test_pickling(self):
1508 args = 6, 7, 23
1509 orig = self.theclass(*args)
1510 for pickler, unpickler, proto in pickle_choices:
1511 green = pickler.dumps(orig, proto)
1512 derived = unpickler.loads(green)
1513 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02001514 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001515
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02001516 def test_compat_unpickle(self):
1517 tests = [
1518 b"cdatetime\ndate\n(S'\\x07\\xdf\\x0b\\x1b'\ntR.",
1519 b'cdatetime\ndate\n(U\x04\x07\xdf\x0b\x1btR.',
1520 b'\x80\x02cdatetime\ndate\nU\x04\x07\xdf\x0b\x1b\x85R.',
1521 ]
1522 args = 2015, 11, 27
1523 expected = self.theclass(*args)
1524 for data in tests:
1525 for loads in pickle_loads:
1526 derived = loads(data, encoding='latin1')
1527 self.assertEqual(derived, expected)
1528
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001529 def test_compare(self):
1530 t1 = self.theclass(2, 3, 4)
1531 t2 = self.theclass(2, 3, 4)
1532 self.assertEqual(t1, t2)
1533 self.assertTrue(t1 <= t2)
1534 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001535 self.assertFalse(t1 != t2)
1536 self.assertFalse(t1 < t2)
1537 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001538
1539 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
1540 t2 = self.theclass(*args) # this is larger than t1
1541 self.assertTrue(t1 < t2)
1542 self.assertTrue(t2 > t1)
1543 self.assertTrue(t1 <= t2)
1544 self.assertTrue(t2 >= t1)
1545 self.assertTrue(t1 != t2)
1546 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001547 self.assertFalse(t1 == t2)
1548 self.assertFalse(t2 == t1)
1549 self.assertFalse(t1 > t2)
1550 self.assertFalse(t2 < t1)
1551 self.assertFalse(t1 >= t2)
1552 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001553
1554 for badarg in OTHERSTUFF:
1555 self.assertEqual(t1 == badarg, False)
1556 self.assertEqual(t1 != badarg, True)
1557 self.assertEqual(badarg == t1, False)
1558 self.assertEqual(badarg != t1, True)
1559
1560 self.assertRaises(TypeError, lambda: t1 < badarg)
1561 self.assertRaises(TypeError, lambda: t1 > badarg)
1562 self.assertRaises(TypeError, lambda: t1 >= badarg)
1563 self.assertRaises(TypeError, lambda: badarg <= t1)
1564 self.assertRaises(TypeError, lambda: badarg < t1)
1565 self.assertRaises(TypeError, lambda: badarg > t1)
1566 self.assertRaises(TypeError, lambda: badarg >= t1)
1567
1568 def test_mixed_compare(self):
1569 our = self.theclass(2000, 4, 5)
1570
1571 # Our class can be compared for equality to other classes
1572 self.assertEqual(our == 1, False)
1573 self.assertEqual(1 == our, False)
1574 self.assertEqual(our != 1, True)
1575 self.assertEqual(1 != our, True)
1576
1577 # But the ordering is undefined
1578 self.assertRaises(TypeError, lambda: our < 1)
1579 self.assertRaises(TypeError, lambda: 1 < our)
1580
1581 # Repeat those tests with a different class
1582
1583 class SomeClass:
1584 pass
1585
1586 their = SomeClass()
1587 self.assertEqual(our == their, False)
1588 self.assertEqual(their == our, False)
1589 self.assertEqual(our != their, True)
1590 self.assertEqual(their != our, True)
1591 self.assertRaises(TypeError, lambda: our < their)
1592 self.assertRaises(TypeError, lambda: their < our)
1593
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001594 def test_bool(self):
1595 # All dates are considered true.
1596 self.assertTrue(self.theclass.min)
1597 self.assertTrue(self.theclass.max)
1598
Alexander Belopolsky89da3492011-05-02 13:14:24 -04001599 def test_strftime_y2k(self):
1600 for y in (1, 49, 70, 99, 100, 999, 1000, 1970):
Florent Xicluna49ce0682011-11-01 12:56:14 +01001601 d = self.theclass(y, 1, 1)
1602 # Issue 13305: For years < 1000, the value is not always
1603 # padded to 4 digits across platforms. The C standard
1604 # assumes year >= 1900, so it does not specify the number
1605 # of digits.
1606 if d.strftime("%Y") != '%04d' % y:
1607 # Year 42 returns '42', not padded
1608 self.assertEqual(d.strftime("%Y"), '%d' % y)
Victor Stinner15a83e82016-03-12 08:16:48 +01001609 # '0042' is obtained anyway
1610 self.assertEqual(d.strftime("%4Y"), '%04d' % y)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001611
1612 def test_replace(self):
1613 cls = self.theclass
1614 args = [1, 2, 3]
1615 base = cls(*args)
1616 self.assertEqual(base, base.replace())
1617
1618 i = 0
1619 for name, newval in (("year", 2),
1620 ("month", 3),
1621 ("day", 4)):
1622 newargs = args[:]
1623 newargs[i] = newval
1624 expected = cls(*newargs)
1625 got = base.replace(**{name: newval})
1626 self.assertEqual(expected, got)
1627 i += 1
1628
1629 # Out of bounds.
1630 base = cls(2000, 2, 29)
1631 self.assertRaises(ValueError, base.replace, year=2001)
1632
Paul Ganssle191e9932017-11-09 16:34:29 -05001633 def test_subclass_replace(self):
1634 class DateSubclass(self.theclass):
1635 pass
1636
1637 dt = DateSubclass(2012, 1, 1)
1638 self.assertIs(type(dt.replace(year=2013)), DateSubclass)
1639
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001640 def test_subclass_date(self):
1641
1642 class C(self.theclass):
1643 theAnswer = 42
1644
1645 def __new__(cls, *args, **kws):
1646 temp = kws.copy()
1647 extra = temp.pop('extra')
1648 result = self.theclass.__new__(cls, *args, **temp)
1649 result.extra = extra
1650 return result
1651
1652 def newmeth(self, start):
1653 return start + self.year + self.month
1654
1655 args = 2003, 4, 14
1656
1657 dt1 = self.theclass(*args)
1658 dt2 = C(*args, **{'extra': 7})
1659
1660 self.assertEqual(dt2.__class__, C)
1661 self.assertEqual(dt2.theAnswer, 42)
1662 self.assertEqual(dt2.extra, 7)
1663 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1664 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1665
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05001666 def test_subclass_alternate_constructors(self):
1667 # Test that alternate constructors call the constructor
1668 class DateSubclass(self.theclass):
1669 def __new__(cls, *args, **kwargs):
1670 result = self.theclass.__new__(cls, *args, **kwargs)
1671 result.extra = 7
1672
1673 return result
1674
1675 args = (2003, 4, 14)
1676 d_ord = 731319 # Equivalent ordinal date
1677 d_isoformat = '2003-04-14' # Equivalent isoformat()
1678
1679 base_d = DateSubclass(*args)
1680 self.assertIsInstance(base_d, DateSubclass)
1681 self.assertEqual(base_d.extra, 7)
1682
1683 # Timestamp depends on time zone, so we'll calculate the equivalent here
1684 ts = datetime.combine(base_d, time(0)).timestamp()
1685
1686 test_cases = [
1687 ('fromordinal', (d_ord,)),
1688 ('fromtimestamp', (ts,)),
1689 ('fromisoformat', (d_isoformat,)),
1690 ]
1691
1692 for constr_name, constr_args in test_cases:
1693 for base_obj in (DateSubclass, base_d):
1694 # Test both the classmethod and method
1695 with self.subTest(base_obj_type=type(base_obj),
1696 constr_name=constr_name):
1697 constr = getattr(base_obj, constr_name)
1698
1699 dt = constr(*constr_args)
1700
1701 # Test that it creates the right subclass
1702 self.assertIsInstance(dt, DateSubclass)
1703
1704 # Test that it's equal to the base object
1705 self.assertEqual(dt, base_d)
1706
1707 # Test that it called the constructor
1708 self.assertEqual(dt.extra, 7)
1709
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001710 def test_pickling_subclass_date(self):
1711
1712 args = 6, 7, 23
1713 orig = SubclassDate(*args)
1714 for pickler, unpickler, proto in pickle_choices:
1715 green = pickler.dumps(orig, proto)
1716 derived = unpickler.loads(green)
1717 self.assertEqual(orig, derived)
1718
1719 def test_backdoor_resistance(self):
1720 # For fast unpickling, the constructor accepts a pickle byte string.
1721 # This is a low-overhead backdoor. A user can (by intent or
1722 # mistake) pass a string directly, which (if it's the right length)
1723 # will get treated like a pickle, and bypass the normal sanity
1724 # checks in the constructor. This can create insane objects.
1725 # The constructor doesn't want to burn the time to validate all
1726 # fields, but does check the month field. This stops, e.g.,
1727 # datetime.datetime('1995-03-25') from yielding an insane object.
1728 base = b'1995-03-25'
1729 if not issubclass(self.theclass, datetime):
1730 base = base[:4]
1731 for month_byte in b'9', b'\0', b'\r', b'\xff':
1732 self.assertRaises(TypeError, self.theclass,
1733 base[:2] + month_byte + base[3:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001734 if issubclass(self.theclass, datetime):
1735 # Good bytes, but bad tzinfo:
1736 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
1737 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001738
1739 for ord_byte in range(1, 13):
1740 # This shouldn't blow up because of the month byte alone. If
1741 # the implementation changes to do more-careful checking, it may
1742 # blow up because other fields are insane.
1743 self.theclass(base[:2] + bytes([ord_byte]) + base[3:])
1744
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001745 def test_fromisoformat(self):
1746 # Test that isoformat() is reversible
1747 base_dates = [
1748 (1, 1, 1),
1749 (1000, 2, 14),
1750 (1900, 1, 1),
1751 (2000, 2, 29),
1752 (2004, 11, 12),
1753 (2004, 4, 3),
1754 (2017, 5, 30)
1755 ]
1756
1757 for dt_tuple in base_dates:
1758 dt = self.theclass(*dt_tuple)
1759 dt_str = dt.isoformat()
1760 with self.subTest(dt_str=dt_str):
1761 dt_rt = self.theclass.fromisoformat(dt.isoformat())
1762
1763 self.assertEqual(dt, dt_rt)
1764
1765 def test_fromisoformat_subclass(self):
1766 class DateSubclass(self.theclass):
1767 pass
1768
1769 dt = DateSubclass(2014, 12, 14)
1770
1771 dt_rt = DateSubclass.fromisoformat(dt.isoformat())
1772
1773 self.assertIsInstance(dt_rt, DateSubclass)
1774
1775 def test_fromisoformat_fails(self):
1776 # Test that fromisoformat() fails on invalid values
1777 bad_strs = [
1778 '', # Empty string
Paul Ganssle096329f2018-08-23 11:06:20 -04001779 '\ud800', # bpo-34454: Surrogate code point
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001780 '009-03-04', # Not 10 characters
1781 '123456789', # Not a date
1782 '200a-12-04', # Invalid character in year
1783 '2009-1a-04', # Invalid character in month
1784 '2009-12-0a', # Invalid character in day
1785 '2009-01-32', # Invalid day
1786 '2009-02-29', # Invalid leap day
1787 '20090228', # Valid ISO8601 output not from isoformat()
Paul Ganssle096329f2018-08-23 11:06:20 -04001788 '2009\ud80002\ud80028', # Separators are surrogate codepoints
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001789 ]
1790
1791 for bad_str in bad_strs:
1792 with self.assertRaises(ValueError):
1793 self.theclass.fromisoformat(bad_str)
1794
1795 def test_fromisoformat_fails_typeerror(self):
1796 # Test that fromisoformat fails when passed the wrong type
1797 import io
1798
1799 bad_types = [b'2009-03-01', None, io.StringIO('2009-03-01')]
1800 for bad_type in bad_types:
1801 with self.assertRaises(TypeError):
1802 self.theclass.fromisoformat(bad_type)
1803
Paul Ganssle88c09372019-04-29 09:22:03 -04001804 def test_fromisocalendar(self):
1805 # For each test case, assert that fromisocalendar is the
1806 # inverse of the isocalendar function
1807 dates = [
1808 (2016, 4, 3),
1809 (2005, 1, 2), # (2004, 53, 7)
1810 (2008, 12, 30), # (2009, 1, 2)
1811 (2010, 1, 2), # (2009, 53, 6)
1812 (2009, 12, 31), # (2009, 53, 4)
1813 (1900, 1, 1), # Unusual non-leap year (year % 100 == 0)
1814 (1900, 12, 31),
1815 (2000, 1, 1), # Unusual leap year (year % 400 == 0)
1816 (2000, 12, 31),
1817 (2004, 1, 1), # Leap year
1818 (2004, 12, 31),
1819 (1, 1, 1),
1820 (9999, 12, 31),
1821 (MINYEAR, 1, 1),
1822 (MAXYEAR, 12, 31),
1823 ]
1824
1825 for datecomps in dates:
1826 with self.subTest(datecomps=datecomps):
1827 dobj = self.theclass(*datecomps)
1828 isocal = dobj.isocalendar()
1829
1830 d_roundtrip = self.theclass.fromisocalendar(*isocal)
1831
1832 self.assertEqual(dobj, d_roundtrip)
1833
1834 def test_fromisocalendar_value_errors(self):
1835 isocals = [
1836 (2019, 0, 1),
1837 (2019, -1, 1),
1838 (2019, 54, 1),
1839 (2019, 1, 0),
1840 (2019, 1, -1),
1841 (2019, 1, 8),
1842 (2019, 53, 1),
1843 (10000, 1, 1),
1844 (0, 1, 1),
1845 (9999999, 1, 1),
1846 (2<<32, 1, 1),
1847 (2019, 2<<32, 1),
1848 (2019, 1, 2<<32),
1849 ]
1850
1851 for isocal in isocals:
1852 with self.subTest(isocal=isocal):
1853 with self.assertRaises(ValueError):
1854 self.theclass.fromisocalendar(*isocal)
1855
1856 def test_fromisocalendar_type_errors(self):
1857 err_txformers = [
1858 str,
1859 float,
1860 lambda x: None,
1861 ]
1862
1863 # Take a valid base tuple and transform it to contain one argument
1864 # with the wrong type. Repeat this for each argument, e.g.
1865 # [("2019", 1, 1), (2019, "1", 1), (2019, 1, "1"), ...]
1866 isocals = []
1867 base = (2019, 1, 1)
1868 for i in range(3):
1869 for txformer in err_txformers:
1870 err_val = list(base)
1871 err_val[i] = txformer(err_val[i])
1872 isocals.append(tuple(err_val))
1873
1874 for isocal in isocals:
1875 with self.subTest(isocal=isocal):
1876 with self.assertRaises(TypeError):
1877 self.theclass.fromisocalendar(*isocal)
1878
1879
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001880#############################################################################
1881# datetime tests
1882
1883class SubclassDatetime(datetime):
1884 sub_var = 1
1885
1886class TestDateTime(TestDate):
1887
1888 theclass = datetime
1889
1890 def test_basic_attributes(self):
1891 dt = self.theclass(2002, 3, 1, 12, 0)
1892 self.assertEqual(dt.year, 2002)
1893 self.assertEqual(dt.month, 3)
1894 self.assertEqual(dt.day, 1)
1895 self.assertEqual(dt.hour, 12)
1896 self.assertEqual(dt.minute, 0)
1897 self.assertEqual(dt.second, 0)
1898 self.assertEqual(dt.microsecond, 0)
1899
1900 def test_basic_attributes_nonzero(self):
1901 # Make sure all attributes are non-zero so bugs in
1902 # bit-shifting access show up.
1903 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1904 self.assertEqual(dt.year, 2002)
1905 self.assertEqual(dt.month, 3)
1906 self.assertEqual(dt.day, 1)
1907 self.assertEqual(dt.hour, 12)
1908 self.assertEqual(dt.minute, 59)
1909 self.assertEqual(dt.second, 59)
1910 self.assertEqual(dt.microsecond, 8000)
1911
1912 def test_roundtrip(self):
1913 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1914 self.theclass.now()):
1915 # Verify dt -> string -> datetime identity.
1916 s = repr(dt)
1917 self.assertTrue(s.startswith('datetime.'))
1918 s = s[9:]
1919 dt2 = eval(s)
1920 self.assertEqual(dt, dt2)
1921
1922 # Verify identity via reconstructing from pieces.
1923 dt2 = self.theclass(dt.year, dt.month, dt.day,
1924 dt.hour, dt.minute, dt.second,
1925 dt.microsecond)
1926 self.assertEqual(dt, dt2)
1927
1928 def test_isoformat(self):
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001929 t = self.theclass(1, 2, 3, 4, 5, 1, 123)
1930 self.assertEqual(t.isoformat(), "0001-02-03T04:05:01.000123")
1931 self.assertEqual(t.isoformat('T'), "0001-02-03T04:05:01.000123")
1932 self.assertEqual(t.isoformat(' '), "0001-02-03 04:05:01.000123")
1933 self.assertEqual(t.isoformat('\x00'), "0001-02-03\x0004:05:01.000123")
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03001934 # bpo-34482: Check that surrogates are handled properly.
1935 self.assertEqual(t.isoformat('\ud800'),
1936 "0001-02-03\ud80004:05:01.000123")
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001937 self.assertEqual(t.isoformat(timespec='hours'), "0001-02-03T04")
1938 self.assertEqual(t.isoformat(timespec='minutes'), "0001-02-03T04:05")
1939 self.assertEqual(t.isoformat(timespec='seconds'), "0001-02-03T04:05:01")
1940 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1941 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000123")
1942 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01.000123")
1943 self.assertEqual(t.isoformat(sep=' ', timespec='minutes'), "0001-02-03 04:05")
1944 self.assertRaises(ValueError, t.isoformat, timespec='foo')
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03001945 # bpo-34482: Check that surrogates are handled properly.
1946 self.assertRaises(ValueError, t.isoformat, timespec='\ud800')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001947 # str is ISO format with the separator forced to a blank.
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001948 self.assertEqual(str(t), "0001-02-03 04:05:01.000123")
1949
1950 t = self.theclass(1, 2, 3, 4, 5, 1, 999500, tzinfo=timezone.utc)
1951 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999+00:00")
1952
1953 t = self.theclass(1, 2, 3, 4, 5, 1, 999500)
1954 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999")
1955
1956 t = self.theclass(1, 2, 3, 4, 5, 1)
1957 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01")
1958 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1959 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000000")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001960
1961 t = self.theclass(2, 3, 2)
1962 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1963 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1964 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1965 # str is ISO format with the separator forced to a blank.
1966 self.assertEqual(str(t), "0002-03-02 00:00:00")
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001967 # ISO format with timezone
1968 tz = FixedOffset(timedelta(seconds=16), 'XXX')
1969 t = self.theclass(2, 3, 2, tzinfo=tz)
1970 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00+00:00:16")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001971
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001972 def test_isoformat_timezone(self):
1973 tzoffsets = [
1974 ('05:00', timedelta(hours=5)),
1975 ('02:00', timedelta(hours=2)),
1976 ('06:27', timedelta(hours=6, minutes=27)),
1977 ('12:32:30', timedelta(hours=12, minutes=32, seconds=30)),
1978 ('02:04:09.123456', timedelta(hours=2, minutes=4, seconds=9, microseconds=123456))
1979 ]
1980
1981 tzinfos = [
1982 ('', None),
1983 ('+00:00', timezone.utc),
1984 ('+00:00', timezone(timedelta(0))),
1985 ]
1986
1987 tzinfos += [
1988 (prefix + expected, timezone(sign * td))
1989 for expected, td in tzoffsets
1990 for prefix, sign in [('-', -1), ('+', 1)]
1991 ]
1992
1993 dt_base = self.theclass(2016, 4, 1, 12, 37, 9)
1994 exp_base = '2016-04-01T12:37:09'
1995
1996 for exp_tz, tzi in tzinfos:
1997 dt = dt_base.replace(tzinfo=tzi)
1998 exp = exp_base + exp_tz
1999 with self.subTest(tzi=tzi):
2000 assert dt.isoformat() == exp
2001
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002002 def test_format(self):
2003 dt = self.theclass(2007, 9, 10, 4, 5, 1, 123)
2004 self.assertEqual(dt.__format__(''), str(dt))
2005
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02002006 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002007 dt.__format__(123)
2008
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002009 # check that a derived class's __str__() gets called
2010 class A(self.theclass):
2011 def __str__(self):
2012 return 'A'
2013 a = A(2007, 9, 10, 4, 5, 1, 123)
2014 self.assertEqual(a.__format__(''), 'A')
2015
2016 # check that a derived class's strftime gets called
2017 class B(self.theclass):
2018 def strftime(self, format_spec):
2019 return 'B'
2020 b = B(2007, 9, 10, 4, 5, 1, 123)
2021 self.assertEqual(b.__format__(''), str(dt))
2022
2023 for fmt in ["m:%m d:%d y:%y",
2024 "m:%m d:%d y:%y H:%H M:%M S:%S",
2025 "%z %Z",
2026 ]:
2027 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
2028 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
2029 self.assertEqual(b.__format__(fmt), 'B')
2030
2031 def test_more_ctime(self):
2032 # Test fields that TestDate doesn't touch.
2033 import time
2034
2035 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
2036 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
2037 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
2038 # out. The difference is that t.ctime() produces " 2" for the day,
2039 # but platform ctime() produces "02" for the day. According to
2040 # C99, t.ctime() is correct here.
2041 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
2042
2043 # So test a case where that difference doesn't matter.
2044 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
2045 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
2046
2047 def test_tz_independent_comparing(self):
2048 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
2049 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
2050 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
2051 self.assertEqual(dt1, dt3)
2052 self.assertTrue(dt2 > dt3)
2053
2054 # Make sure comparison doesn't forget microseconds, and isn't done
2055 # via comparing a float timestamp (an IEEE double doesn't have enough
Leo Ariasc3d95082018-02-03 18:36:10 -06002056 # precision to span microsecond resolution across years 1 through 9999,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002057 # so comparing via timestamp necessarily calls some distinct values
2058 # equal).
2059 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
2060 us = timedelta(microseconds=1)
2061 dt2 = dt1 + us
2062 self.assertEqual(dt2 - dt1, us)
2063 self.assertTrue(dt1 < dt2)
2064
2065 def test_strftime_with_bad_tzname_replace(self):
2066 # verify ok if tzinfo.tzname().replace() returns a non-string
2067 class MyTzInfo(FixedOffset):
2068 def tzname(self, dt):
2069 class MyStr(str):
2070 def replace(self, *args):
2071 return None
2072 return MyStr('name')
2073 t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
2074 self.assertRaises(TypeError, t.strftime, '%Z')
2075
2076 def test_bad_constructor_arguments(self):
2077 # bad years
2078 self.theclass(MINYEAR, 1, 1) # no exception
2079 self.theclass(MAXYEAR, 1, 1) # no exception
2080 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
2081 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
2082 # bad months
2083 self.theclass(2000, 1, 1) # no exception
2084 self.theclass(2000, 12, 1) # no exception
2085 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
2086 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
2087 # bad days
2088 self.theclass(2000, 2, 29) # no exception
2089 self.theclass(2004, 2, 29) # no exception
2090 self.theclass(2400, 2, 29) # no exception
2091 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
2092 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
2093 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
2094 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
2095 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
2096 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
2097 # bad hours
2098 self.theclass(2000, 1, 31, 0) # no exception
2099 self.theclass(2000, 1, 31, 23) # no exception
2100 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
2101 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
2102 # bad minutes
2103 self.theclass(2000, 1, 31, 23, 0) # no exception
2104 self.theclass(2000, 1, 31, 23, 59) # no exception
2105 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
2106 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
2107 # bad seconds
2108 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
2109 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
2110 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
2111 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
2112 # bad microseconds
2113 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
2114 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
2115 self.assertRaises(ValueError, self.theclass,
2116 2000, 1, 31, 23, 59, 59, -1)
2117 self.assertRaises(ValueError, self.theclass,
2118 2000, 1, 31, 23, 59, 59,
2119 1000000)
Alexander Belopolsky47649ab2016-08-08 17:05:40 -04002120 # bad fold
2121 self.assertRaises(ValueError, self.theclass,
2122 2000, 1, 31, fold=-1)
2123 self.assertRaises(ValueError, self.theclass,
2124 2000, 1, 31, fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002125 # Positional fold:
2126 self.assertRaises(TypeError, self.theclass,
2127 2000, 1, 31, 23, 59, 59, 0, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04002128
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002129 def test_hash_equality(self):
2130 d = self.theclass(2000, 12, 31, 23, 30, 17)
2131 e = self.theclass(2000, 12, 31, 23, 30, 17)
2132 self.assertEqual(d, e)
2133 self.assertEqual(hash(d), hash(e))
2134
2135 dic = {d: 1}
2136 dic[e] = 2
2137 self.assertEqual(len(dic), 1)
2138 self.assertEqual(dic[d], 2)
2139 self.assertEqual(dic[e], 2)
2140
2141 d = self.theclass(2001, 1, 1, 0, 5, 17)
2142 e = self.theclass(2001, 1, 1, 0, 5, 17)
2143 self.assertEqual(d, e)
2144 self.assertEqual(hash(d), hash(e))
2145
2146 dic = {d: 1}
2147 dic[e] = 2
2148 self.assertEqual(len(dic), 1)
2149 self.assertEqual(dic[d], 2)
2150 self.assertEqual(dic[e], 2)
2151
2152 def test_computations(self):
2153 a = self.theclass(2002, 1, 31)
2154 b = self.theclass(1956, 1, 31)
2155 diff = a-b
2156 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
2157 self.assertEqual(diff.seconds, 0)
2158 self.assertEqual(diff.microseconds, 0)
2159 a = self.theclass(2002, 3, 2, 17, 6)
2160 millisec = timedelta(0, 0, 1000)
2161 hour = timedelta(0, 3600)
2162 day = timedelta(1)
2163 week = timedelta(7)
2164 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
2165 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
2166 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
2167 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
2168 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
2169 self.assertEqual(a - hour, a + -hour)
2170 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
2171 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
2172 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
2173 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
2174 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
2175 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
2176 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
2177 self.assertEqual((a + week) - a, week)
2178 self.assertEqual((a + day) - a, day)
2179 self.assertEqual((a + hour) - a, hour)
2180 self.assertEqual((a + millisec) - a, millisec)
2181 self.assertEqual((a - week) - a, -week)
2182 self.assertEqual((a - day) - a, -day)
2183 self.assertEqual((a - hour) - a, -hour)
2184 self.assertEqual((a - millisec) - a, -millisec)
2185 self.assertEqual(a - (a + week), -week)
2186 self.assertEqual(a - (a + day), -day)
2187 self.assertEqual(a - (a + hour), -hour)
2188 self.assertEqual(a - (a + millisec), -millisec)
2189 self.assertEqual(a - (a - week), week)
2190 self.assertEqual(a - (a - day), day)
2191 self.assertEqual(a - (a - hour), hour)
2192 self.assertEqual(a - (a - millisec), millisec)
2193 self.assertEqual(a + (week + day + hour + millisec),
2194 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
2195 self.assertEqual(a + (week + day + hour + millisec),
2196 (((a + week) + day) + hour) + millisec)
2197 self.assertEqual(a - (week + day + hour + millisec),
2198 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
2199 self.assertEqual(a - (week + day + hour + millisec),
2200 (((a - week) - day) - hour) - millisec)
2201 # Add/sub ints or floats should be illegal
2202 for i in 1, 1.0:
2203 self.assertRaises(TypeError, lambda: a+i)
2204 self.assertRaises(TypeError, lambda: a-i)
2205 self.assertRaises(TypeError, lambda: i+a)
2206 self.assertRaises(TypeError, lambda: i-a)
2207
2208 # delta - datetime is senseless.
2209 self.assertRaises(TypeError, lambda: day - a)
2210 # mixing datetime and (delta or datetime) via * or // is senseless
2211 self.assertRaises(TypeError, lambda: day * a)
2212 self.assertRaises(TypeError, lambda: a * day)
2213 self.assertRaises(TypeError, lambda: day // a)
2214 self.assertRaises(TypeError, lambda: a // day)
2215 self.assertRaises(TypeError, lambda: a * a)
2216 self.assertRaises(TypeError, lambda: a // a)
2217 # datetime + datetime is senseless
2218 self.assertRaises(TypeError, lambda: a + a)
2219
2220 def test_pickling(self):
2221 args = 6, 7, 23, 20, 59, 1, 64**2
2222 orig = self.theclass(*args)
2223 for pickler, unpickler, proto in pickle_choices:
2224 green = pickler.dumps(orig, proto)
2225 derived = unpickler.loads(green)
2226 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02002227 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002228
2229 def test_more_pickling(self):
2230 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
Serhiy Storchakabad12572014-12-15 14:03:42 +02002231 for proto in range(pickle.HIGHEST_PROTOCOL + 1):
2232 s = pickle.dumps(a, proto)
2233 b = pickle.loads(s)
2234 self.assertEqual(b.year, 2003)
2235 self.assertEqual(b.month, 2)
2236 self.assertEqual(b.day, 7)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002237
2238 def test_pickling_subclass_datetime(self):
2239 args = 6, 7, 23, 20, 59, 1, 64**2
2240 orig = SubclassDatetime(*args)
2241 for pickler, unpickler, proto in pickle_choices:
2242 green = pickler.dumps(orig, proto)
2243 derived = unpickler.loads(green)
2244 self.assertEqual(orig, derived)
2245
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02002246 def test_compat_unpickle(self):
2247 tests = [
2248 b'cdatetime\ndatetime\n('
2249 b"S'\\x07\\xdf\\x0b\\x1b\\x14;\\x01\\x00\\x10\\x00'\ntR.",
2250
2251 b'cdatetime\ndatetime\n('
2252 b'U\n\x07\xdf\x0b\x1b\x14;\x01\x00\x10\x00tR.',
2253
2254 b'\x80\x02cdatetime\ndatetime\n'
2255 b'U\n\x07\xdf\x0b\x1b\x14;\x01\x00\x10\x00\x85R.',
2256 ]
2257 args = 2015, 11, 27, 20, 59, 1, 64**2
2258 expected = self.theclass(*args)
2259 for data in tests:
2260 for loads in pickle_loads:
2261 derived = loads(data, encoding='latin1')
2262 self.assertEqual(derived, expected)
2263
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002264 def test_more_compare(self):
2265 # The test_compare() inherited from TestDate covers the error cases.
2266 # We just want to test lexicographic ordering on the members datetime
2267 # has that date lacks.
2268 args = [2000, 11, 29, 20, 58, 16, 999998]
2269 t1 = self.theclass(*args)
2270 t2 = self.theclass(*args)
2271 self.assertEqual(t1, t2)
2272 self.assertTrue(t1 <= t2)
2273 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002274 self.assertFalse(t1 != t2)
2275 self.assertFalse(t1 < t2)
2276 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002277
2278 for i in range(len(args)):
2279 newargs = args[:]
2280 newargs[i] = args[i] + 1
2281 t2 = self.theclass(*newargs) # this is larger than t1
2282 self.assertTrue(t1 < t2)
2283 self.assertTrue(t2 > t1)
2284 self.assertTrue(t1 <= t2)
2285 self.assertTrue(t2 >= t1)
2286 self.assertTrue(t1 != t2)
2287 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002288 self.assertFalse(t1 == t2)
2289 self.assertFalse(t2 == t1)
2290 self.assertFalse(t1 > t2)
2291 self.assertFalse(t2 < t1)
2292 self.assertFalse(t1 >= t2)
2293 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002294
2295
2296 # A helper for timestamp constructor tests.
2297 def verify_field_equality(self, expected, got):
2298 self.assertEqual(expected.tm_year, got.year)
2299 self.assertEqual(expected.tm_mon, got.month)
2300 self.assertEqual(expected.tm_mday, got.day)
2301 self.assertEqual(expected.tm_hour, got.hour)
2302 self.assertEqual(expected.tm_min, got.minute)
2303 self.assertEqual(expected.tm_sec, got.second)
2304
2305 def test_fromtimestamp(self):
2306 import time
2307
2308 ts = time.time()
2309 expected = time.localtime(ts)
2310 got = self.theclass.fromtimestamp(ts)
2311 self.verify_field_equality(expected, got)
2312
2313 def test_utcfromtimestamp(self):
2314 import time
2315
2316 ts = time.time()
2317 expected = time.gmtime(ts)
2318 got = self.theclass.utcfromtimestamp(ts)
2319 self.verify_field_equality(expected, got)
2320
Alexander Belopolskya4415142012-06-08 12:33:09 -04002321 # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in
2322 # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0).
2323 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
2324 def test_timestamp_naive(self):
2325 t = self.theclass(1970, 1, 1)
2326 self.assertEqual(t.timestamp(), 18000.0)
2327 t = self.theclass(1970, 1, 1, 1, 2, 3, 4)
2328 self.assertEqual(t.timestamp(),
2329 18000.0 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002330 # Missing hour
2331 t0 = self.theclass(2012, 3, 11, 2, 30)
2332 t1 = t0.replace(fold=1)
2333 self.assertEqual(self.theclass.fromtimestamp(t1.timestamp()),
2334 t0 - timedelta(hours=1))
2335 self.assertEqual(self.theclass.fromtimestamp(t0.timestamp()),
2336 t1 + timedelta(hours=1))
Alexander Belopolskya4415142012-06-08 12:33:09 -04002337 # Ambiguous hour defaults to DST
2338 t = self.theclass(2012, 11, 4, 1, 30)
2339 self.assertEqual(self.theclass.fromtimestamp(t.timestamp()), t)
2340
2341 # Timestamp may raise an overflow error on some platforms
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002342 # XXX: Do we care to support the first and last year?
2343 for t in [self.theclass(2,1,1), self.theclass(9998,12,12)]:
Alexander Belopolskya4415142012-06-08 12:33:09 -04002344 try:
2345 s = t.timestamp()
2346 except OverflowError:
2347 pass
2348 else:
2349 self.assertEqual(self.theclass.fromtimestamp(s), t)
2350
2351 def test_timestamp_aware(self):
2352 t = self.theclass(1970, 1, 1, tzinfo=timezone.utc)
2353 self.assertEqual(t.timestamp(), 0.0)
2354 t = self.theclass(1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone.utc)
2355 self.assertEqual(t.timestamp(),
2356 3600 + 2*60 + 3 + 4*1e-6)
2357 t = self.theclass(1970, 1, 1, 1, 2, 3, 4,
2358 tzinfo=timezone(timedelta(hours=-5), 'EST'))
2359 self.assertEqual(t.timestamp(),
2360 18000 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002361
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002362 @support.run_with_tz('MSK-03') # Something east of Greenwich
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002363 def test_microsecond_rounding(self):
Victor Stinner5ebfe422015-09-18 14:52:15 +02002364 for fts in [self.theclass.fromtimestamp,
2365 self.theclass.utcfromtimestamp]:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002366 zero = fts(0)
2367 self.assertEqual(zero.second, 0)
2368 self.assertEqual(zero.microsecond, 0)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002369 one = fts(1e-6)
Victor Stinner8050ca92012-03-14 00:17:05 +01002370 try:
2371 minus_one = fts(-1e-6)
2372 except OSError:
2373 # localtime(-1) and gmtime(-1) is not supported on Windows
2374 pass
2375 else:
2376 self.assertEqual(minus_one.second, 59)
2377 self.assertEqual(minus_one.microsecond, 999999)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002378
Victor Stinner8050ca92012-03-14 00:17:05 +01002379 t = fts(-1e-8)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002380 self.assertEqual(t, zero)
Victor Stinner8050ca92012-03-14 00:17:05 +01002381 t = fts(-9e-7)
2382 self.assertEqual(t, minus_one)
2383 t = fts(-1e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002384 self.assertEqual(t, zero)
Victor Stinner8820a352015-09-05 10:50:20 +02002385 t = fts(-1/2**7)
2386 self.assertEqual(t.second, 59)
Victor Stinner7667f582015-09-09 01:02:23 +02002387 self.assertEqual(t.microsecond, 992188)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002388
2389 t = fts(1e-7)
2390 self.assertEqual(t, zero)
2391 t = fts(9e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002392 self.assertEqual(t, one)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002393 t = fts(0.99999949)
2394 self.assertEqual(t.second, 0)
2395 self.assertEqual(t.microsecond, 999999)
2396 t = fts(0.9999999)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002397 self.assertEqual(t.second, 1)
2398 self.assertEqual(t.microsecond, 0)
Victor Stinneradfefa52015-09-04 23:57:25 +02002399 t = fts(1/2**7)
2400 self.assertEqual(t.second, 0)
Victor Stinner7667f582015-09-09 01:02:23 +02002401 self.assertEqual(t.microsecond, 7812)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002402
Victor Stinnerb67f0962017-02-10 10:34:02 +01002403 def test_timestamp_limits(self):
2404 # minimum timestamp
2405 min_dt = self.theclass.min.replace(tzinfo=timezone.utc)
2406 min_ts = min_dt.timestamp()
Victor Stinner6f37e362017-02-10 11:45:14 +01002407 try:
2408 # date 0001-01-01 00:00:00+00:00: timestamp=-62135596800
2409 self.assertEqual(self.theclass.fromtimestamp(min_ts, tz=timezone.utc),
2410 min_dt)
Victor Stinner2a35c932017-02-10 12:37:21 +01002411 except (OverflowError, OSError) as exc:
2412 # the date 0001-01-01 doesn't fit into 32-bit time_t,
2413 # or platform doesn't support such very old date
Victor Stinner6f37e362017-02-10 11:45:14 +01002414 self.skipTest(str(exc))
Victor Stinnerb67f0962017-02-10 10:34:02 +01002415
2416 # maximum timestamp: set seconds to zero to avoid rounding issues
2417 max_dt = self.theclass.max.replace(tzinfo=timezone.utc,
2418 second=0, microsecond=0)
2419 max_ts = max_dt.timestamp()
2420 # date 9999-12-31 23:59:00+00:00: timestamp 253402300740
2421 self.assertEqual(self.theclass.fromtimestamp(max_ts, tz=timezone.utc),
2422 max_dt)
2423
2424 # number of seconds greater than 1 year: make sure that the new date
2425 # is not valid in datetime.datetime limits
2426 delta = 3600 * 24 * 400
2427
2428 # too small
2429 ts = min_ts - delta
2430 # converting a Python int to C time_t can raise a OverflowError,
2431 # especially on 32-bit platforms.
2432 with self.assertRaises((ValueError, OverflowError)):
2433 self.theclass.fromtimestamp(ts)
2434 with self.assertRaises((ValueError, OverflowError)):
2435 self.theclass.utcfromtimestamp(ts)
2436
2437 # too big
2438 ts = max_dt.timestamp() + delta
2439 with self.assertRaises((ValueError, OverflowError)):
2440 self.theclass.fromtimestamp(ts)
2441 with self.assertRaises((ValueError, OverflowError)):
2442 self.theclass.utcfromtimestamp(ts)
2443
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002444 def test_insane_fromtimestamp(self):
2445 # It's possible that some platform maps time_t to double,
2446 # and that this test will fail there. This test should
2447 # exempt such platforms (provided they return reasonable
2448 # results!).
2449 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002450 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002451 insane)
2452
2453 def test_insane_utcfromtimestamp(self):
2454 # It's possible that some platform maps time_t to double,
2455 # and that this test will fail there. This test should
2456 # exempt such platforms (provided they return reasonable
2457 # results!).
2458 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002459 self.assertRaises(OverflowError, self.theclass.utcfromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002460 insane)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002461
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002462 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2463 def test_negative_float_fromtimestamp(self):
2464 # The result is tz-dependent; at least test that this doesn't
2465 # fail (like it did before bug 1646728 was fixed).
2466 self.theclass.fromtimestamp(-1.05)
2467
2468 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2469 def test_negative_float_utcfromtimestamp(self):
2470 d = self.theclass.utcfromtimestamp(-1.05)
2471 self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
2472
2473 def test_utcnow(self):
2474 import time
2475
2476 # Call it a success if utcnow() and utcfromtimestamp() are within
2477 # a second of each other.
2478 tolerance = timedelta(seconds=1)
2479 for dummy in range(3):
2480 from_now = self.theclass.utcnow()
2481 from_timestamp = self.theclass.utcfromtimestamp(time.time())
2482 if abs(from_timestamp - from_now) <= tolerance:
2483 break
2484 # Else try again a few times.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002485 self.assertLessEqual(abs(from_timestamp - from_now), tolerance)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002486
2487 def test_strptime(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002488 string = '2004-12-01 13:02:47.197'
2489 format = '%Y-%m-%d %H:%M:%S.%f'
2490 expected = _strptime._strptime_datetime(self.theclass, string, format)
2491 got = self.theclass.strptime(string, format)
2492 self.assertEqual(expected, got)
2493 self.assertIs(type(expected), self.theclass)
2494 self.assertIs(type(got), self.theclass)
2495
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03002496 # bpo-34482: Check that surrogates are handled properly.
2497 inputs = [
2498 ('2004-12-01\ud80013:02:47.197', '%Y-%m-%d\ud800%H:%M:%S.%f'),
2499 ('2004\ud80012-01 13:02:47.197', '%Y\ud800%m-%d %H:%M:%S.%f'),
2500 ('2004-12-01 13:02\ud80047.197', '%Y-%m-%d %H:%M\ud800%S.%f'),
2501 ]
2502 for string, format in inputs:
2503 with self.subTest(string=string, format=format):
2504 expected = _strptime._strptime_datetime(self.theclass, string,
2505 format)
2506 got = self.theclass.strptime(string, format)
2507 self.assertEqual(expected, got)
2508
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002509 strptime = self.theclass.strptime
Mike Gleen6b9c2042019-06-18 19:14:57 +01002510
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002511 self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
2512 self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
Mario Corchero32318932017-10-26 01:35:41 +01002513 self.assertEqual(
2514 strptime("-00:02:01.000003", "%z").utcoffset(),
2515 -timedelta(minutes=2, seconds=1, microseconds=3)
2516 )
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002517 # Only local timezone and UTC are supported
2518 for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
2519 (-_time.timezone, _time.tzname[0])):
2520 if tzseconds < 0:
2521 sign = '-'
2522 seconds = -tzseconds
2523 else:
2524 sign ='+'
2525 seconds = tzseconds
2526 hours, minutes = divmod(seconds//60, 60)
2527 dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
Martin Panterfca22322015-11-16 09:22:19 +00002528 dt = strptime(dtstr, "%z %Z")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002529 self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
2530 self.assertEqual(dt.tzname(), tzname)
2531 # Can produce inconsistent datetime
2532 dtstr, fmt = "+1234 UTC", "%z %Z"
2533 dt = strptime(dtstr, fmt)
2534 self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE)
2535 self.assertEqual(dt.tzname(), 'UTC')
2536 # yet will roundtrip
2537 self.assertEqual(dt.strftime(fmt), dtstr)
2538
2539 # Produce naive datetime if no %z is provided
2540 self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
2541
2542 with self.assertRaises(ValueError): strptime("-2400", "%z")
2543 with self.assertRaises(ValueError): strptime("-000", "%z")
2544
Mike Gleen6b9c2042019-06-18 19:14:57 +01002545 def test_strptime_single_digit(self):
2546 # bpo-34903: Check that single digit dates and times are allowed.
2547
2548 strptime = self.theclass.strptime
2549
2550 with self.assertRaises(ValueError):
2551 # %y does require two digits.
2552 newdate = strptime('01/02/3 04:05:06', '%d/%m/%y %H:%M:%S')
2553 dt1 = self.theclass(2003, 2, 1, 4, 5, 6)
2554 dt2 = self.theclass(2003, 1, 2, 4, 5, 6)
2555 dt3 = self.theclass(2003, 2, 1, 0, 0, 0)
2556 dt4 = self.theclass(2003, 1, 25, 0, 0, 0)
2557 inputs = [
2558 ('%d', '1/02/03 4:5:6', '%d/%m/%y %H:%M:%S', dt1),
2559 ('%m', '01/2/03 4:5:6', '%d/%m/%y %H:%M:%S', dt1),
2560 ('%H', '01/02/03 4:05:06', '%d/%m/%y %H:%M:%S', dt1),
2561 ('%M', '01/02/03 04:5:06', '%d/%m/%y %H:%M:%S', dt1),
2562 ('%S', '01/02/03 04:05:6', '%d/%m/%y %H:%M:%S', dt1),
2563 ('%j', '2/03 04am:05:06', '%j/%y %I%p:%M:%S',dt2),
2564 ('%I', '02/03 4am:05:06', '%j/%y %I%p:%M:%S',dt2),
2565 ('%w', '6/04/03', '%w/%U/%y', dt3),
2566 # %u requires a single digit.
2567 ('%W', '6/4/2003', '%u/%W/%Y', dt3),
2568 ('%V', '6/4/2003', '%u/%V/%G', dt4),
2569 ]
2570 for reason, string, format, target in inputs:
2571 reason = 'test single digit ' + reason
2572 with self.subTest(reason=reason,
2573 string=string,
2574 format=format,
2575 target=target):
2576 newdate = strptime(string, format)
2577 self.assertEqual(newdate, target, msg=reason)
2578
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002579 def test_more_timetuple(self):
2580 # This tests fields beyond those tested by the TestDate.test_timetuple.
2581 t = self.theclass(2004, 12, 31, 6, 22, 33)
2582 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
2583 self.assertEqual(t.timetuple(),
2584 (t.year, t.month, t.day,
2585 t.hour, t.minute, t.second,
2586 t.weekday(),
2587 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
2588 -1))
2589 tt = t.timetuple()
2590 self.assertEqual(tt.tm_year, t.year)
2591 self.assertEqual(tt.tm_mon, t.month)
2592 self.assertEqual(tt.tm_mday, t.day)
2593 self.assertEqual(tt.tm_hour, t.hour)
2594 self.assertEqual(tt.tm_min, t.minute)
2595 self.assertEqual(tt.tm_sec, t.second)
2596 self.assertEqual(tt.tm_wday, t.weekday())
2597 self.assertEqual(tt.tm_yday, t.toordinal() -
2598 date(t.year, 1, 1).toordinal() + 1)
2599 self.assertEqual(tt.tm_isdst, -1)
2600
2601 def test_more_strftime(self):
2602 # This tests fields beyond those tested by the TestDate.test_strftime.
2603 t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
2604 self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
2605 "12 31 04 000047 33 22 06 366")
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04002606 for (s, us), z in [((33, 123), "33.000123"), ((33, 0), "33"),]:
2607 tz = timezone(-timedelta(hours=2, seconds=s, microseconds=us))
2608 t = t.replace(tzinfo=tz)
2609 self.assertEqual(t.strftime("%z"), "-0200" + z)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002610
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03002611 # bpo-34482: Check that surrogates don't cause a crash.
2612 try:
2613 t.strftime('%y\ud800%m %H\ud800%M')
2614 except UnicodeEncodeError:
2615 pass
2616
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002617 def test_extract(self):
2618 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2619 self.assertEqual(dt.date(), date(2002, 3, 4))
2620 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2621
2622 def test_combine(self):
2623 d = date(2002, 3, 4)
2624 t = time(18, 45, 3, 1234)
2625 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2626 combine = self.theclass.combine
2627 dt = combine(d, t)
2628 self.assertEqual(dt, expected)
2629
2630 dt = combine(time=t, date=d)
2631 self.assertEqual(dt, expected)
2632
2633 self.assertEqual(d, dt.date())
2634 self.assertEqual(t, dt.time())
2635 self.assertEqual(dt, combine(dt.date(), dt.time()))
2636
2637 self.assertRaises(TypeError, combine) # need an arg
2638 self.assertRaises(TypeError, combine, d) # need two args
2639 self.assertRaises(TypeError, combine, t, d) # args reversed
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002640 self.assertRaises(TypeError, combine, d, t, 1) # wrong tzinfo type
2641 self.assertRaises(TypeError, combine, d, t, 1, 2) # too many args
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002642 self.assertRaises(TypeError, combine, "date", "time") # wrong types
2643 self.assertRaises(TypeError, combine, d, "time") # wrong type
2644 self.assertRaises(TypeError, combine, "date", t) # wrong type
2645
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002646 # tzinfo= argument
2647 dt = combine(d, t, timezone.utc)
2648 self.assertIs(dt.tzinfo, timezone.utc)
2649 dt = combine(d, t, tzinfo=timezone.utc)
2650 self.assertIs(dt.tzinfo, timezone.utc)
2651 t = time()
2652 dt = combine(dt, t)
2653 self.assertEqual(dt.date(), d)
2654 self.assertEqual(dt.time(), t)
2655
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002656 def test_replace(self):
2657 cls = self.theclass
2658 args = [1, 2, 3, 4, 5, 6, 7]
2659 base = cls(*args)
2660 self.assertEqual(base, base.replace())
2661
2662 i = 0
2663 for name, newval in (("year", 2),
2664 ("month", 3),
2665 ("day", 4),
2666 ("hour", 5),
2667 ("minute", 6),
2668 ("second", 7),
2669 ("microsecond", 8)):
2670 newargs = args[:]
2671 newargs[i] = newval
2672 expected = cls(*newargs)
2673 got = base.replace(**{name: newval})
2674 self.assertEqual(expected, got)
2675 i += 1
2676
2677 # Out of bounds.
2678 base = cls(2000, 2, 29)
2679 self.assertRaises(ValueError, base.replace, year=2001)
2680
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002681 @support.run_with_tz('EDT4')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002682 def test_astimezone(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002683 dt = self.theclass.now()
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002684 f = FixedOffset(44, "0044")
2685 dt_utc = dt.replace(tzinfo=timezone(timedelta(hours=-4), 'EDT'))
2686 self.assertEqual(dt.astimezone(), dt_utc) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002687 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
2688 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002689 dt_f = dt.replace(tzinfo=f) + timedelta(hours=4, minutes=44)
2690 self.assertEqual(dt.astimezone(f), dt_f) # naive
2691 self.assertEqual(dt.astimezone(tz=f), dt_f) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002692
2693 class Bogus(tzinfo):
2694 def utcoffset(self, dt): return None
2695 def dst(self, dt): return timedelta(0)
2696 bog = Bogus()
2697 self.assertRaises(ValueError, dt.astimezone, bog) # naive
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002698 self.assertEqual(dt.replace(tzinfo=bog).astimezone(f), dt_f)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002699
2700 class AlsoBogus(tzinfo):
2701 def utcoffset(self, dt): return timedelta(0)
2702 def dst(self, dt): return None
2703 alsobog = AlsoBogus()
2704 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
2705
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002706 class Broken(tzinfo):
2707 def utcoffset(self, dt): return 1
2708 def dst(self, dt): return 1
2709 broken = Broken()
2710 dt_broken = dt.replace(tzinfo=broken)
2711 with self.assertRaises(TypeError):
2712 dt_broken.astimezone()
2713
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002714 def test_subclass_datetime(self):
2715
2716 class C(self.theclass):
2717 theAnswer = 42
2718
2719 def __new__(cls, *args, **kws):
2720 temp = kws.copy()
2721 extra = temp.pop('extra')
2722 result = self.theclass.__new__(cls, *args, **temp)
2723 result.extra = extra
2724 return result
2725
2726 def newmeth(self, start):
2727 return start + self.year + self.month + self.second
2728
2729 args = 2003, 4, 14, 12, 13, 41
2730
2731 dt1 = self.theclass(*args)
2732 dt2 = C(*args, **{'extra': 7})
2733
2734 self.assertEqual(dt2.__class__, C)
2735 self.assertEqual(dt2.theAnswer, 42)
2736 self.assertEqual(dt2.extra, 7)
2737 self.assertEqual(dt1.toordinal(), dt2.toordinal())
2738 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
2739 dt1.second - 7)
2740
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002741 def test_subclass_alternate_constructors_datetime(self):
2742 # Test that alternate constructors call the constructor
2743 class DateTimeSubclass(self.theclass):
2744 def __new__(cls, *args, **kwargs):
2745 result = self.theclass.__new__(cls, *args, **kwargs)
2746 result.extra = 7
2747
2748 return result
2749
2750 args = (2003, 4, 14, 12, 30, 15, 123456)
2751 d_isoformat = '2003-04-14T12:30:15.123456' # Equivalent isoformat()
2752 utc_ts = 1050323415.123456 # UTC timestamp
2753
2754 base_d = DateTimeSubclass(*args)
2755 self.assertIsInstance(base_d, DateTimeSubclass)
2756 self.assertEqual(base_d.extra, 7)
2757
2758 # Timestamp depends on time zone, so we'll calculate the equivalent here
2759 ts = base_d.timestamp()
2760
2761 test_cases = [
Paul Ganssle89427cd2019-02-04 14:42:04 -05002762 ('fromtimestamp', (ts,), base_d),
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002763 # See https://bugs.python.org/issue32417
Paul Ganssle89427cd2019-02-04 14:42:04 -05002764 ('fromtimestamp', (ts, timezone.utc),
2765 base_d.astimezone(timezone.utc)),
2766 ('utcfromtimestamp', (utc_ts,), base_d),
2767 ('fromisoformat', (d_isoformat,), base_d),
2768 ('strptime', (d_isoformat, '%Y-%m-%dT%H:%M:%S.%f'), base_d),
2769 ('combine', (date(*args[0:3]), time(*args[3:])), base_d),
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002770 ]
2771
Paul Ganssle89427cd2019-02-04 14:42:04 -05002772 for constr_name, constr_args, expected in test_cases:
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002773 for base_obj in (DateTimeSubclass, base_d):
2774 # Test both the classmethod and method
2775 with self.subTest(base_obj_type=type(base_obj),
2776 constr_name=constr_name):
Paul Ganssle89427cd2019-02-04 14:42:04 -05002777 constructor = getattr(base_obj, constr_name)
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002778
Paul Ganssle89427cd2019-02-04 14:42:04 -05002779 dt = constructor(*constr_args)
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002780
2781 # Test that it creates the right subclass
2782 self.assertIsInstance(dt, DateTimeSubclass)
2783
2784 # Test that it's equal to the base object
Paul Ganssle89427cd2019-02-04 14:42:04 -05002785 self.assertEqual(dt, expected)
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002786
2787 # Test that it called the constructor
2788 self.assertEqual(dt.extra, 7)
2789
Paul Ganssle89427cd2019-02-04 14:42:04 -05002790 def test_subclass_now(self):
2791 # Test that alternate constructors call the constructor
2792 class DateTimeSubclass(self.theclass):
2793 def __new__(cls, *args, **kwargs):
2794 result = self.theclass.__new__(cls, *args, **kwargs)
2795 result.extra = 7
2796
2797 return result
2798
2799 test_cases = [
2800 ('now', 'now', {}),
2801 ('utcnow', 'utcnow', {}),
2802 ('now_utc', 'now', {'tz': timezone.utc}),
2803 ('now_fixed', 'now', {'tz': timezone(timedelta(hours=-5), "EST")}),
2804 ]
2805
2806 for name, meth_name, kwargs in test_cases:
2807 with self.subTest(name):
2808 constr = getattr(DateTimeSubclass, meth_name)
2809 dt = constr(**kwargs)
2810
2811 self.assertIsInstance(dt, DateTimeSubclass)
2812 self.assertEqual(dt.extra, 7)
2813
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002814 def test_fromisoformat_datetime(self):
2815 # Test that isoformat() is reversible
2816 base_dates = [
2817 (1, 1, 1),
2818 (1900, 1, 1),
2819 (2004, 11, 12),
2820 (2017, 5, 30)
2821 ]
2822
2823 base_times = [
2824 (0, 0, 0, 0),
2825 (0, 0, 0, 241000),
2826 (0, 0, 0, 234567),
2827 (12, 30, 45, 234567)
2828 ]
2829
2830 separators = [' ', 'T']
2831
2832 tzinfos = [None, timezone.utc,
2833 timezone(timedelta(hours=-5)),
2834 timezone(timedelta(hours=2))]
2835
2836 dts = [self.theclass(*date_tuple, *time_tuple, tzinfo=tzi)
2837 for date_tuple in base_dates
2838 for time_tuple in base_times
2839 for tzi in tzinfos]
2840
2841 for dt in dts:
2842 for sep in separators:
2843 dtstr = dt.isoformat(sep=sep)
2844
2845 with self.subTest(dtstr=dtstr):
2846 dt_rt = self.theclass.fromisoformat(dtstr)
2847 self.assertEqual(dt, dt_rt)
2848
2849 def test_fromisoformat_timezone(self):
2850 base_dt = self.theclass(2014, 12, 30, 12, 30, 45, 217456)
2851
2852 tzoffsets = [
2853 timedelta(hours=5), timedelta(hours=2),
2854 timedelta(hours=6, minutes=27),
2855 timedelta(hours=12, minutes=32, seconds=30),
2856 timedelta(hours=2, minutes=4, seconds=9, microseconds=123456)
2857 ]
2858
2859 tzoffsets += [-1 * td for td in tzoffsets]
2860
2861 tzinfos = [None, timezone.utc,
2862 timezone(timedelta(hours=0))]
2863
2864 tzinfos += [timezone(td) for td in tzoffsets]
2865
2866 for tzi in tzinfos:
2867 dt = base_dt.replace(tzinfo=tzi)
2868 dtstr = dt.isoformat()
2869
2870 with self.subTest(tstr=dtstr):
2871 dt_rt = self.theclass.fromisoformat(dtstr)
2872 assert dt == dt_rt, dt_rt
2873
2874 def test_fromisoformat_separators(self):
2875 separators = [
2876 ' ', 'T', '\u007f', # 1-bit widths
2877 '\u0080', 'ʁ', # 2-bit widths
2878 'ᛇ', '時', # 3-bit widths
Paul Ganssle096329f2018-08-23 11:06:20 -04002879 '🐍', # 4-bit widths
2880 '\ud800', # bpo-34454: Surrogate code point
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002881 ]
2882
2883 for sep in separators:
2884 dt = self.theclass(2018, 1, 31, 23, 59, 47, 124789)
2885 dtstr = dt.isoformat(sep=sep)
2886
2887 with self.subTest(dtstr=dtstr):
2888 dt_rt = self.theclass.fromisoformat(dtstr)
2889 self.assertEqual(dt, dt_rt)
2890
2891 def test_fromisoformat_ambiguous(self):
2892 # Test strings like 2018-01-31+12:15 (where +12:15 is not a time zone)
2893 separators = ['+', '-']
2894 for sep in separators:
2895 dt = self.theclass(2018, 1, 31, 12, 15)
2896 dtstr = dt.isoformat(sep=sep)
2897
2898 with self.subTest(dtstr=dtstr):
2899 dt_rt = self.theclass.fromisoformat(dtstr)
2900 self.assertEqual(dt, dt_rt)
2901
2902 def test_fromisoformat_timespecs(self):
2903 datetime_bases = [
2904 (2009, 12, 4, 8, 17, 45, 123456),
2905 (2009, 12, 4, 8, 17, 45, 0)]
2906
2907 tzinfos = [None, timezone.utc,
2908 timezone(timedelta(hours=-5)),
2909 timezone(timedelta(hours=2)),
2910 timezone(timedelta(hours=6, minutes=27))]
2911
2912 timespecs = ['hours', 'minutes', 'seconds',
2913 'milliseconds', 'microseconds']
2914
2915 for ip, ts in enumerate(timespecs):
2916 for tzi in tzinfos:
2917 for dt_tuple in datetime_bases:
2918 if ts == 'milliseconds':
2919 new_microseconds = 1000 * (dt_tuple[6] // 1000)
2920 dt_tuple = dt_tuple[0:6] + (new_microseconds,)
2921
2922 dt = self.theclass(*(dt_tuple[0:(4 + ip)]), tzinfo=tzi)
2923 dtstr = dt.isoformat(timespec=ts)
2924 with self.subTest(dtstr=dtstr):
2925 dt_rt = self.theclass.fromisoformat(dtstr)
2926 self.assertEqual(dt, dt_rt)
2927
2928 def test_fromisoformat_fails_datetime(self):
2929 # Test that fromisoformat() fails on invalid values
2930 bad_strs = [
2931 '', # Empty string
Paul Ganssle096329f2018-08-23 11:06:20 -04002932 '\ud800', # bpo-34454: Surrogate code point
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002933 '2009.04-19T03', # Wrong first separator
2934 '2009-04.19T03', # Wrong second separator
2935 '2009-04-19T0a', # Invalid hours
2936 '2009-04-19T03:1a:45', # Invalid minutes
2937 '2009-04-19T03:15:4a', # Invalid seconds
2938 '2009-04-19T03;15:45', # Bad first time separator
2939 '2009-04-19T03:15;45', # Bad second time separator
2940 '2009-04-19T03:15:4500:00', # Bad time zone separator
2941 '2009-04-19T03:15:45.2345', # Too many digits for milliseconds
2942 '2009-04-19T03:15:45.1234567', # Too many digits for microseconds
2943 '2009-04-19T03:15:45.123456+24:30', # Invalid time zone offset
2944 '2009-04-19T03:15:45.123456-24:30', # Invalid negative offset
2945 '2009-04-10ᛇᛇᛇᛇᛇ12:15', # Too many unicode separators
Paul Ganssle096329f2018-08-23 11:06:20 -04002946 '2009-04\ud80010T12:15', # Surrogate char in date
2947 '2009-04-10T12\ud80015', # Surrogate char in time
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002948 '2009-04-19T1', # Incomplete hours
2949 '2009-04-19T12:3', # Incomplete minutes
2950 '2009-04-19T12:30:4', # Incomplete seconds
2951 '2009-04-19T12:', # Ends with time separator
2952 '2009-04-19T12:30:', # Ends with time separator
2953 '2009-04-19T12:30:45.', # Ends with time separator
2954 '2009-04-19T12:30:45.123456+', # Ends with timzone separator
2955 '2009-04-19T12:30:45.123456-', # Ends with timzone separator
2956 '2009-04-19T12:30:45.123456-05:00a', # Extra text
2957 '2009-04-19T12:30:45.123-05:00a', # Extra text
2958 '2009-04-19T12:30:45-05:00a', # Extra text
2959 ]
2960
2961 for bad_str in bad_strs:
2962 with self.subTest(bad_str=bad_str):
2963 with self.assertRaises(ValueError):
2964 self.theclass.fromisoformat(bad_str)
2965
Paul Ganssle3df85402018-10-22 12:32:52 -04002966 def test_fromisoformat_fails_surrogate(self):
2967 # Test that when fromisoformat() fails with a surrogate character as
2968 # the separator, the error message contains the original string
2969 dtstr = "2018-01-03\ud80001:0113"
2970
2971 with self.assertRaisesRegex(ValueError, re.escape(repr(dtstr))):
2972 self.theclass.fromisoformat(dtstr)
2973
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002974 def test_fromisoformat_utc(self):
2975 dt_str = '2014-04-19T13:21:13+00:00'
2976 dt = self.theclass.fromisoformat(dt_str)
2977
2978 self.assertIs(dt.tzinfo, timezone.utc)
2979
2980 def test_fromisoformat_subclass(self):
2981 class DateTimeSubclass(self.theclass):
2982 pass
2983
2984 dt = DateTimeSubclass(2014, 12, 14, 9, 30, 45, 457390,
2985 tzinfo=timezone(timedelta(hours=10, minutes=45)))
2986
2987 dt_rt = DateTimeSubclass.fromisoformat(dt.isoformat())
2988
2989 self.assertEqual(dt, dt_rt)
2990 self.assertIsInstance(dt_rt, DateTimeSubclass)
2991
2992
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002993class TestSubclassDateTime(TestDateTime):
2994 theclass = SubclassDatetime
2995 # Override tests not designed for subclass
Zachary Ware9fe6d862013-12-08 00:20:35 -06002996 @unittest.skip('not appropriate for subclasses')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002997 def test_roundtrip(self):
2998 pass
2999
3000class SubclassTime(time):
3001 sub_var = 1
3002
3003class TestTime(HarmlessMixedComparison, unittest.TestCase):
3004
3005 theclass = time
3006
3007 def test_basic_attributes(self):
3008 t = self.theclass(12, 0)
3009 self.assertEqual(t.hour, 12)
3010 self.assertEqual(t.minute, 0)
3011 self.assertEqual(t.second, 0)
3012 self.assertEqual(t.microsecond, 0)
3013
3014 def test_basic_attributes_nonzero(self):
3015 # Make sure all attributes are non-zero so bugs in
3016 # bit-shifting access show up.
3017 t = self.theclass(12, 59, 59, 8000)
3018 self.assertEqual(t.hour, 12)
3019 self.assertEqual(t.minute, 59)
3020 self.assertEqual(t.second, 59)
3021 self.assertEqual(t.microsecond, 8000)
3022
3023 def test_roundtrip(self):
3024 t = self.theclass(1, 2, 3, 4)
3025
3026 # Verify t -> string -> time identity.
3027 s = repr(t)
3028 self.assertTrue(s.startswith('datetime.'))
3029 s = s[9:]
3030 t2 = eval(s)
3031 self.assertEqual(t, t2)
3032
3033 # Verify identity via reconstructing from pieces.
3034 t2 = self.theclass(t.hour, t.minute, t.second,
3035 t.microsecond)
3036 self.assertEqual(t, t2)
3037
3038 def test_comparing(self):
3039 args = [1, 2, 3, 4]
3040 t1 = self.theclass(*args)
3041 t2 = self.theclass(*args)
3042 self.assertEqual(t1, t2)
3043 self.assertTrue(t1 <= t2)
3044 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003045 self.assertFalse(t1 != t2)
3046 self.assertFalse(t1 < t2)
3047 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003048
3049 for i in range(len(args)):
3050 newargs = args[:]
3051 newargs[i] = args[i] + 1
3052 t2 = self.theclass(*newargs) # this is larger than t1
3053 self.assertTrue(t1 < t2)
3054 self.assertTrue(t2 > t1)
3055 self.assertTrue(t1 <= t2)
3056 self.assertTrue(t2 >= t1)
3057 self.assertTrue(t1 != t2)
3058 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003059 self.assertFalse(t1 == t2)
3060 self.assertFalse(t2 == t1)
3061 self.assertFalse(t1 > t2)
3062 self.assertFalse(t2 < t1)
3063 self.assertFalse(t1 >= t2)
3064 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003065
3066 for badarg in OTHERSTUFF:
3067 self.assertEqual(t1 == badarg, False)
3068 self.assertEqual(t1 != badarg, True)
3069 self.assertEqual(badarg == t1, False)
3070 self.assertEqual(badarg != t1, True)
3071
3072 self.assertRaises(TypeError, lambda: t1 <= badarg)
3073 self.assertRaises(TypeError, lambda: t1 < badarg)
3074 self.assertRaises(TypeError, lambda: t1 > badarg)
3075 self.assertRaises(TypeError, lambda: t1 >= badarg)
3076 self.assertRaises(TypeError, lambda: badarg <= t1)
3077 self.assertRaises(TypeError, lambda: badarg < t1)
3078 self.assertRaises(TypeError, lambda: badarg > t1)
3079 self.assertRaises(TypeError, lambda: badarg >= t1)
3080
3081 def test_bad_constructor_arguments(self):
3082 # bad hours
3083 self.theclass(0, 0) # no exception
3084 self.theclass(23, 0) # no exception
3085 self.assertRaises(ValueError, self.theclass, -1, 0)
3086 self.assertRaises(ValueError, self.theclass, 24, 0)
3087 # bad minutes
3088 self.theclass(23, 0) # no exception
3089 self.theclass(23, 59) # no exception
3090 self.assertRaises(ValueError, self.theclass, 23, -1)
3091 self.assertRaises(ValueError, self.theclass, 23, 60)
3092 # bad seconds
3093 self.theclass(23, 59, 0) # no exception
3094 self.theclass(23, 59, 59) # no exception
3095 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
3096 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
3097 # bad microseconds
3098 self.theclass(23, 59, 59, 0) # no exception
3099 self.theclass(23, 59, 59, 999999) # no exception
3100 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
3101 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
3102
3103 def test_hash_equality(self):
3104 d = self.theclass(23, 30, 17)
3105 e = self.theclass(23, 30, 17)
3106 self.assertEqual(d, e)
3107 self.assertEqual(hash(d), hash(e))
3108
3109 dic = {d: 1}
3110 dic[e] = 2
3111 self.assertEqual(len(dic), 1)
3112 self.assertEqual(dic[d], 2)
3113 self.assertEqual(dic[e], 2)
3114
3115 d = self.theclass(0, 5, 17)
3116 e = self.theclass(0, 5, 17)
3117 self.assertEqual(d, e)
3118 self.assertEqual(hash(d), hash(e))
3119
3120 dic = {d: 1}
3121 dic[e] = 2
3122 self.assertEqual(len(dic), 1)
3123 self.assertEqual(dic[d], 2)
3124 self.assertEqual(dic[e], 2)
3125
3126 def test_isoformat(self):
3127 t = self.theclass(4, 5, 1, 123)
3128 self.assertEqual(t.isoformat(), "04:05:01.000123")
3129 self.assertEqual(t.isoformat(), str(t))
3130
3131 t = self.theclass()
3132 self.assertEqual(t.isoformat(), "00:00:00")
3133 self.assertEqual(t.isoformat(), str(t))
3134
3135 t = self.theclass(microsecond=1)
3136 self.assertEqual(t.isoformat(), "00:00:00.000001")
3137 self.assertEqual(t.isoformat(), str(t))
3138
3139 t = self.theclass(microsecond=10)
3140 self.assertEqual(t.isoformat(), "00:00:00.000010")
3141 self.assertEqual(t.isoformat(), str(t))
3142
3143 t = self.theclass(microsecond=100)
3144 self.assertEqual(t.isoformat(), "00:00:00.000100")
3145 self.assertEqual(t.isoformat(), str(t))
3146
3147 t = self.theclass(microsecond=1000)
3148 self.assertEqual(t.isoformat(), "00:00:00.001000")
3149 self.assertEqual(t.isoformat(), str(t))
3150
3151 t = self.theclass(microsecond=10000)
3152 self.assertEqual(t.isoformat(), "00:00:00.010000")
3153 self.assertEqual(t.isoformat(), str(t))
3154
3155 t = self.theclass(microsecond=100000)
3156 self.assertEqual(t.isoformat(), "00:00:00.100000")
3157 self.assertEqual(t.isoformat(), str(t))
3158
Alexander Belopolskya2998a62016-03-06 14:58:43 -05003159 t = self.theclass(hour=12, minute=34, second=56, microsecond=123456)
3160 self.assertEqual(t.isoformat(timespec='hours'), "12")
3161 self.assertEqual(t.isoformat(timespec='minutes'), "12:34")
3162 self.assertEqual(t.isoformat(timespec='seconds'), "12:34:56")
3163 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.123")
3164 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.123456")
3165 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56.123456")
3166 self.assertRaises(ValueError, t.isoformat, timespec='monkey')
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03003167 # bpo-34482: Check that surrogates are handled properly.
3168 self.assertRaises(ValueError, t.isoformat, timespec='\ud800')
Alexander Belopolskya2998a62016-03-06 14:58:43 -05003169
3170 t = self.theclass(hour=12, minute=34, second=56, microsecond=999500)
3171 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.999")
3172
3173 t = self.theclass(hour=12, minute=34, second=56, microsecond=0)
3174 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.000")
3175 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.000000")
3176 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56")
3177
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003178 def test_isoformat_timezone(self):
3179 tzoffsets = [
3180 ('05:00', timedelta(hours=5)),
3181 ('02:00', timedelta(hours=2)),
3182 ('06:27', timedelta(hours=6, minutes=27)),
3183 ('12:32:30', timedelta(hours=12, minutes=32, seconds=30)),
3184 ('02:04:09.123456', timedelta(hours=2, minutes=4, seconds=9, microseconds=123456))
3185 ]
3186
3187 tzinfos = [
3188 ('', None),
3189 ('+00:00', timezone.utc),
3190 ('+00:00', timezone(timedelta(0))),
3191 ]
3192
3193 tzinfos += [
3194 (prefix + expected, timezone(sign * td))
3195 for expected, td in tzoffsets
3196 for prefix, sign in [('-', -1), ('+', 1)]
3197 ]
3198
3199 t_base = self.theclass(12, 37, 9)
3200 exp_base = '12:37:09'
3201
3202 for exp_tz, tzi in tzinfos:
3203 t = t_base.replace(tzinfo=tzi)
3204 exp = exp_base + exp_tz
3205 with self.subTest(tzi=tzi):
3206 assert t.isoformat() == exp
3207
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003208 def test_1653736(self):
3209 # verify it doesn't accept extra keyword arguments
3210 t = self.theclass(second=1)
3211 self.assertRaises(TypeError, t.isoformat, foo=3)
3212
3213 def test_strftime(self):
3214 t = self.theclass(1, 2, 3, 4)
3215 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
3216 # A naive object replaces %z and %Z with empty strings.
3217 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
3218
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03003219 # bpo-34482: Check that surrogates don't cause a crash.
3220 try:
3221 t.strftime('%H\ud800%M')
3222 except UnicodeEncodeError:
3223 pass
3224
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003225 def test_format(self):
3226 t = self.theclass(1, 2, 3, 4)
3227 self.assertEqual(t.__format__(''), str(t))
3228
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02003229 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04003230 t.__format__(123)
3231
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003232 # check that a derived class's __str__() gets called
3233 class A(self.theclass):
3234 def __str__(self):
3235 return 'A'
3236 a = A(1, 2, 3, 4)
3237 self.assertEqual(a.__format__(''), 'A')
3238
3239 # check that a derived class's strftime gets called
3240 class B(self.theclass):
3241 def strftime(self, format_spec):
3242 return 'B'
3243 b = B(1, 2, 3, 4)
3244 self.assertEqual(b.__format__(''), str(t))
3245
3246 for fmt in ['%H %M %S',
3247 ]:
3248 self.assertEqual(t.__format__(fmt), t.strftime(fmt))
3249 self.assertEqual(a.__format__(fmt), t.strftime(fmt))
3250 self.assertEqual(b.__format__(fmt), 'B')
3251
3252 def test_str(self):
3253 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
3254 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
3255 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
3256 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
3257 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
3258
3259 def test_repr(self):
3260 name = 'datetime.' + self.theclass.__name__
3261 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
3262 "%s(1, 2, 3, 4)" % name)
3263 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
3264 "%s(10, 2, 3, 4000)" % name)
3265 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
3266 "%s(0, 2, 3, 400000)" % name)
3267 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
3268 "%s(12, 2, 3)" % name)
3269 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
3270 "%s(23, 15)" % name)
3271
3272 def test_resolution_info(self):
3273 self.assertIsInstance(self.theclass.min, self.theclass)
3274 self.assertIsInstance(self.theclass.max, self.theclass)
3275 self.assertIsInstance(self.theclass.resolution, timedelta)
3276 self.assertTrue(self.theclass.max > self.theclass.min)
3277
3278 def test_pickling(self):
3279 args = 20, 59, 16, 64**2
3280 orig = self.theclass(*args)
3281 for pickler, unpickler, proto in pickle_choices:
3282 green = pickler.dumps(orig, proto)
3283 derived = unpickler.loads(green)
3284 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003285 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003286
3287 def test_pickling_subclass_time(self):
3288 args = 20, 59, 16, 64**2
3289 orig = SubclassTime(*args)
3290 for pickler, unpickler, proto in pickle_choices:
3291 green = pickler.dumps(orig, proto)
3292 derived = unpickler.loads(green)
3293 self.assertEqual(orig, derived)
3294
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02003295 def test_compat_unpickle(self):
3296 tests = [
3297 b"cdatetime\ntime\n(S'\\x14;\\x10\\x00\\x10\\x00'\ntR.",
3298 b'cdatetime\ntime\n(U\x06\x14;\x10\x00\x10\x00tR.',
3299 b'\x80\x02cdatetime\ntime\nU\x06\x14;\x10\x00\x10\x00\x85R.',
3300 ]
3301 args = 20, 59, 16, 64**2
3302 expected = self.theclass(*args)
3303 for data in tests:
3304 for loads in pickle_loads:
3305 derived = loads(data, encoding='latin1')
3306 self.assertEqual(derived, expected)
3307
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003308 def test_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003309 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003310 cls = self.theclass
3311 self.assertTrue(cls(1))
3312 self.assertTrue(cls(0, 1))
3313 self.assertTrue(cls(0, 0, 1))
3314 self.assertTrue(cls(0, 0, 0, 1))
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003315 self.assertTrue(cls(0))
3316 self.assertTrue(cls())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003317
3318 def test_replace(self):
3319 cls = self.theclass
3320 args = [1, 2, 3, 4]
3321 base = cls(*args)
3322 self.assertEqual(base, base.replace())
3323
3324 i = 0
3325 for name, newval in (("hour", 5),
3326 ("minute", 6),
3327 ("second", 7),
3328 ("microsecond", 8)):
3329 newargs = args[:]
3330 newargs[i] = newval
3331 expected = cls(*newargs)
3332 got = base.replace(**{name: newval})
3333 self.assertEqual(expected, got)
3334 i += 1
3335
3336 # Out of bounds.
3337 base = cls(1)
3338 self.assertRaises(ValueError, base.replace, hour=24)
3339 self.assertRaises(ValueError, base.replace, minute=-1)
3340 self.assertRaises(ValueError, base.replace, second=100)
3341 self.assertRaises(ValueError, base.replace, microsecond=1000000)
3342
Paul Ganssle191e9932017-11-09 16:34:29 -05003343 def test_subclass_replace(self):
3344 class TimeSubclass(self.theclass):
3345 pass
3346
3347 ctime = TimeSubclass(12, 30)
3348 self.assertIs(type(ctime.replace(hour=10)), TimeSubclass)
3349
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003350 def test_subclass_time(self):
3351
3352 class C(self.theclass):
3353 theAnswer = 42
3354
3355 def __new__(cls, *args, **kws):
3356 temp = kws.copy()
3357 extra = temp.pop('extra')
3358 result = self.theclass.__new__(cls, *args, **temp)
3359 result.extra = extra
3360 return result
3361
3362 def newmeth(self, start):
3363 return start + self.hour + self.second
3364
3365 args = 4, 5, 6
3366
3367 dt1 = self.theclass(*args)
3368 dt2 = C(*args, **{'extra': 7})
3369
3370 self.assertEqual(dt2.__class__, C)
3371 self.assertEqual(dt2.theAnswer, 42)
3372 self.assertEqual(dt2.extra, 7)
3373 self.assertEqual(dt1.isoformat(), dt2.isoformat())
3374 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
3375
3376 def test_backdoor_resistance(self):
3377 # see TestDate.test_backdoor_resistance().
3378 base = '2:59.0'
3379 for hour_byte in ' ', '9', chr(24), '\xff':
3380 self.assertRaises(TypeError, self.theclass,
3381 hour_byte + base[1:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04003382 # Good bytes, but bad tzinfo:
3383 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
3384 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003385
3386# A mixin for classes with a tzinfo= argument. Subclasses must define
Martin Panter46f50722016-05-26 05:35:26 +00003387# theclass as a class attribute, and theclass(1, 1, 1, tzinfo=whatever)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003388# must be legit (which is true for time and datetime).
3389class TZInfoBase:
3390
3391 def test_argument_passing(self):
3392 cls = self.theclass
3393 # A datetime passes itself on, a time passes None.
3394 class introspective(tzinfo):
3395 def tzname(self, dt): return dt and "real" or "none"
3396 def utcoffset(self, dt):
3397 return timedelta(minutes = dt and 42 or -42)
3398 dst = utcoffset
3399
3400 obj = cls(1, 2, 3, tzinfo=introspective())
3401
3402 expected = cls is time and "none" or "real"
3403 self.assertEqual(obj.tzname(), expected)
3404
3405 expected = timedelta(minutes=(cls is time and -42 or 42))
3406 self.assertEqual(obj.utcoffset(), expected)
3407 self.assertEqual(obj.dst(), expected)
3408
3409 def test_bad_tzinfo_classes(self):
3410 cls = self.theclass
3411 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
3412
3413 class NiceTry(object):
3414 def __init__(self): pass
3415 def utcoffset(self, dt): pass
3416 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
3417
3418 class BetterTry(tzinfo):
3419 def __init__(self): pass
3420 def utcoffset(self, dt): pass
3421 b = BetterTry()
3422 t = cls(1, 1, 1, tzinfo=b)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003423 self.assertIs(t.tzinfo, b)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003424
3425 def test_utc_offset_out_of_bounds(self):
3426 class Edgy(tzinfo):
3427 def __init__(self, offset):
3428 self.offset = timedelta(minutes=offset)
3429 def utcoffset(self, dt):
3430 return self.offset
3431
3432 cls = self.theclass
3433 for offset, legit in ((-1440, False),
3434 (-1439, True),
3435 (1439, True),
3436 (1440, False)):
3437 if cls is time:
3438 t = cls(1, 2, 3, tzinfo=Edgy(offset))
3439 elif cls is datetime:
3440 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
3441 else:
3442 assert 0, "impossible"
3443 if legit:
3444 aofs = abs(offset)
3445 h, m = divmod(aofs, 60)
3446 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
3447 if isinstance(t, datetime):
3448 t = t.timetz()
3449 self.assertEqual(str(t), "01:02:03" + tag)
3450 else:
3451 self.assertRaises(ValueError, str, t)
3452
3453 def test_tzinfo_classes(self):
3454 cls = self.theclass
3455 class C1(tzinfo):
3456 def utcoffset(self, dt): return None
3457 def dst(self, dt): return None
3458 def tzname(self, dt): return None
3459 for t in (cls(1, 1, 1),
3460 cls(1, 1, 1, tzinfo=None),
3461 cls(1, 1, 1, tzinfo=C1())):
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003462 self.assertIsNone(t.utcoffset())
3463 self.assertIsNone(t.dst())
3464 self.assertIsNone(t.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003465
3466 class C3(tzinfo):
3467 def utcoffset(self, dt): return timedelta(minutes=-1439)
3468 def dst(self, dt): return timedelta(minutes=1439)
3469 def tzname(self, dt): return "aname"
3470 t = cls(1, 1, 1, tzinfo=C3())
3471 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
3472 self.assertEqual(t.dst(), timedelta(minutes=1439))
3473 self.assertEqual(t.tzname(), "aname")
3474
3475 # Wrong types.
3476 class C4(tzinfo):
3477 def utcoffset(self, dt): return "aname"
3478 def dst(self, dt): return 7
3479 def tzname(self, dt): return 0
3480 t = cls(1, 1, 1, tzinfo=C4())
3481 self.assertRaises(TypeError, t.utcoffset)
3482 self.assertRaises(TypeError, t.dst)
3483 self.assertRaises(TypeError, t.tzname)
3484
3485 # Offset out of range.
3486 class C6(tzinfo):
3487 def utcoffset(self, dt): return timedelta(hours=-24)
3488 def dst(self, dt): return timedelta(hours=24)
3489 t = cls(1, 1, 1, tzinfo=C6())
3490 self.assertRaises(ValueError, t.utcoffset)
3491 self.assertRaises(ValueError, t.dst)
3492
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04003493 # Not a whole number of seconds.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003494 class C7(tzinfo):
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04003495 def utcoffset(self, dt): return timedelta(microseconds=61)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003496 def dst(self, dt): return timedelta(microseconds=-81)
3497 t = cls(1, 1, 1, tzinfo=C7())
Alexander Belopolsky018d3532017-07-31 10:26:50 -04003498 self.assertEqual(t.utcoffset(), timedelta(microseconds=61))
3499 self.assertEqual(t.dst(), timedelta(microseconds=-81))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003500
3501 def test_aware_compare(self):
3502 cls = self.theclass
3503
3504 # Ensure that utcoffset() gets ignored if the comparands have
3505 # the same tzinfo member.
3506 class OperandDependentOffset(tzinfo):
3507 def utcoffset(self, t):
3508 if t.minute < 10:
3509 # d0 and d1 equal after adjustment
3510 return timedelta(minutes=t.minute)
3511 else:
3512 # d2 off in the weeds
3513 return timedelta(minutes=59)
3514
3515 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
3516 d0 = base.replace(minute=3)
3517 d1 = base.replace(minute=9)
3518 d2 = base.replace(minute=11)
3519 for x in d0, d1, d2:
3520 for y in d0, d1, d2:
3521 for op in lt, le, gt, ge, eq, ne:
3522 got = op(x, y)
3523 expected = op(x.minute, y.minute)
3524 self.assertEqual(got, expected)
3525
3526 # However, if they're different members, uctoffset is not ignored.
penguindustin96466302019-05-06 14:57:17 -04003527 # Note that a time can't actually have an operand-dependent offset,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003528 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
3529 # so skip this test for time.
3530 if cls is not time:
3531 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
3532 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
3533 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
3534 for x in d0, d1, d2:
3535 for y in d0, d1, d2:
3536 got = (x > y) - (x < y)
3537 if (x is d0 or x is d1) and (y is d0 or y is d1):
3538 expected = 0
3539 elif x is y is d2:
3540 expected = 0
3541 elif x is d2:
3542 expected = -1
3543 else:
3544 assert y is d2
3545 expected = 1
3546 self.assertEqual(got, expected)
3547
3548
3549# Testing time objects with a non-None tzinfo.
3550class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
3551 theclass = time
3552
3553 def test_empty(self):
3554 t = self.theclass()
3555 self.assertEqual(t.hour, 0)
3556 self.assertEqual(t.minute, 0)
3557 self.assertEqual(t.second, 0)
3558 self.assertEqual(t.microsecond, 0)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003559 self.assertIsNone(t.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003560
3561 def test_zones(self):
3562 est = FixedOffset(-300, "EST", 1)
3563 utc = FixedOffset(0, "UTC", -2)
3564 met = FixedOffset(60, "MET", 3)
3565 t1 = time( 7, 47, tzinfo=est)
3566 t2 = time(12, 47, tzinfo=utc)
3567 t3 = time(13, 47, tzinfo=met)
3568 t4 = time(microsecond=40)
3569 t5 = time(microsecond=40, tzinfo=utc)
3570
3571 self.assertEqual(t1.tzinfo, est)
3572 self.assertEqual(t2.tzinfo, utc)
3573 self.assertEqual(t3.tzinfo, met)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003574 self.assertIsNone(t4.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003575 self.assertEqual(t5.tzinfo, utc)
3576
3577 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
3578 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
3579 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003580 self.assertIsNone(t4.utcoffset())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003581 self.assertRaises(TypeError, t1.utcoffset, "no args")
3582
3583 self.assertEqual(t1.tzname(), "EST")
3584 self.assertEqual(t2.tzname(), "UTC")
3585 self.assertEqual(t3.tzname(), "MET")
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003586 self.assertIsNone(t4.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003587 self.assertRaises(TypeError, t1.tzname, "no args")
3588
3589 self.assertEqual(t1.dst(), timedelta(minutes=1))
3590 self.assertEqual(t2.dst(), timedelta(minutes=-2))
3591 self.assertEqual(t3.dst(), timedelta(minutes=3))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003592 self.assertIsNone(t4.dst())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003593 self.assertRaises(TypeError, t1.dst, "no args")
3594
3595 self.assertEqual(hash(t1), hash(t2))
3596 self.assertEqual(hash(t1), hash(t3))
3597 self.assertEqual(hash(t2), hash(t3))
3598
3599 self.assertEqual(t1, t2)
3600 self.assertEqual(t1, t3)
3601 self.assertEqual(t2, t3)
Alexander Belopolsky08313822012-06-15 20:19:47 -04003602 self.assertNotEqual(t4, t5) # mixed tz-aware & naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003603 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
3604 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
3605
3606 self.assertEqual(str(t1), "07:47:00-05:00")
3607 self.assertEqual(str(t2), "12:47:00+00:00")
3608 self.assertEqual(str(t3), "13:47:00+01:00")
3609 self.assertEqual(str(t4), "00:00:00.000040")
3610 self.assertEqual(str(t5), "00:00:00.000040+00:00")
3611
3612 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
3613 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
3614 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
3615 self.assertEqual(t4.isoformat(), "00:00:00.000040")
3616 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
3617
3618 d = 'datetime.time'
3619 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
3620 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
3621 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
3622 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
3623 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
3624
3625 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
3626 "07:47:00 %Z=EST %z=-0500")
3627 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
3628 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
3629
3630 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
3631 t1 = time(23, 59, tzinfo=yuck)
3632 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
3633 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
3634
3635 # Check that an invalid tzname result raises an exception.
3636 class Badtzname(tzinfo):
Alexander Belopolskye239d232010-12-08 23:31:48 +00003637 tz = 42
3638 def tzname(self, dt): return self.tz
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003639 t = time(2, 3, 4, tzinfo=Badtzname())
3640 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
3641 self.assertRaises(TypeError, t.strftime, "%Z")
3642
Alexander Belopolskye239d232010-12-08 23:31:48 +00003643 # Issue #6697:
Utkarsh Upadhyay287c5592017-07-21 02:14:54 +02003644 if '_Fast' in self.__class__.__name__:
Alexander Belopolskye239d232010-12-08 23:31:48 +00003645 Badtzname.tz = '\ud800'
3646 self.assertRaises(ValueError, t.strftime, "%Z")
3647
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003648 def test_hash_edge_cases(self):
3649 # Offsets that overflow a basic time.
3650 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
3651 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
3652 self.assertEqual(hash(t1), hash(t2))
3653
3654 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
3655 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
3656 self.assertEqual(hash(t1), hash(t2))
3657
3658 def test_pickling(self):
3659 # Try one without a tzinfo.
3660 args = 20, 59, 16, 64**2
3661 orig = self.theclass(*args)
3662 for pickler, unpickler, proto in pickle_choices:
3663 green = pickler.dumps(orig, proto)
3664 derived = unpickler.loads(green)
3665 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003666 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003667
3668 # Try one with a tzinfo.
3669 tinfo = PicklableFixedOffset(-300, 'cookie')
3670 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
3671 for pickler, unpickler, proto in pickle_choices:
3672 green = pickler.dumps(orig, proto)
3673 derived = unpickler.loads(green)
3674 self.assertEqual(orig, derived)
3675 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3676 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3677 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003678 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003679
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02003680 def test_compat_unpickle(self):
3681 tests = [
3682 b"cdatetime\ntime\n(S'\\x05\\x06\\x07\\x01\\xe2@'\n"
3683 b"ctest.datetimetester\nPicklableFixedOffset\n(tR"
3684 b"(dS'_FixedOffset__offset'\ncdatetime\ntimedelta\n"
3685 b"(I-1\nI68400\nI0\ntRs"
3686 b"S'_FixedOffset__dstoffset'\nNs"
3687 b"S'_FixedOffset__name'\nS'cookie'\nsbtR.",
3688
3689 b'cdatetime\ntime\n(U\x06\x05\x06\x07\x01\xe2@'
3690 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
3691 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
3692 b'(J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00tR'
3693 b'U\x17_FixedOffset__dstoffsetN'
3694 b'U\x12_FixedOffset__nameU\x06cookieubtR.',
3695
3696 b'\x80\x02cdatetime\ntime\nU\x06\x05\x06\x07\x01\xe2@'
3697 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
3698 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
3699 b'J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00\x87R'
3700 b'U\x17_FixedOffset__dstoffsetN'
3701 b'U\x12_FixedOffset__nameU\x06cookieub\x86R.',
3702 ]
3703
3704 tinfo = PicklableFixedOffset(-300, 'cookie')
3705 expected = self.theclass(5, 6, 7, 123456, tzinfo=tinfo)
3706 for data in tests:
3707 for loads in pickle_loads:
3708 derived = loads(data, encoding='latin1')
3709 self.assertEqual(derived, expected, repr(data))
3710 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3711 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3712 self.assertEqual(derived.tzname(), 'cookie')
3713
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003714 def test_more_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003715 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003716 cls = self.theclass
3717
3718 t = cls(0, tzinfo=FixedOffset(-300, ""))
3719 self.assertTrue(t)
3720
3721 t = cls(5, tzinfo=FixedOffset(-300, ""))
3722 self.assertTrue(t)
3723
3724 t = cls(5, tzinfo=FixedOffset(300, ""))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003725 self.assertTrue(t)
3726
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003727 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
3728 self.assertTrue(t)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003729
3730 def test_replace(self):
3731 cls = self.theclass
3732 z100 = FixedOffset(100, "+100")
3733 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3734 args = [1, 2, 3, 4, z100]
3735 base = cls(*args)
3736 self.assertEqual(base, base.replace())
3737
3738 i = 0
3739 for name, newval in (("hour", 5),
3740 ("minute", 6),
3741 ("second", 7),
3742 ("microsecond", 8),
3743 ("tzinfo", zm200)):
3744 newargs = args[:]
3745 newargs[i] = newval
3746 expected = cls(*newargs)
3747 got = base.replace(**{name: newval})
3748 self.assertEqual(expected, got)
3749 i += 1
3750
3751 # Ensure we can get rid of a tzinfo.
3752 self.assertEqual(base.tzname(), "+100")
3753 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003754 self.assertIsNone(base2.tzinfo)
3755 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003756
3757 # Ensure we can add one.
3758 base3 = base2.replace(tzinfo=z100)
3759 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003760 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003761
3762 # Out of bounds.
3763 base = cls(1)
3764 self.assertRaises(ValueError, base.replace, hour=24)
3765 self.assertRaises(ValueError, base.replace, minute=-1)
3766 self.assertRaises(ValueError, base.replace, second=100)
3767 self.assertRaises(ValueError, base.replace, microsecond=1000000)
3768
3769 def test_mixed_compare(self):
Serhiy Storchaka17e52642019-08-04 12:38:46 +03003770 t1 = self.theclass(1, 2, 3)
3771 t2 = self.theclass(1, 2, 3)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003772 self.assertEqual(t1, t2)
3773 t2 = t2.replace(tzinfo=None)
3774 self.assertEqual(t1, t2)
3775 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3776 self.assertEqual(t1, t2)
3777 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04003778 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003779
3780 # In time w/ identical tzinfo objects, utcoffset is ignored.
3781 class Varies(tzinfo):
3782 def __init__(self):
3783 self.offset = timedelta(minutes=22)
3784 def utcoffset(self, t):
3785 self.offset += timedelta(minutes=1)
3786 return self.offset
3787
3788 v = Varies()
3789 t1 = t2.replace(tzinfo=v)
3790 t2 = t2.replace(tzinfo=v)
3791 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3792 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3793 self.assertEqual(t1, t2)
3794
3795 # But if they're not identical, it isn't ignored.
3796 t2 = t2.replace(tzinfo=Varies())
3797 self.assertTrue(t1 < t2) # t1's offset counter still going up
3798
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003799 def test_fromisoformat(self):
3800 time_examples = [
3801 (0, 0, 0, 0),
3802 (23, 59, 59, 999999),
3803 ]
3804
3805 hh = (9, 12, 20)
3806 mm = (5, 30)
3807 ss = (4, 45)
3808 usec = (0, 245000, 678901)
3809
3810 time_examples += list(itertools.product(hh, mm, ss, usec))
3811
3812 tzinfos = [None, timezone.utc,
3813 timezone(timedelta(hours=2)),
3814 timezone(timedelta(hours=6, minutes=27))]
3815
3816 for ttup in time_examples:
3817 for tzi in tzinfos:
3818 t = self.theclass(*ttup, tzinfo=tzi)
3819 tstr = t.isoformat()
3820
3821 with self.subTest(tstr=tstr):
3822 t_rt = self.theclass.fromisoformat(tstr)
3823 self.assertEqual(t, t_rt)
3824
3825 def test_fromisoformat_timezone(self):
3826 base_time = self.theclass(12, 30, 45, 217456)
3827
3828 tzoffsets = [
3829 timedelta(hours=5), timedelta(hours=2),
3830 timedelta(hours=6, minutes=27),
3831 timedelta(hours=12, minutes=32, seconds=30),
3832 timedelta(hours=2, minutes=4, seconds=9, microseconds=123456)
3833 ]
3834
3835 tzoffsets += [-1 * td for td in tzoffsets]
3836
3837 tzinfos = [None, timezone.utc,
3838 timezone(timedelta(hours=0))]
3839
3840 tzinfos += [timezone(td) for td in tzoffsets]
3841
3842 for tzi in tzinfos:
3843 t = base_time.replace(tzinfo=tzi)
3844 tstr = t.isoformat()
3845
3846 with self.subTest(tstr=tstr):
3847 t_rt = self.theclass.fromisoformat(tstr)
3848 assert t == t_rt, t_rt
3849
3850 def test_fromisoformat_timespecs(self):
3851 time_bases = [
3852 (8, 17, 45, 123456),
3853 (8, 17, 45, 0)
3854 ]
3855
3856 tzinfos = [None, timezone.utc,
3857 timezone(timedelta(hours=-5)),
3858 timezone(timedelta(hours=2)),
3859 timezone(timedelta(hours=6, minutes=27))]
3860
3861 timespecs = ['hours', 'minutes', 'seconds',
3862 'milliseconds', 'microseconds']
3863
3864 for ip, ts in enumerate(timespecs):
3865 for tzi in tzinfos:
3866 for t_tuple in time_bases:
3867 if ts == 'milliseconds':
3868 new_microseconds = 1000 * (t_tuple[-1] // 1000)
3869 t_tuple = t_tuple[0:-1] + (new_microseconds,)
3870
3871 t = self.theclass(*(t_tuple[0:(1 + ip)]), tzinfo=tzi)
3872 tstr = t.isoformat(timespec=ts)
3873 with self.subTest(tstr=tstr):
3874 t_rt = self.theclass.fromisoformat(tstr)
3875 self.assertEqual(t, t_rt)
3876
3877 def test_fromisoformat_fails(self):
3878 bad_strs = [
3879 '', # Empty string
Paul Ganssle096329f2018-08-23 11:06:20 -04003880 '12\ud80000', # Invalid separator - surrogate char
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003881 '12:', # Ends on a separator
3882 '12:30:', # Ends on a separator
3883 '12:30:15.', # Ends on a separator
3884 '1', # Incomplete hours
3885 '12:3', # Incomplete minutes
3886 '12:30:1', # Incomplete seconds
3887 '1a:30:45.334034', # Invalid character in hours
3888 '12:a0:45.334034', # Invalid character in minutes
3889 '12:30:a5.334034', # Invalid character in seconds
3890 '12:30:45.1234', # Too many digits for milliseconds
3891 '12:30:45.1234567', # Too many digits for microseconds
3892 '12:30:45.123456+24:30', # Invalid time zone offset
3893 '12:30:45.123456-24:30', # Invalid negative offset
3894 '12:30:45', # Uses full-width unicode colons
3895 '12:30:45․123456', # Uses \u2024 in place of decimal point
3896 '12:30:45a', # Extra at tend of basic time
3897 '12:30:45.123a', # Extra at end of millisecond time
3898 '12:30:45.123456a', # Extra at end of microsecond time
3899 '12:30:45.123456+12:00:30a', # Extra at end of full time
3900 ]
3901
3902 for bad_str in bad_strs:
3903 with self.subTest(bad_str=bad_str):
3904 with self.assertRaises(ValueError):
3905 self.theclass.fromisoformat(bad_str)
3906
3907 def test_fromisoformat_fails_typeerror(self):
3908 # Test the fromisoformat fails when passed the wrong type
3909 import io
3910
3911 bad_types = [b'12:30:45', None, io.StringIO('12:30:45')]
3912
3913 for bad_type in bad_types:
3914 with self.assertRaises(TypeError):
3915 self.theclass.fromisoformat(bad_type)
3916
3917 def test_fromisoformat_subclass(self):
3918 class TimeSubclass(self.theclass):
3919 pass
3920
3921 tsc = TimeSubclass(12, 14, 45, 203745, tzinfo=timezone.utc)
3922 tsc_rt = TimeSubclass.fromisoformat(tsc.isoformat())
3923
3924 self.assertEqual(tsc, tsc_rt)
3925 self.assertIsInstance(tsc_rt, TimeSubclass)
3926
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003927 def test_subclass_timetz(self):
3928
3929 class C(self.theclass):
3930 theAnswer = 42
3931
3932 def __new__(cls, *args, **kws):
3933 temp = kws.copy()
3934 extra = temp.pop('extra')
3935 result = self.theclass.__new__(cls, *args, **temp)
3936 result.extra = extra
3937 return result
3938
3939 def newmeth(self, start):
3940 return start + self.hour + self.second
3941
3942 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3943
3944 dt1 = self.theclass(*args)
3945 dt2 = C(*args, **{'extra': 7})
3946
3947 self.assertEqual(dt2.__class__, C)
3948 self.assertEqual(dt2.theAnswer, 42)
3949 self.assertEqual(dt2.extra, 7)
3950 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3951 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
3952
3953
3954# Testing datetime objects with a non-None tzinfo.
3955
3956class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
3957 theclass = datetime
3958
3959 def test_trivial(self):
3960 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
3961 self.assertEqual(dt.year, 1)
3962 self.assertEqual(dt.month, 2)
3963 self.assertEqual(dt.day, 3)
3964 self.assertEqual(dt.hour, 4)
3965 self.assertEqual(dt.minute, 5)
3966 self.assertEqual(dt.second, 6)
3967 self.assertEqual(dt.microsecond, 7)
3968 self.assertEqual(dt.tzinfo, None)
3969
3970 def test_even_more_compare(self):
3971 # The test_compare() and test_more_compare() inherited from TestDate
3972 # and TestDateTime covered non-tzinfo cases.
3973
3974 # Smallest possible after UTC adjustment.
3975 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
3976 # Largest possible after UTC adjustment.
3977 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3978 tzinfo=FixedOffset(-1439, ""))
3979
3980 # Make sure those compare correctly, and w/o overflow.
3981 self.assertTrue(t1 < t2)
3982 self.assertTrue(t1 != t2)
3983 self.assertTrue(t2 > t1)
3984
3985 self.assertEqual(t1, t1)
3986 self.assertEqual(t2, t2)
3987
3988 # Equal afer adjustment.
3989 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
3990 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
3991 self.assertEqual(t1, t2)
3992
3993 # Change t1 not to subtract a minute, and t1 should be larger.
3994 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
3995 self.assertTrue(t1 > t2)
3996
3997 # Change t1 to subtract 2 minutes, and t1 should be smaller.
3998 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
3999 self.assertTrue(t1 < t2)
4000
4001 # Back to the original t1, but make seconds resolve it.
4002 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
4003 second=1)
4004 self.assertTrue(t1 > t2)
4005
4006 # Likewise, but make microseconds resolve it.
4007 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
4008 microsecond=1)
4009 self.assertTrue(t1 > t2)
4010
Alexander Belopolsky08313822012-06-15 20:19:47 -04004011 # Make t2 naive and it should differ.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004012 t2 = self.theclass.min
Alexander Belopolsky08313822012-06-15 20:19:47 -04004013 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004014 self.assertEqual(t2, t2)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04004015 # and > comparison should fail
4016 with self.assertRaises(TypeError):
4017 t1 > t2
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004018
4019 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
4020 class Naive(tzinfo):
4021 def utcoffset(self, dt): return None
4022 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
Alexander Belopolsky08313822012-06-15 20:19:47 -04004023 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004024 self.assertEqual(t2, t2)
4025
4026 # OTOH, it's OK to compare two of these mixing the two ways of being
4027 # naive.
4028 t1 = self.theclass(5, 6, 7)
4029 self.assertEqual(t1, t2)
4030
4031 # Try a bogus uctoffset.
4032 class Bogus(tzinfo):
4033 def utcoffset(self, dt):
4034 return timedelta(minutes=1440) # out of bounds
4035 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
4036 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
4037 self.assertRaises(ValueError, lambda: t1 == t2)
4038
4039 def test_pickling(self):
4040 # Try one without a tzinfo.
4041 args = 6, 7, 23, 20, 59, 1, 64**2
4042 orig = self.theclass(*args)
4043 for pickler, unpickler, proto in pickle_choices:
4044 green = pickler.dumps(orig, proto)
4045 derived = unpickler.loads(green)
4046 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02004047 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004048
4049 # Try one with a tzinfo.
4050 tinfo = PicklableFixedOffset(-300, 'cookie')
4051 orig = self.theclass(*args, **{'tzinfo': tinfo})
4052 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
4053 for pickler, unpickler, proto in pickle_choices:
4054 green = pickler.dumps(orig, proto)
4055 derived = unpickler.loads(green)
4056 self.assertEqual(orig, derived)
4057 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
4058 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
4059 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02004060 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004061
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02004062 def test_compat_unpickle(self):
4063 tests = [
4064 b'cdatetime\ndatetime\n'
4065 b"(S'\\x07\\xdf\\x0b\\x1b\\x14;\\x01\\x01\\xe2@'\n"
4066 b'ctest.datetimetester\nPicklableFixedOffset\n(tR'
4067 b"(dS'_FixedOffset__offset'\ncdatetime\ntimedelta\n"
4068 b'(I-1\nI68400\nI0\ntRs'
4069 b"S'_FixedOffset__dstoffset'\nNs"
4070 b"S'_FixedOffset__name'\nS'cookie'\nsbtR.",
4071
4072 b'cdatetime\ndatetime\n'
4073 b'(U\n\x07\xdf\x0b\x1b\x14;\x01\x01\xe2@'
4074 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
4075 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
4076 b'(J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00tR'
4077 b'U\x17_FixedOffset__dstoffsetN'
4078 b'U\x12_FixedOffset__nameU\x06cookieubtR.',
4079
4080 b'\x80\x02cdatetime\ndatetime\n'
4081 b'U\n\x07\xdf\x0b\x1b\x14;\x01\x01\xe2@'
4082 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
4083 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
4084 b'J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00\x87R'
4085 b'U\x17_FixedOffset__dstoffsetN'
4086 b'U\x12_FixedOffset__nameU\x06cookieub\x86R.',
4087 ]
4088 args = 2015, 11, 27, 20, 59, 1, 123456
4089 tinfo = PicklableFixedOffset(-300, 'cookie')
4090 expected = self.theclass(*args, **{'tzinfo': tinfo})
4091 for data in tests:
4092 for loads in pickle_loads:
4093 derived = loads(data, encoding='latin1')
4094 self.assertEqual(derived, expected)
4095 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
4096 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
4097 self.assertEqual(derived.tzname(), 'cookie')
4098
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004099 def test_extreme_hashes(self):
4100 # If an attempt is made to hash these via subtracting the offset
4101 # then hashing a datetime object, OverflowError results. The
4102 # Python implementation used to blow up here.
4103 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
4104 hash(t)
4105 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
4106 tzinfo=FixedOffset(-1439, ""))
4107 hash(t)
4108
4109 # OTOH, an OOB offset should blow up.
4110 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
4111 self.assertRaises(ValueError, hash, t)
4112
4113 def test_zones(self):
4114 est = FixedOffset(-300, "EST")
4115 utc = FixedOffset(0, "UTC")
4116 met = FixedOffset(60, "MET")
4117 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
4118 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
4119 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
4120 self.assertEqual(t1.tzinfo, est)
4121 self.assertEqual(t2.tzinfo, utc)
4122 self.assertEqual(t3.tzinfo, met)
4123 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
4124 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
4125 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
4126 self.assertEqual(t1.tzname(), "EST")
4127 self.assertEqual(t2.tzname(), "UTC")
4128 self.assertEqual(t3.tzname(), "MET")
4129 self.assertEqual(hash(t1), hash(t2))
4130 self.assertEqual(hash(t1), hash(t3))
4131 self.assertEqual(hash(t2), hash(t3))
4132 self.assertEqual(t1, t2)
4133 self.assertEqual(t1, t3)
4134 self.assertEqual(t2, t3)
4135 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
4136 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
4137 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
4138 d = 'datetime.datetime(2002, 3, 19, '
4139 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
4140 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
4141 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
4142
4143 def test_combine(self):
4144 met = FixedOffset(60, "MET")
4145 d = date(2002, 3, 4)
4146 tz = time(18, 45, 3, 1234, tzinfo=met)
4147 dt = datetime.combine(d, tz)
4148 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
4149 tzinfo=met))
4150
4151 def test_extract(self):
4152 met = FixedOffset(60, "MET")
4153 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
4154 self.assertEqual(dt.date(), date(2002, 3, 4))
4155 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
4156 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
4157
4158 def test_tz_aware_arithmetic(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004159 now = self.theclass.now()
4160 tz55 = FixedOffset(-330, "west 5:30")
4161 timeaware = now.time().replace(tzinfo=tz55)
4162 nowaware = self.theclass.combine(now.date(), timeaware)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004163 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004164 self.assertEqual(nowaware.timetz(), timeaware)
4165
4166 # Can't mix aware and non-aware.
4167 self.assertRaises(TypeError, lambda: now - nowaware)
4168 self.assertRaises(TypeError, lambda: nowaware - now)
4169
4170 # And adding datetime's doesn't make sense, aware or not.
4171 self.assertRaises(TypeError, lambda: now + nowaware)
4172 self.assertRaises(TypeError, lambda: nowaware + now)
4173 self.assertRaises(TypeError, lambda: nowaware + nowaware)
4174
4175 # Subtracting should yield 0.
4176 self.assertEqual(now - now, timedelta(0))
4177 self.assertEqual(nowaware - nowaware, timedelta(0))
4178
4179 # Adding a delta should preserve tzinfo.
4180 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
4181 nowawareplus = nowaware + delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004182 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004183 nowawareplus2 = delta + nowaware
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004184 self.assertIs(nowawareplus2.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004185 self.assertEqual(nowawareplus, nowawareplus2)
4186
4187 # that - delta should be what we started with, and that - what we
4188 # started with should be delta.
4189 diff = nowawareplus - delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004190 self.assertIs(diff.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004191 self.assertEqual(nowaware, diff)
4192 self.assertRaises(TypeError, lambda: delta - nowawareplus)
4193 self.assertEqual(nowawareplus - nowaware, delta)
4194
4195 # Make up a random timezone.
4196 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
4197 # Attach it to nowawareplus.
4198 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004199 self.assertIs(nowawareplus.tzinfo, tzr)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004200 # Make sure the difference takes the timezone adjustments into account.
4201 got = nowaware - nowawareplus
4202 # Expected: (nowaware base - nowaware offset) -
4203 # (nowawareplus base - nowawareplus offset) =
4204 # (nowaware base - nowawareplus base) +
4205 # (nowawareplus offset - nowaware offset) =
4206 # -delta + nowawareplus offset - nowaware offset
4207 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
4208 self.assertEqual(got, expected)
4209
4210 # Try max possible difference.
4211 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
4212 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
4213 tzinfo=FixedOffset(-1439, "max"))
4214 maxdiff = max - min
4215 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
4216 timedelta(minutes=2*1439))
4217 # Different tzinfo, but the same offset
4218 tza = timezone(HOUR, 'A')
4219 tzb = timezone(HOUR, 'B')
4220 delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
4221 self.assertEqual(delta, self.theclass.min - self.theclass.max)
4222
4223 def test_tzinfo_now(self):
4224 meth = self.theclass.now
4225 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4226 base = meth()
4227 # Try with and without naming the keyword.
4228 off42 = FixedOffset(42, "42")
4229 another = meth(off42)
4230 again = meth(tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004231 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004232 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
4233 # Bad argument with and w/o naming the keyword.
4234 self.assertRaises(TypeError, meth, 16)
4235 self.assertRaises(TypeError, meth, tzinfo=16)
4236 # Bad keyword name.
4237 self.assertRaises(TypeError, meth, tinfo=off42)
4238 # Too many args.
4239 self.assertRaises(TypeError, meth, off42, off42)
4240
4241 # We don't know which time zone we're in, and don't have a tzinfo
4242 # class to represent it, so seeing whether a tz argument actually
4243 # does a conversion is tricky.
4244 utc = FixedOffset(0, "utc", 0)
4245 for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
4246 timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
4247 for dummy in range(3):
4248 now = datetime.now(weirdtz)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004249 self.assertIs(now.tzinfo, weirdtz)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004250 utcnow = datetime.utcnow().replace(tzinfo=utc)
4251 now2 = utcnow.astimezone(weirdtz)
4252 if abs(now - now2) < timedelta(seconds=30):
4253 break
4254 # Else the code is broken, or more than 30 seconds passed between
4255 # calls; assuming the latter, just try again.
4256 else:
4257 # Three strikes and we're out.
4258 self.fail("utcnow(), now(tz), or astimezone() may be broken")
4259
4260 def test_tzinfo_fromtimestamp(self):
4261 import time
4262 meth = self.theclass.fromtimestamp
4263 ts = time.time()
4264 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4265 base = meth(ts)
4266 # Try with and without naming the keyword.
4267 off42 = FixedOffset(42, "42")
4268 another = meth(ts, off42)
4269 again = meth(ts, tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004270 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004271 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
4272 # Bad argument with and w/o naming the keyword.
4273 self.assertRaises(TypeError, meth, ts, 16)
4274 self.assertRaises(TypeError, meth, ts, tzinfo=16)
4275 # Bad keyword name.
4276 self.assertRaises(TypeError, meth, ts, tinfo=off42)
4277 # Too many args.
4278 self.assertRaises(TypeError, meth, ts, off42, off42)
4279 # Too few args.
4280 self.assertRaises(TypeError, meth)
4281
4282 # Try to make sure tz= actually does some conversion.
4283 timestamp = 1000000000
4284 utcdatetime = datetime.utcfromtimestamp(timestamp)
4285 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
4286 # But on some flavor of Mac, it's nowhere near that. So we can't have
4287 # any idea here what time that actually is, we can only test that
4288 # relative changes match.
4289 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
4290 tz = FixedOffset(utcoffset, "tz", 0)
4291 expected = utcdatetime + utcoffset
4292 got = datetime.fromtimestamp(timestamp, tz)
4293 self.assertEqual(expected, got.replace(tzinfo=None))
4294
4295 def test_tzinfo_utcnow(self):
4296 meth = self.theclass.utcnow
4297 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4298 base = meth()
4299 # Try with and without naming the keyword; for whatever reason,
4300 # utcnow() doesn't accept a tzinfo argument.
4301 off42 = FixedOffset(42, "42")
4302 self.assertRaises(TypeError, meth, off42)
4303 self.assertRaises(TypeError, meth, tzinfo=off42)
4304
4305 def test_tzinfo_utcfromtimestamp(self):
4306 import time
4307 meth = self.theclass.utcfromtimestamp
4308 ts = time.time()
4309 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4310 base = meth(ts)
4311 # Try with and without naming the keyword; for whatever reason,
4312 # utcfromtimestamp() doesn't accept a tzinfo argument.
4313 off42 = FixedOffset(42, "42")
4314 self.assertRaises(TypeError, meth, ts, off42)
4315 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
4316
4317 def test_tzinfo_timetuple(self):
4318 # TestDateTime tested most of this. datetime adds a twist to the
4319 # DST flag.
4320 class DST(tzinfo):
4321 def __init__(self, dstvalue):
4322 if isinstance(dstvalue, int):
4323 dstvalue = timedelta(minutes=dstvalue)
4324 self.dstvalue = dstvalue
4325 def dst(self, dt):
4326 return self.dstvalue
4327
4328 cls = self.theclass
4329 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
4330 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
4331 t = d.timetuple()
4332 self.assertEqual(1, t.tm_year)
4333 self.assertEqual(1, t.tm_mon)
4334 self.assertEqual(1, t.tm_mday)
4335 self.assertEqual(10, t.tm_hour)
4336 self.assertEqual(20, t.tm_min)
4337 self.assertEqual(30, t.tm_sec)
4338 self.assertEqual(0, t.tm_wday)
4339 self.assertEqual(1, t.tm_yday)
4340 self.assertEqual(flag, t.tm_isdst)
4341
4342 # dst() returns wrong type.
4343 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
4344
4345 # dst() at the edge.
4346 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
4347 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
4348
4349 # dst() out of range.
4350 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
4351 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
4352
4353 def test_utctimetuple(self):
4354 class DST(tzinfo):
4355 def __init__(self, dstvalue=0):
4356 if isinstance(dstvalue, int):
4357 dstvalue = timedelta(minutes=dstvalue)
4358 self.dstvalue = dstvalue
4359 def dst(self, dt):
4360 return self.dstvalue
4361
4362 cls = self.theclass
4363 # This can't work: DST didn't implement utcoffset.
4364 self.assertRaises(NotImplementedError,
4365 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
4366
4367 class UOFS(DST):
4368 def __init__(self, uofs, dofs=None):
4369 DST.__init__(self, dofs)
4370 self.uofs = timedelta(minutes=uofs)
4371 def utcoffset(self, dt):
4372 return self.uofs
4373
4374 for dstvalue in -33, 33, 0, None:
4375 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
4376 t = d.utctimetuple()
4377 self.assertEqual(d.year, t.tm_year)
4378 self.assertEqual(d.month, t.tm_mon)
4379 self.assertEqual(d.day, t.tm_mday)
4380 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
4381 self.assertEqual(13, t.tm_min)
4382 self.assertEqual(d.second, t.tm_sec)
4383 self.assertEqual(d.weekday(), t.tm_wday)
4384 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
4385 t.tm_yday)
4386 # Ensure tm_isdst is 0 regardless of what dst() says: DST
4387 # is never in effect for a UTC time.
4388 self.assertEqual(0, t.tm_isdst)
4389
4390 # For naive datetime, utctimetuple == timetuple except for isdst
4391 d = cls(1, 2, 3, 10, 20, 30, 40)
4392 t = d.utctimetuple()
4393 self.assertEqual(t[:-1], d.timetuple()[:-1])
4394 self.assertEqual(0, t.tm_isdst)
4395 # Same if utcoffset is None
4396 class NOFS(DST):
4397 def utcoffset(self, dt):
4398 return None
4399 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
4400 t = d.utctimetuple()
4401 self.assertEqual(t[:-1], d.timetuple()[:-1])
4402 self.assertEqual(0, t.tm_isdst)
4403 # Check that bad tzinfo is detected
4404 class BOFS(DST):
4405 def utcoffset(self, dt):
4406 return "EST"
4407 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
4408 self.assertRaises(TypeError, d.utctimetuple)
4409
4410 # Check that utctimetuple() is the same as
4411 # astimezone(utc).timetuple()
4412 d = cls(2010, 11, 13, 14, 15, 16, 171819)
4413 for tz in [timezone.min, timezone.utc, timezone.max]:
4414 dtz = d.replace(tzinfo=tz)
4415 self.assertEqual(dtz.utctimetuple()[:-1],
4416 dtz.astimezone(timezone.utc).timetuple()[:-1])
4417 # At the edges, UTC adjustment can produce years out-of-range
4418 # for a datetime object. Ensure that an OverflowError is
4419 # raised.
4420 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
4421 # That goes back 1 minute less than a full day.
4422 self.assertRaises(OverflowError, tiny.utctimetuple)
4423
4424 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
4425 # That goes forward 1 minute less than a full day.
4426 self.assertRaises(OverflowError, huge.utctimetuple)
4427 # More overflow cases
4428 tiny = cls.min.replace(tzinfo=timezone(MINUTE))
4429 self.assertRaises(OverflowError, tiny.utctimetuple)
4430 huge = cls.max.replace(tzinfo=timezone(-MINUTE))
4431 self.assertRaises(OverflowError, huge.utctimetuple)
4432
4433 def test_tzinfo_isoformat(self):
4434 zero = FixedOffset(0, "+00:00")
4435 plus = FixedOffset(220, "+03:40")
4436 minus = FixedOffset(-231, "-03:51")
4437 unknown = FixedOffset(None, "")
4438
4439 cls = self.theclass
4440 datestr = '0001-02-03'
4441 for ofs in None, zero, plus, minus, unknown:
4442 for us in 0, 987001:
4443 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
4444 timestr = '04:05:59' + (us and '.987001' or '')
4445 ofsstr = ofs is not None and d.tzname() or ''
4446 tailstr = timestr + ofsstr
4447 iso = d.isoformat()
4448 self.assertEqual(iso, datestr + 'T' + tailstr)
4449 self.assertEqual(iso, d.isoformat('T'))
4450 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
4451 self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
4452 self.assertEqual(str(d), datestr + ' ' + tailstr)
4453
4454 def test_replace(self):
4455 cls = self.theclass
4456 z100 = FixedOffset(100, "+100")
4457 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
4458 args = [1, 2, 3, 4, 5, 6, 7, z100]
4459 base = cls(*args)
4460 self.assertEqual(base, base.replace())
4461
4462 i = 0
4463 for name, newval in (("year", 2),
4464 ("month", 3),
4465 ("day", 4),
4466 ("hour", 5),
4467 ("minute", 6),
4468 ("second", 7),
4469 ("microsecond", 8),
4470 ("tzinfo", zm200)):
4471 newargs = args[:]
4472 newargs[i] = newval
4473 expected = cls(*newargs)
4474 got = base.replace(**{name: newval})
4475 self.assertEqual(expected, got)
4476 i += 1
4477
4478 # Ensure we can get rid of a tzinfo.
4479 self.assertEqual(base.tzname(), "+100")
4480 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004481 self.assertIsNone(base2.tzinfo)
4482 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004483
4484 # Ensure we can add one.
4485 base3 = base2.replace(tzinfo=z100)
4486 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004487 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004488
4489 # Out of bounds.
4490 base = cls(2000, 2, 29)
4491 self.assertRaises(ValueError, base.replace, year=2001)
4492
4493 def test_more_astimezone(self):
4494 # The inherited test_astimezone covered some trivial and error cases.
4495 fnone = FixedOffset(None, "None")
4496 f44m = FixedOffset(44, "44")
4497 fm5h = FixedOffset(-timedelta(hours=5), "m300")
4498
4499 dt = self.theclass.now(tz=f44m)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004500 self.assertIs(dt.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004501 # Replacing with degenerate tzinfo raises an exception.
4502 self.assertRaises(ValueError, dt.astimezone, fnone)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004503 # Replacing with same tzinfo makes no change.
4504 x = dt.astimezone(dt.tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004505 self.assertIs(x.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004506 self.assertEqual(x.date(), dt.date())
4507 self.assertEqual(x.time(), dt.time())
4508
4509 # Replacing with different tzinfo does adjust.
4510 got = dt.astimezone(fm5h)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004511 self.assertIs(got.tzinfo, fm5h)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004512 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
4513 expected = dt - dt.utcoffset() # in effect, convert to UTC
4514 expected += fm5h.utcoffset(dt) # and from there to local time
4515 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
4516 self.assertEqual(got.date(), expected.date())
4517 self.assertEqual(got.time(), expected.time())
4518 self.assertEqual(got.timetz(), expected.timetz())
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004519 self.assertIs(got.tzinfo, expected.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004520 self.assertEqual(got, expected)
4521
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004522 @support.run_with_tz('UTC')
4523 def test_astimezone_default_utc(self):
4524 dt = self.theclass.now(timezone.utc)
4525 self.assertEqual(dt.astimezone(None), dt)
4526 self.assertEqual(dt.astimezone(), dt)
4527
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004528 # Note that offset in TZ variable has the opposite sign to that
4529 # produced by %z directive.
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004530 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4531 def test_astimezone_default_eastern(self):
4532 dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
4533 local = dt.astimezone()
4534 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004535 self.assertEqual(local.strftime("%z %Z"), "-0500 EST")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004536 dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc)
4537 local = dt.astimezone()
4538 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004539 self.assertEqual(local.strftime("%z %Z"), "-0400 EDT")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004540
Alexander Belopolsky1dcf4f92016-03-25 15:42:59 -04004541 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4542 def test_astimezone_default_near_fold(self):
4543 # Issue #26616.
4544 u = datetime(2015, 11, 1, 5, tzinfo=timezone.utc)
4545 t = u.astimezone()
4546 s = t.astimezone()
4547 self.assertEqual(t.tzinfo, s.tzinfo)
4548
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004549 def test_aware_subtract(self):
4550 cls = self.theclass
4551
4552 # Ensure that utcoffset() is ignored when the operands have the
4553 # same tzinfo member.
4554 class OperandDependentOffset(tzinfo):
4555 def utcoffset(self, t):
4556 if t.minute < 10:
4557 # d0 and d1 equal after adjustment
4558 return timedelta(minutes=t.minute)
4559 else:
4560 # d2 off in the weeds
4561 return timedelta(minutes=59)
4562
4563 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
4564 d0 = base.replace(minute=3)
4565 d1 = base.replace(minute=9)
4566 d2 = base.replace(minute=11)
4567 for x in d0, d1, d2:
4568 for y in d0, d1, d2:
4569 got = x - y
4570 expected = timedelta(minutes=x.minute - y.minute)
4571 self.assertEqual(got, expected)
4572
4573 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
4574 # ignored.
4575 base = cls(8, 9, 10, 11, 12, 13, 14)
4576 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
4577 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
4578 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
4579 for x in d0, d1, d2:
4580 for y in d0, d1, d2:
4581 got = x - y
4582 if (x is d0 or x is d1) and (y is d0 or y is d1):
4583 expected = timedelta(0)
4584 elif x is y is d2:
4585 expected = timedelta(0)
4586 elif x is d2:
4587 expected = timedelta(minutes=(11-59)-0)
4588 else:
4589 assert y is d2
4590 expected = timedelta(minutes=0-(11-59))
4591 self.assertEqual(got, expected)
4592
4593 def test_mixed_compare(self):
4594 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
4595 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
4596 self.assertEqual(t1, t2)
4597 t2 = t2.replace(tzinfo=None)
4598 self.assertEqual(t1, t2)
4599 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
4600 self.assertEqual(t1, t2)
4601 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04004602 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004603
4604 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
4605 class Varies(tzinfo):
4606 def __init__(self):
4607 self.offset = timedelta(minutes=22)
4608 def utcoffset(self, t):
4609 self.offset += timedelta(minutes=1)
4610 return self.offset
4611
4612 v = Varies()
4613 t1 = t2.replace(tzinfo=v)
4614 t2 = t2.replace(tzinfo=v)
4615 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
4616 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
4617 self.assertEqual(t1, t2)
4618
4619 # But if they're not identical, it isn't ignored.
4620 t2 = t2.replace(tzinfo=Varies())
4621 self.assertTrue(t1 < t2) # t1's offset counter still going up
4622
4623 def test_subclass_datetimetz(self):
4624
4625 class C(self.theclass):
4626 theAnswer = 42
4627
4628 def __new__(cls, *args, **kws):
4629 temp = kws.copy()
4630 extra = temp.pop('extra')
4631 result = self.theclass.__new__(cls, *args, **temp)
4632 result.extra = extra
4633 return result
4634
4635 def newmeth(self, start):
4636 return start + self.hour + self.year
4637
4638 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
4639
4640 dt1 = self.theclass(*args)
4641 dt2 = C(*args, **{'extra': 7})
4642
4643 self.assertEqual(dt2.__class__, C)
4644 self.assertEqual(dt2.theAnswer, 42)
4645 self.assertEqual(dt2.extra, 7)
4646 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
4647 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
4648
4649# Pain to set up DST-aware tzinfo classes.
4650
4651def first_sunday_on_or_after(dt):
4652 days_to_go = 6 - dt.weekday()
4653 if days_to_go:
4654 dt += timedelta(days_to_go)
4655 return dt
4656
4657ZERO = timedelta(0)
4658MINUTE = timedelta(minutes=1)
4659HOUR = timedelta(hours=1)
4660DAY = timedelta(days=1)
4661# In the US, DST starts at 2am (standard time) on the first Sunday in April.
4662DSTSTART = datetime(1, 4, 1, 2)
4663# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
4664# which is the first Sunday on or after Oct 25. Because we view 1:MM as
4665# being standard time on that day, there is no spelling in local time of
4666# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
4667DSTEND = datetime(1, 10, 25, 1)
4668
4669class USTimeZone(tzinfo):
4670
4671 def __init__(self, hours, reprname, stdname, dstname):
4672 self.stdoffset = timedelta(hours=hours)
4673 self.reprname = reprname
4674 self.stdname = stdname
4675 self.dstname = dstname
4676
4677 def __repr__(self):
4678 return self.reprname
4679
4680 def tzname(self, dt):
4681 if self.dst(dt):
4682 return self.dstname
4683 else:
4684 return self.stdname
4685
4686 def utcoffset(self, dt):
4687 return self.stdoffset + self.dst(dt)
4688
4689 def dst(self, dt):
4690 if dt is None or dt.tzinfo is None:
4691 # An exception instead may be sensible here, in one or more of
4692 # the cases.
4693 return ZERO
4694 assert dt.tzinfo is self
4695
4696 # Find first Sunday in April.
4697 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
4698 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
4699
4700 # Find last Sunday in October.
4701 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
4702 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
4703
4704 # Can't compare naive to aware objects, so strip the timezone from
4705 # dt first.
4706 if start <= dt.replace(tzinfo=None) < end:
4707 return HOUR
4708 else:
4709 return ZERO
4710
4711Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
4712Central = USTimeZone(-6, "Central", "CST", "CDT")
4713Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
4714Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
4715utc_real = FixedOffset(0, "UTC", 0)
4716# For better test coverage, we want another flavor of UTC that's west of
4717# the Eastern and Pacific timezones.
4718utc_fake = FixedOffset(-12*60, "UTCfake", 0)
4719
4720class TestTimezoneConversions(unittest.TestCase):
4721 # The DST switch times for 2002, in std time.
4722 dston = datetime(2002, 4, 7, 2)
4723 dstoff = datetime(2002, 10, 27, 1)
4724
4725 theclass = datetime
4726
4727 # Check a time that's inside DST.
4728 def checkinside(self, dt, tz, utc, dston, dstoff):
4729 self.assertEqual(dt.dst(), HOUR)
4730
4731 # Conversion to our own timezone is always an identity.
4732 self.assertEqual(dt.astimezone(tz), dt)
4733
4734 asutc = dt.astimezone(utc)
4735 there_and_back = asutc.astimezone(tz)
4736
4737 # Conversion to UTC and back isn't always an identity here,
4738 # because there are redundant spellings (in local time) of
4739 # UTC time when DST begins: the clock jumps from 1:59:59
4740 # to 3:00:00, and a local time of 2:MM:SS doesn't really
4741 # make sense then. The classes above treat 2:MM:SS as
4742 # daylight time then (it's "after 2am"), really an alias
4743 # for 1:MM:SS standard time. The latter form is what
4744 # conversion back from UTC produces.
4745 if dt.date() == dston.date() and dt.hour == 2:
4746 # We're in the redundant hour, and coming back from
4747 # UTC gives the 1:MM:SS standard-time spelling.
4748 self.assertEqual(there_and_back + HOUR, dt)
4749 # Although during was considered to be in daylight
4750 # time, there_and_back is not.
4751 self.assertEqual(there_and_back.dst(), ZERO)
4752 # They're the same times in UTC.
4753 self.assertEqual(there_and_back.astimezone(utc),
4754 dt.astimezone(utc))
4755 else:
4756 # We're not in the redundant hour.
4757 self.assertEqual(dt, there_and_back)
4758
4759 # Because we have a redundant spelling when DST begins, there is
Ezio Melotti3b3499b2011-03-16 11:35:38 +02004760 # (unfortunately) an hour when DST ends that can't be spelled at all in
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004761 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
4762 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
4763 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
4764 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
4765 # expressed in local time. Nevertheless, we want conversion back
4766 # from UTC to mimic the local clock's "repeat an hour" behavior.
4767 nexthour_utc = asutc + HOUR
4768 nexthour_tz = nexthour_utc.astimezone(tz)
4769 if dt.date() == dstoff.date() and dt.hour == 0:
4770 # We're in the hour before the last DST hour. The last DST hour
4771 # is ineffable. We want the conversion back to repeat 1:MM.
4772 self.assertEqual(nexthour_tz, dt.replace(hour=1))
4773 nexthour_utc += HOUR
4774 nexthour_tz = nexthour_utc.astimezone(tz)
4775 self.assertEqual(nexthour_tz, dt.replace(hour=1))
4776 else:
4777 self.assertEqual(nexthour_tz - dt, HOUR)
4778
4779 # Check a time that's outside DST.
4780 def checkoutside(self, dt, tz, utc):
4781 self.assertEqual(dt.dst(), ZERO)
4782
4783 # Conversion to our own timezone is always an identity.
4784 self.assertEqual(dt.astimezone(tz), dt)
4785
4786 # Converting to UTC and back is an identity too.
4787 asutc = dt.astimezone(utc)
4788 there_and_back = asutc.astimezone(tz)
4789 self.assertEqual(dt, there_and_back)
4790
4791 def convert_between_tz_and_utc(self, tz, utc):
4792 dston = self.dston.replace(tzinfo=tz)
4793 # Because 1:MM on the day DST ends is taken as being standard time,
4794 # there is no spelling in tz for the last hour of daylight time.
4795 # For purposes of the test, the last hour of DST is 0:MM, which is
4796 # taken as being daylight time (and 1:MM is taken as being standard
4797 # time).
4798 dstoff = self.dstoff.replace(tzinfo=tz)
4799 for delta in (timedelta(weeks=13),
4800 DAY,
4801 HOUR,
4802 timedelta(minutes=1),
4803 timedelta(microseconds=1)):
4804
4805 self.checkinside(dston, tz, utc, dston, dstoff)
4806 for during in dston + delta, dstoff - delta:
4807 self.checkinside(during, tz, utc, dston, dstoff)
4808
4809 self.checkoutside(dstoff, tz, utc)
4810 for outside in dston - delta, dstoff + delta:
4811 self.checkoutside(outside, tz, utc)
4812
4813 def test_easy(self):
4814 # Despite the name of this test, the endcases are excruciating.
4815 self.convert_between_tz_and_utc(Eastern, utc_real)
4816 self.convert_between_tz_and_utc(Pacific, utc_real)
4817 self.convert_between_tz_and_utc(Eastern, utc_fake)
4818 self.convert_between_tz_and_utc(Pacific, utc_fake)
4819 # The next is really dancing near the edge. It works because
4820 # Pacific and Eastern are far enough apart that their "problem
4821 # hours" don't overlap.
4822 self.convert_between_tz_and_utc(Eastern, Pacific)
4823 self.convert_between_tz_and_utc(Pacific, Eastern)
4824 # OTOH, these fail! Don't enable them. The difficulty is that
4825 # the edge case tests assume that every hour is representable in
4826 # the "utc" class. This is always true for a fixed-offset tzinfo
4827 # class (lke utc_real and utc_fake), but not for Eastern or Central.
4828 # For these adjacent DST-aware time zones, the range of time offsets
4829 # tested ends up creating hours in the one that aren't representable
4830 # in the other. For the same reason, we would see failures in the
4831 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
4832 # offset deltas in convert_between_tz_and_utc().
4833 #
4834 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
4835 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
4836
4837 def test_tricky(self):
4838 # 22:00 on day before daylight starts.
4839 fourback = self.dston - timedelta(hours=4)
4840 ninewest = FixedOffset(-9*60, "-0900", 0)
4841 fourback = fourback.replace(tzinfo=ninewest)
4842 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
4843 # 2", we should get the 3 spelling.
4844 # If we plug 22:00 the day before into Eastern, it "looks like std
4845 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
4846 # to 22:00 lands on 2:00, which makes no sense in local time (the
4847 # local clock jumps from 1 to 3). The point here is to make sure we
4848 # get the 3 spelling.
4849 expected = self.dston.replace(hour=3)
4850 got = fourback.astimezone(Eastern).replace(tzinfo=None)
4851 self.assertEqual(expected, got)
4852
4853 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
4854 # case we want the 1:00 spelling.
4855 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
4856 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
4857 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
4858 # spelling.
4859 expected = self.dston.replace(hour=1)
4860 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
4861 self.assertEqual(expected, got)
4862
4863 # Now on the day DST ends, we want "repeat an hour" behavior.
4864 # UTC 4:MM 5:MM 6:MM 7:MM checking these
4865 # EST 23:MM 0:MM 1:MM 2:MM
4866 # EDT 0:MM 1:MM 2:MM 3:MM
4867 # wall 0:MM 1:MM 1:MM 2:MM against these
4868 for utc in utc_real, utc_fake:
4869 for tz in Eastern, Pacific:
4870 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
4871 # Convert that to UTC.
4872 first_std_hour -= tz.utcoffset(None)
4873 # Adjust for possibly fake UTC.
4874 asutc = first_std_hour + utc.utcoffset(None)
4875 # First UTC hour to convert; this is 4:00 when utc=utc_real &
4876 # tz=Eastern.
4877 asutcbase = asutc.replace(tzinfo=utc)
4878 for tzhour in (0, 1, 1, 2):
4879 expectedbase = self.dstoff.replace(hour=tzhour)
4880 for minute in 0, 30, 59:
4881 expected = expectedbase.replace(minute=minute)
4882 asutc = asutcbase.replace(minute=minute)
4883 astz = asutc.astimezone(tz)
4884 self.assertEqual(astz.replace(tzinfo=None), expected)
4885 asutcbase += HOUR
4886
4887
4888 def test_bogus_dst(self):
4889 class ok(tzinfo):
4890 def utcoffset(self, dt): return HOUR
4891 def dst(self, dt): return HOUR
4892
4893 now = self.theclass.now().replace(tzinfo=utc_real)
4894 # Doesn't blow up.
4895 now.astimezone(ok())
4896
4897 # Does blow up.
4898 class notok(ok):
4899 def dst(self, dt): return None
4900 self.assertRaises(ValueError, now.astimezone, notok())
4901
4902 # Sometimes blow up. In the following, tzinfo.dst()
4903 # implementation may return None or not None depending on
4904 # whether DST is assumed to be in effect. In this situation,
4905 # a ValueError should be raised by astimezone().
4906 class tricky_notok(ok):
4907 def dst(self, dt):
4908 if dt.year == 2000:
4909 return None
4910 else:
4911 return 10*HOUR
4912 dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
4913 self.assertRaises(ValueError, dt.astimezone, tricky_notok())
4914
4915 def test_fromutc(self):
4916 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
4917 now = datetime.utcnow().replace(tzinfo=utc_real)
4918 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
4919 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
4920 enow = Eastern.fromutc(now) # doesn't blow up
4921 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
4922 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
4923 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
4924
4925 # Always converts UTC to standard time.
4926 class FauxUSTimeZone(USTimeZone):
4927 def fromutc(self, dt):
4928 return dt + self.stdoffset
4929 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
4930
4931 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
4932 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
4933 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
4934
4935 # Check around DST start.
4936 start = self.dston.replace(hour=4, tzinfo=Eastern)
4937 fstart = start.replace(tzinfo=FEastern)
4938 for wall in 23, 0, 1, 3, 4, 5:
4939 expected = start.replace(hour=wall)
4940 if wall == 23:
4941 expected -= timedelta(days=1)
4942 got = Eastern.fromutc(start)
4943 self.assertEqual(expected, got)
4944
4945 expected = fstart + FEastern.stdoffset
4946 got = FEastern.fromutc(fstart)
4947 self.assertEqual(expected, got)
4948
4949 # Ensure astimezone() calls fromutc() too.
4950 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
4951 self.assertEqual(expected, got)
4952
4953 start += HOUR
4954 fstart += HOUR
4955
4956 # Check around DST end.
4957 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
4958 fstart = start.replace(tzinfo=FEastern)
4959 for wall in 0, 1, 1, 2, 3, 4:
4960 expected = start.replace(hour=wall)
4961 got = Eastern.fromutc(start)
4962 self.assertEqual(expected, got)
4963
4964 expected = fstart + FEastern.stdoffset
4965 got = FEastern.fromutc(fstart)
4966 self.assertEqual(expected, got)
4967
4968 # Ensure astimezone() calls fromutc() too.
4969 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
4970 self.assertEqual(expected, got)
4971
4972 start += HOUR
4973 fstart += HOUR
4974
4975
4976#############################################################################
4977# oddballs
4978
4979class Oddballs(unittest.TestCase):
4980
4981 def test_bug_1028306(self):
4982 # Trying to compare a date to a datetime should act like a mixed-
4983 # type comparison, despite that datetime is a subclass of date.
4984 as_date = date.today()
4985 as_datetime = datetime.combine(as_date, time())
4986 self.assertTrue(as_date != as_datetime)
4987 self.assertTrue(as_datetime != as_date)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004988 self.assertFalse(as_date == as_datetime)
4989 self.assertFalse(as_datetime == as_date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004990 self.assertRaises(TypeError, lambda: as_date < as_datetime)
4991 self.assertRaises(TypeError, lambda: as_datetime < as_date)
4992 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
4993 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
4994 self.assertRaises(TypeError, lambda: as_date > as_datetime)
4995 self.assertRaises(TypeError, lambda: as_datetime > as_date)
4996 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
4997 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
4998
Raymond Hettinger15f44ab2016-08-30 10:47:49 -07004999 # Nevertheless, comparison should work with the base-class (date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00005000 # projection if use of a date method is forced.
5001 self.assertEqual(as_date.__eq__(as_datetime), True)
5002 different_day = (as_date.day + 1) % 20 + 1
5003 as_different = as_datetime.replace(day= different_day)
5004 self.assertEqual(as_date.__eq__(as_different), False)
5005
5006 # And date should compare with other subclasses of date. If a
5007 # subclass wants to stop this, it's up to the subclass to do so.
5008 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
5009 self.assertEqual(as_date, date_sc)
5010 self.assertEqual(date_sc, as_date)
5011
5012 # Ditto for datetimes.
5013 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
5014 as_date.day, 0, 0, 0)
5015 self.assertEqual(as_datetime, datetime_sc)
5016 self.assertEqual(datetime_sc, as_datetime)
5017
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04005018 def test_extra_attributes(self):
5019 for x in [date.today(),
5020 time(),
5021 datetime.utcnow(),
5022 timedelta(),
5023 tzinfo(),
5024 timezone(timedelta())]:
5025 with self.assertRaises(AttributeError):
5026 x.abc = 1
5027
5028 def test_check_arg_types(self):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04005029 class Number:
5030 def __init__(self, value):
5031 self.value = value
5032 def __int__(self):
5033 return self.value
5034
5035 for xx in [decimal.Decimal(10),
5036 decimal.Decimal('10.9'),
5037 Number(10)]:
Serhiy Storchaka6a44f6e2019-02-25 17:57:58 +02005038 with self.assertWarns(DeprecationWarning):
5039 self.assertEqual(datetime(10, 10, 10, 10, 10, 10, 10),
5040 datetime(xx, xx, xx, xx, xx, xx, xx))
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04005041
5042 with self.assertRaisesRegex(TypeError, '^an integer is required '
R David Murray44b548d2016-09-08 13:59:53 -04005043 r'\(got type str\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04005044 datetime(10, 10, '10')
5045
5046 f10 = Number(10.9)
5047 with self.assertRaisesRegex(TypeError, '^__int__ returned non-int '
Serhiy Storchaka6a44f6e2019-02-25 17:57:58 +02005048 r'\(type float\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04005049 datetime(10, 10, f10)
5050
5051 class Float(float):
5052 pass
5053 s10 = Float(10.9)
5054 with self.assertRaisesRegex(TypeError, '^integer argument expected, '
5055 'got float$'):
5056 datetime(10, 10, s10)
5057
5058 with self.assertRaises(TypeError):
5059 datetime(10., 10, 10)
5060 with self.assertRaises(TypeError):
5061 datetime(10, 10., 10)
5062 with self.assertRaises(TypeError):
5063 datetime(10, 10, 10.)
5064 with self.assertRaises(TypeError):
5065 datetime(10, 10, 10, 10.)
5066 with self.assertRaises(TypeError):
5067 datetime(10, 10, 10, 10, 10.)
5068 with self.assertRaises(TypeError):
5069 datetime(10, 10, 10, 10, 10, 10.)
5070 with self.assertRaises(TypeError):
5071 datetime(10, 10, 10, 10, 10, 10, 10.)
5072
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005073#############################################################################
5074# Local Time Disambiguation
5075
5076# An experimental reimplementation of fromutc that respects the "fold" flag.
5077
5078class tzinfo2(tzinfo):
5079
5080 def fromutc(self, dt):
5081 "datetime in UTC -> datetime in local time."
5082
5083 if not isinstance(dt, datetime):
5084 raise TypeError("fromutc() requires a datetime argument")
5085 if dt.tzinfo is not self:
5086 raise ValueError("dt.tzinfo is not self")
5087 # Returned value satisfies
5088 # dt + ldt.utcoffset() = ldt
5089 off0 = dt.replace(fold=0).utcoffset()
5090 off1 = dt.replace(fold=1).utcoffset()
5091 if off0 is None or off1 is None or dt.dst() is None:
5092 raise ValueError
5093 if off0 == off1:
5094 ldt = dt + off0
5095 off1 = ldt.utcoffset()
5096 if off0 == off1:
5097 return ldt
5098 # Now, we discovered both possible offsets, so
5099 # we can just try four possible solutions:
5100 for off in [off0, off1]:
5101 ldt = dt + off
5102 if ldt.utcoffset() == off:
5103 return ldt
5104 ldt = ldt.replace(fold=1)
5105 if ldt.utcoffset() == off:
5106 return ldt
5107
5108 raise ValueError("No suitable local time found")
5109
5110# Reimplementing simplified US timezones to respect the "fold" flag:
5111
5112class USTimeZone2(tzinfo2):
5113
5114 def __init__(self, hours, reprname, stdname, dstname):
5115 self.stdoffset = timedelta(hours=hours)
5116 self.reprname = reprname
5117 self.stdname = stdname
5118 self.dstname = dstname
5119
5120 def __repr__(self):
5121 return self.reprname
5122
5123 def tzname(self, dt):
5124 if self.dst(dt):
5125 return self.dstname
5126 else:
5127 return self.stdname
5128
5129 def utcoffset(self, dt):
5130 return self.stdoffset + self.dst(dt)
5131
5132 def dst(self, dt):
5133 if dt is None or dt.tzinfo is None:
5134 # An exception instead may be sensible here, in one or more of
5135 # the cases.
5136 return ZERO
5137 assert dt.tzinfo is self
5138
5139 # Find first Sunday in April.
5140 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
5141 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
5142
5143 # Find last Sunday in October.
5144 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
5145 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
5146
5147 # Can't compare naive to aware objects, so strip the timezone from
5148 # dt first.
5149 dt = dt.replace(tzinfo=None)
5150 if start + HOUR <= dt < end:
5151 # DST is in effect.
5152 return HOUR
5153 elif end <= dt < end + HOUR:
5154 # Fold (an ambiguous hour): use dt.fold to disambiguate.
5155 return ZERO if dt.fold else HOUR
5156 elif start <= dt < start + HOUR:
5157 # Gap (a non-existent hour): reverse the fold rule.
5158 return HOUR if dt.fold else ZERO
5159 else:
5160 # DST is off.
5161 return ZERO
5162
5163Eastern2 = USTimeZone2(-5, "Eastern2", "EST", "EDT")
5164Central2 = USTimeZone2(-6, "Central2", "CST", "CDT")
5165Mountain2 = USTimeZone2(-7, "Mountain2", "MST", "MDT")
5166Pacific2 = USTimeZone2(-8, "Pacific2", "PST", "PDT")
5167
5168# Europe_Vilnius_1941 tzinfo implementation reproduces the following
5169# 1941 transition from Olson's tzdist:
5170#
5171# Zone NAME GMTOFF RULES FORMAT [UNTIL]
5172# ZoneEurope/Vilnius 1:00 - CET 1940 Aug 3
5173# 3:00 - MSK 1941 Jun 24
5174# 1:00 C-Eur CE%sT 1944 Aug
5175#
5176# $ zdump -v Europe/Vilnius | grep 1941
5177# Europe/Vilnius Mon Jun 23 20:59:59 1941 UTC = Mon Jun 23 23:59:59 1941 MSK isdst=0 gmtoff=10800
5178# Europe/Vilnius Mon Jun 23 21:00:00 1941 UTC = Mon Jun 23 23:00:00 1941 CEST isdst=1 gmtoff=7200
5179
5180class Europe_Vilnius_1941(tzinfo):
5181 def _utc_fold(self):
5182 return [datetime(1941, 6, 23, 21, tzinfo=self), # Mon Jun 23 21:00:00 1941 UTC
5183 datetime(1941, 6, 23, 22, tzinfo=self)] # Mon Jun 23 22:00:00 1941 UTC
5184
5185 def _loc_fold(self):
5186 return [datetime(1941, 6, 23, 23, tzinfo=self), # Mon Jun 23 23:00:00 1941 MSK / CEST
5187 datetime(1941, 6, 24, 0, tzinfo=self)] # Mon Jun 24 00:00:00 1941 CEST
5188
5189 def utcoffset(self, dt):
5190 fold_start, fold_stop = self._loc_fold()
5191 if dt < fold_start:
5192 return 3 * HOUR
5193 if dt < fold_stop:
5194 return (2 if dt.fold else 3) * HOUR
5195 # if dt >= fold_stop
5196 return 2 * HOUR
5197
5198 def dst(self, dt):
5199 fold_start, fold_stop = self._loc_fold()
5200 if dt < fold_start:
5201 return 0 * HOUR
5202 if dt < fold_stop:
5203 return (1 if dt.fold else 0) * HOUR
5204 # if dt >= fold_stop
5205 return 1 * HOUR
5206
5207 def tzname(self, dt):
5208 fold_start, fold_stop = self._loc_fold()
5209 if dt < fold_start:
5210 return 'MSK'
5211 if dt < fold_stop:
5212 return ('MSK', 'CEST')[dt.fold]
5213 # if dt >= fold_stop
5214 return 'CEST'
5215
5216 def fromutc(self, dt):
5217 assert dt.fold == 0
5218 assert dt.tzinfo is self
5219 if dt.year != 1941:
5220 raise NotImplementedError
5221 fold_start, fold_stop = self._utc_fold()
5222 if dt < fold_start:
5223 return dt + 3 * HOUR
5224 if dt < fold_stop:
5225 return (dt + 2 * HOUR).replace(fold=1)
5226 # if dt >= fold_stop
5227 return dt + 2 * HOUR
5228
5229
5230class TestLocalTimeDisambiguation(unittest.TestCase):
5231
5232 def test_vilnius_1941_fromutc(self):
5233 Vilnius = Europe_Vilnius_1941()
5234
5235 gdt = datetime(1941, 6, 23, 20, 59, 59, tzinfo=timezone.utc)
5236 ldt = gdt.astimezone(Vilnius)
5237 self.assertEqual(ldt.strftime("%c %Z%z"),
5238 'Mon Jun 23 23:59:59 1941 MSK+0300')
5239 self.assertEqual(ldt.fold, 0)
5240 self.assertFalse(ldt.dst())
5241
5242 gdt = datetime(1941, 6, 23, 21, tzinfo=timezone.utc)
5243 ldt = gdt.astimezone(Vilnius)
5244 self.assertEqual(ldt.strftime("%c %Z%z"),
5245 'Mon Jun 23 23:00:00 1941 CEST+0200')
5246 self.assertEqual(ldt.fold, 1)
5247 self.assertTrue(ldt.dst())
5248
5249 gdt = datetime(1941, 6, 23, 22, tzinfo=timezone.utc)
5250 ldt = gdt.astimezone(Vilnius)
5251 self.assertEqual(ldt.strftime("%c %Z%z"),
5252 'Tue Jun 24 00:00:00 1941 CEST+0200')
5253 self.assertEqual(ldt.fold, 0)
5254 self.assertTrue(ldt.dst())
5255
5256 def test_vilnius_1941_toutc(self):
5257 Vilnius = Europe_Vilnius_1941()
5258
5259 ldt = datetime(1941, 6, 23, 22, 59, 59, tzinfo=Vilnius)
5260 gdt = ldt.astimezone(timezone.utc)
5261 self.assertEqual(gdt.strftime("%c %Z"),
5262 'Mon Jun 23 19:59:59 1941 UTC')
5263
5264 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius)
5265 gdt = ldt.astimezone(timezone.utc)
5266 self.assertEqual(gdt.strftime("%c %Z"),
5267 'Mon Jun 23 20:59:59 1941 UTC')
5268
5269 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius, fold=1)
5270 gdt = ldt.astimezone(timezone.utc)
5271 self.assertEqual(gdt.strftime("%c %Z"),
5272 'Mon Jun 23 21:59:59 1941 UTC')
5273
5274 ldt = datetime(1941, 6, 24, 0, tzinfo=Vilnius)
5275 gdt = ldt.astimezone(timezone.utc)
5276 self.assertEqual(gdt.strftime("%c %Z"),
5277 'Mon Jun 23 22:00:00 1941 UTC')
5278
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005279 def test_constructors(self):
5280 t = time(0, fold=1)
5281 dt = datetime(1, 1, 1, fold=1)
5282 self.assertEqual(t.fold, 1)
5283 self.assertEqual(dt.fold, 1)
5284 with self.assertRaises(TypeError):
5285 time(0, 0, 0, 0, None, 0)
5286
5287 def test_member(self):
5288 dt = datetime(1, 1, 1, fold=1)
5289 t = dt.time()
5290 self.assertEqual(t.fold, 1)
5291 t = dt.timetz()
5292 self.assertEqual(t.fold, 1)
5293
5294 def test_replace(self):
5295 t = time(0)
5296 dt = datetime(1, 1, 1)
5297 self.assertEqual(t.replace(fold=1).fold, 1)
5298 self.assertEqual(dt.replace(fold=1).fold, 1)
5299 self.assertEqual(t.replace(fold=0).fold, 0)
5300 self.assertEqual(dt.replace(fold=0).fold, 0)
5301 # Check that replacement of other fields does not change "fold".
5302 t = t.replace(fold=1, tzinfo=Eastern)
5303 dt = dt.replace(fold=1, tzinfo=Eastern)
5304 self.assertEqual(t.replace(tzinfo=None).fold, 1)
5305 self.assertEqual(dt.replace(tzinfo=None).fold, 1)
Serhiy Storchaka314d6fc2017-03-31 22:48:16 +03005306 # Out of bounds.
5307 with self.assertRaises(ValueError):
5308 t.replace(fold=2)
5309 with self.assertRaises(ValueError):
5310 dt.replace(fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005311 # Check that fold is a keyword-only argument
5312 with self.assertRaises(TypeError):
5313 t.replace(1, 1, 1, None, 1)
5314 with self.assertRaises(TypeError):
5315 dt.replace(1, 1, 1, 1, 1, 1, 1, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04005316
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005317 def test_comparison(self):
5318 t = time(0)
5319 dt = datetime(1, 1, 1)
5320 self.assertEqual(t, t.replace(fold=1))
5321 self.assertEqual(dt, dt.replace(fold=1))
5322
5323 def test_hash(self):
5324 t = time(0)
5325 dt = datetime(1, 1, 1)
5326 self.assertEqual(hash(t), hash(t.replace(fold=1)))
5327 self.assertEqual(hash(dt), hash(dt.replace(fold=1)))
5328
5329 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5330 def test_fromtimestamp(self):
5331 s = 1414906200
5332 dt0 = datetime.fromtimestamp(s)
5333 dt1 = datetime.fromtimestamp(s + 3600)
5334 self.assertEqual(dt0.fold, 0)
5335 self.assertEqual(dt1.fold, 1)
5336
5337 @support.run_with_tz('Australia/Lord_Howe')
5338 def test_fromtimestamp_lord_howe(self):
5339 tm = _time.localtime(1.4e9)
5340 if _time.strftime('%Z%z', tm) != 'LHST+1030':
5341 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
5342 # $ TZ=Australia/Lord_Howe date -r 1428158700
5343 # Sun Apr 5 01:45:00 LHDT 2015
5344 # $ TZ=Australia/Lord_Howe date -r 1428160500
5345 # Sun Apr 5 01:45:00 LHST 2015
5346 s = 1428158700
5347 t0 = datetime.fromtimestamp(s)
5348 t1 = datetime.fromtimestamp(s + 1800)
5349 self.assertEqual(t0, t1)
5350 self.assertEqual(t0.fold, 0)
5351 self.assertEqual(t1.fold, 1)
5352
Ammar Askar96d1e692018-07-25 09:54:58 -07005353 def test_fromtimestamp_low_fold_detection(self):
5354 # Ensure that fold detection doesn't cause an
5355 # OSError for really low values, see bpo-29097
5356 self.assertEqual(datetime.fromtimestamp(0).fold, 0)
5357
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005358 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5359 def test_timestamp(self):
5360 dt0 = datetime(2014, 11, 2, 1, 30)
5361 dt1 = dt0.replace(fold=1)
5362 self.assertEqual(dt0.timestamp() + 3600,
5363 dt1.timestamp())
5364
5365 @support.run_with_tz('Australia/Lord_Howe')
5366 def test_timestamp_lord_howe(self):
5367 tm = _time.localtime(1.4e9)
5368 if _time.strftime('%Z%z', tm) != 'LHST+1030':
5369 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
5370 t = datetime(2015, 4, 5, 1, 45)
5371 s0 = t.replace(fold=0).timestamp()
5372 s1 = t.replace(fold=1).timestamp()
5373 self.assertEqual(s0 + 1800, s1)
5374
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005375 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5376 def test_astimezone(self):
5377 dt0 = datetime(2014, 11, 2, 1, 30)
5378 dt1 = dt0.replace(fold=1)
5379 # Convert both naive instances to aware.
5380 adt0 = dt0.astimezone()
5381 adt1 = dt1.astimezone()
5382 # Check that the first instance in DST zone and the second in STD
5383 self.assertEqual(adt0.tzname(), 'EDT')
5384 self.assertEqual(adt1.tzname(), 'EST')
5385 self.assertEqual(adt0 + HOUR, adt1)
5386 # Aware instances with fixed offset tzinfo's always have fold=0
5387 self.assertEqual(adt0.fold, 0)
5388 self.assertEqual(adt1.fold, 0)
5389
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005390 def test_pickle_fold(self):
5391 t = time(fold=1)
5392 dt = datetime(1, 1, 1, fold=1)
5393 for pickler, unpickler, proto in pickle_choices:
5394 for x in [t, dt]:
5395 s = pickler.dumps(x, proto)
5396 y = unpickler.loads(s)
5397 self.assertEqual(x, y)
5398 self.assertEqual((0 if proto < 4 else x.fold), y.fold)
5399
5400 def test_repr(self):
5401 t = time(fold=1)
5402 dt = datetime(1, 1, 1, fold=1)
5403 self.assertEqual(repr(t), 'datetime.time(0, 0, fold=1)')
5404 self.assertEqual(repr(dt),
5405 'datetime.datetime(1, 1, 1, 0, 0, fold=1)')
5406
5407 def test_dst(self):
5408 # Let's first establish that things work in regular times.
5409 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
5410 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
5411 self.assertEqual(dt_summer.dst(), HOUR)
5412 self.assertEqual(dt_winter.dst(), ZERO)
5413 # The disambiguation flag is ignored
5414 self.assertEqual(dt_summer.replace(fold=1).dst(), HOUR)
5415 self.assertEqual(dt_winter.replace(fold=1).dst(), ZERO)
5416
5417 # Pick local time in the fold.
5418 for minute in [0, 30, 59]:
5419 dt = datetime(2002, 10, 27, 1, minute, tzinfo=Eastern2)
5420 # With fold=0 (the default) it is in DST.
5421 self.assertEqual(dt.dst(), HOUR)
5422 # With fold=1 it is in STD.
5423 self.assertEqual(dt.replace(fold=1).dst(), ZERO)
5424
5425 # Pick local time in the gap.
5426 for minute in [0, 30, 59]:
5427 dt = datetime(2002, 4, 7, 2, minute, tzinfo=Eastern2)
5428 # With fold=0 (the default) it is in STD.
5429 self.assertEqual(dt.dst(), ZERO)
5430 # With fold=1 it is in DST.
5431 self.assertEqual(dt.replace(fold=1).dst(), HOUR)
5432
5433
5434 def test_utcoffset(self):
5435 # Let's first establish that things work in regular times.
5436 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
5437 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
5438 self.assertEqual(dt_summer.utcoffset(), -4 * HOUR)
5439 self.assertEqual(dt_winter.utcoffset(), -5 * HOUR)
5440 # The disambiguation flag is ignored
5441 self.assertEqual(dt_summer.replace(fold=1).utcoffset(), -4 * HOUR)
5442 self.assertEqual(dt_winter.replace(fold=1).utcoffset(), -5 * HOUR)
5443
5444 def test_fromutc(self):
5445 # Let's first establish that things work in regular times.
5446 u_summer = datetime(2002, 10, 27, 6, tzinfo=Eastern2) - timedelta.resolution
5447 u_winter = datetime(2002, 10, 27, 7, tzinfo=Eastern2)
5448 t_summer = Eastern2.fromutc(u_summer)
5449 t_winter = Eastern2.fromutc(u_winter)
5450 self.assertEqual(t_summer, u_summer - 4 * HOUR)
5451 self.assertEqual(t_winter, u_winter - 5 * HOUR)
5452 self.assertEqual(t_summer.fold, 0)
5453 self.assertEqual(t_winter.fold, 0)
5454
5455 # What happens in the fall-back fold?
5456 u = datetime(2002, 10, 27, 5, 30, tzinfo=Eastern2)
5457 t0 = Eastern2.fromutc(u)
5458 u += HOUR
5459 t1 = Eastern2.fromutc(u)
5460 self.assertEqual(t0, t1)
5461 self.assertEqual(t0.fold, 0)
5462 self.assertEqual(t1.fold, 1)
5463 # The tricky part is when u is in the local fold:
5464 u = datetime(2002, 10, 27, 1, 30, tzinfo=Eastern2)
5465 t = Eastern2.fromutc(u)
5466 self.assertEqual((t.day, t.hour), (26, 21))
5467 # .. or gets into the local fold after a standard time adjustment
5468 u = datetime(2002, 10, 27, 6, 30, tzinfo=Eastern2)
5469 t = Eastern2.fromutc(u)
5470 self.assertEqual((t.day, t.hour), (27, 1))
5471
5472 # What happens in the spring-forward gap?
5473 u = datetime(2002, 4, 7, 2, 0, tzinfo=Eastern2)
5474 t = Eastern2.fromutc(u)
5475 self.assertEqual((t.day, t.hour), (6, 21))
5476
5477 def test_mixed_compare_regular(self):
5478 t = datetime(2000, 1, 1, tzinfo=Eastern2)
5479 self.assertEqual(t, t.astimezone(timezone.utc))
5480 t = datetime(2000, 6, 1, tzinfo=Eastern2)
5481 self.assertEqual(t, t.astimezone(timezone.utc))
5482
5483 def test_mixed_compare_fold(self):
5484 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
5485 t_fold_utc = t_fold.astimezone(timezone.utc)
5486 self.assertNotEqual(t_fold, t_fold_utc)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04005487 self.assertNotEqual(t_fold_utc, t_fold)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005488
5489 def test_mixed_compare_gap(self):
5490 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
5491 t_gap_utc = t_gap.astimezone(timezone.utc)
5492 self.assertNotEqual(t_gap, t_gap_utc)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04005493 self.assertNotEqual(t_gap_utc, t_gap)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005494
5495 def test_hash_aware(self):
5496 t = datetime(2000, 1, 1, tzinfo=Eastern2)
5497 self.assertEqual(hash(t), hash(t.replace(fold=1)))
5498 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
5499 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
5500 self.assertEqual(hash(t_fold), hash(t_fold.replace(fold=1)))
5501 self.assertEqual(hash(t_gap), hash(t_gap.replace(fold=1)))
5502
5503SEC = timedelta(0, 1)
5504
5505def pairs(iterable):
5506 a, b = itertools.tee(iterable)
5507 next(b, None)
5508 return zip(a, b)
5509
5510class ZoneInfo(tzinfo):
5511 zoneroot = '/usr/share/zoneinfo'
5512 def __init__(self, ut, ti):
5513 """
5514
5515 :param ut: array
5516 Array of transition point timestamps
5517 :param ti: list
5518 A list of (offset, isdst, abbr) tuples
5519 :return: None
5520 """
5521 self.ut = ut
5522 self.ti = ti
5523 self.lt = self.invert(ut, ti)
5524
5525 @staticmethod
5526 def invert(ut, ti):
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04005527 lt = (array('q', ut), array('q', ut))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005528 if ut:
5529 offset = ti[0][0] // SEC
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04005530 lt[0][0] += offset
5531 lt[1][0] += offset
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005532 for i in range(1, len(ut)):
5533 lt[0][i] += ti[i-1][0] // SEC
5534 lt[1][i] += ti[i][0] // SEC
5535 return lt
5536
5537 @classmethod
5538 def fromfile(cls, fileobj):
5539 if fileobj.read(4).decode() != "TZif":
5540 raise ValueError("not a zoneinfo file")
5541 fileobj.seek(32)
5542 counts = array('i')
5543 counts.fromfile(fileobj, 3)
5544 if sys.byteorder != 'big':
5545 counts.byteswap()
5546
5547 ut = array('i')
5548 ut.fromfile(fileobj, counts[0])
5549 if sys.byteorder != 'big':
5550 ut.byteswap()
5551
5552 type_indices = array('B')
5553 type_indices.fromfile(fileobj, counts[0])
5554
5555 ttis = []
5556 for i in range(counts[1]):
5557 ttis.append(struct.unpack(">lbb", fileobj.read(6)))
5558
5559 abbrs = fileobj.read(counts[2])
5560
5561 # Convert ttis
5562 for i, (gmtoff, isdst, abbrind) in enumerate(ttis):
5563 abbr = abbrs[abbrind:abbrs.find(0, abbrind)].decode()
5564 ttis[i] = (timedelta(0, gmtoff), isdst, abbr)
5565
5566 ti = [None] * len(ut)
5567 for i, idx in enumerate(type_indices):
5568 ti[i] = ttis[idx]
5569
5570 self = cls(ut, ti)
5571
5572 return self
5573
5574 @classmethod
5575 def fromname(cls, name):
5576 path = os.path.join(cls.zoneroot, name)
5577 with open(path, 'rb') as f:
5578 return cls.fromfile(f)
5579
5580 EPOCHORDINAL = date(1970, 1, 1).toordinal()
5581
5582 def fromutc(self, dt):
5583 """datetime in UTC -> datetime in local time."""
5584
5585 if not isinstance(dt, datetime):
5586 raise TypeError("fromutc() requires a datetime argument")
5587 if dt.tzinfo is not self:
5588 raise ValueError("dt.tzinfo is not self")
5589
5590 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
5591 + dt.hour * 3600
5592 + dt.minute * 60
5593 + dt.second)
5594
5595 if timestamp < self.ut[1]:
5596 tti = self.ti[0]
5597 fold = 0
5598 else:
5599 idx = bisect.bisect_right(self.ut, timestamp)
5600 assert self.ut[idx-1] <= timestamp
5601 assert idx == len(self.ut) or timestamp < self.ut[idx]
5602 tti_prev, tti = self.ti[idx-2:idx]
5603 # Detect fold
5604 shift = tti_prev[0] - tti[0]
5605 fold = (shift > timedelta(0, timestamp - self.ut[idx-1]))
5606 dt += tti[0]
5607 if fold:
5608 return dt.replace(fold=1)
5609 else:
5610 return dt
5611
5612 def _find_ti(self, dt, i):
5613 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
5614 + dt.hour * 3600
5615 + dt.minute * 60
5616 + dt.second)
5617 lt = self.lt[dt.fold]
5618 idx = bisect.bisect_right(lt, timestamp)
5619
5620 return self.ti[max(0, idx - 1)][i]
5621
5622 def utcoffset(self, dt):
5623 return self._find_ti(dt, 0)
5624
5625 def dst(self, dt):
5626 isdst = self._find_ti(dt, 1)
5627 # XXX: We cannot accurately determine the "save" value,
5628 # so let's return 1h whenever DST is in effect. Since
5629 # we don't use dst() in fromutc(), it is unlikely that
5630 # it will be needed for anything more than bool(dst()).
5631 return ZERO if isdst else HOUR
5632
5633 def tzname(self, dt):
5634 return self._find_ti(dt, 2)
5635
5636 @classmethod
5637 def zonenames(cls, zonedir=None):
5638 if zonedir is None:
5639 zonedir = cls.zoneroot
Alexander Belopolsky1b8f26c2016-08-11 11:01:45 -04005640 zone_tab = os.path.join(zonedir, 'zone.tab')
5641 try:
5642 f = open(zone_tab)
5643 except OSError:
5644 return
5645 with f:
5646 for line in f:
5647 line = line.strip()
5648 if line and not line.startswith('#'):
5649 yield line.split()[2]
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005650
5651 @classmethod
5652 def stats(cls, start_year=1):
5653 count = gap_count = fold_count = zeros_count = 0
5654 min_gap = min_fold = timedelta.max
5655 max_gap = max_fold = ZERO
5656 min_gap_datetime = max_gap_datetime = datetime.min
5657 min_gap_zone = max_gap_zone = None
5658 min_fold_datetime = max_fold_datetime = datetime.min
5659 min_fold_zone = max_fold_zone = None
5660 stats_since = datetime(start_year, 1, 1) # Starting from 1970 eliminates a lot of noise
5661 for zonename in cls.zonenames():
5662 count += 1
5663 tz = cls.fromname(zonename)
5664 for dt, shift in tz.transitions():
5665 if dt < stats_since:
5666 continue
5667 if shift > ZERO:
5668 gap_count += 1
5669 if (shift, dt) > (max_gap, max_gap_datetime):
5670 max_gap = shift
5671 max_gap_zone = zonename
5672 max_gap_datetime = dt
5673 if (shift, datetime.max - dt) < (min_gap, datetime.max - min_gap_datetime):
5674 min_gap = shift
5675 min_gap_zone = zonename
5676 min_gap_datetime = dt
5677 elif shift < ZERO:
5678 fold_count += 1
5679 shift = -shift
5680 if (shift, dt) > (max_fold, max_fold_datetime):
5681 max_fold = shift
5682 max_fold_zone = zonename
5683 max_fold_datetime = dt
5684 if (shift, datetime.max - dt) < (min_fold, datetime.max - min_fold_datetime):
5685 min_fold = shift
5686 min_fold_zone = zonename
5687 min_fold_datetime = dt
5688 else:
5689 zeros_count += 1
5690 trans_counts = (gap_count, fold_count, zeros_count)
5691 print("Number of zones: %5d" % count)
5692 print("Number of transitions: %5d = %d (gaps) + %d (folds) + %d (zeros)" %
5693 ((sum(trans_counts),) + trans_counts))
5694 print("Min gap: %16s at %s in %s" % (min_gap, min_gap_datetime, min_gap_zone))
5695 print("Max gap: %16s at %s in %s" % (max_gap, max_gap_datetime, max_gap_zone))
5696 print("Min fold: %16s at %s in %s" % (min_fold, min_fold_datetime, min_fold_zone))
5697 print("Max fold: %16s at %s in %s" % (max_fold, max_fold_datetime, max_fold_zone))
5698
5699
5700 def transitions(self):
5701 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
5702 shift = ti[0] - prev_ti[0]
5703 yield datetime.utcfromtimestamp(t), shift
5704
5705 def nondst_folds(self):
5706 """Find all folds with the same value of isdst on both sides of the transition."""
5707 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
5708 shift = ti[0] - prev_ti[0]
5709 if shift < ZERO and ti[1] == prev_ti[1]:
5710 yield datetime.utcfromtimestamp(t), -shift, prev_ti[2], ti[2]
5711
5712 @classmethod
5713 def print_all_nondst_folds(cls, same_abbr=False, start_year=1):
5714 count = 0
5715 for zonename in cls.zonenames():
5716 tz = cls.fromname(zonename)
5717 for dt, shift, prev_abbr, abbr in tz.nondst_folds():
5718 if dt.year < start_year or same_abbr and prev_abbr != abbr:
5719 continue
5720 count += 1
5721 print("%3d) %-30s %s %10s %5s -> %s" %
5722 (count, zonename, dt, shift, prev_abbr, abbr))
5723
5724 def folds(self):
5725 for t, shift in self.transitions():
5726 if shift < ZERO:
5727 yield t, -shift
5728
5729 def gaps(self):
5730 for t, shift in self.transitions():
5731 if shift > ZERO:
5732 yield t, shift
5733
5734 def zeros(self):
5735 for t, shift in self.transitions():
5736 if not shift:
5737 yield t
5738
5739
5740class ZoneInfoTest(unittest.TestCase):
5741 zonename = 'America/New_York'
5742
5743 def setUp(self):
5744 if sys.platform == "win32":
5745 self.skipTest("Skipping zoneinfo tests on Windows")
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04005746 try:
5747 self.tz = ZoneInfo.fromname(self.zonename)
5748 except FileNotFoundError as err:
5749 self.skipTest("Skipping %s: %s" % (self.zonename, err))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005750
5751 def assertEquivDatetimes(self, a, b):
5752 self.assertEqual((a.replace(tzinfo=None), a.fold, id(a.tzinfo)),
5753 (b.replace(tzinfo=None), b.fold, id(b.tzinfo)))
5754
5755 def test_folds(self):
5756 tz = self.tz
5757 for dt, shift in tz.folds():
5758 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
5759 udt = dt + x
5760 ldt = tz.fromutc(udt.replace(tzinfo=tz))
5761 self.assertEqual(ldt.fold, 1)
5762 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
5763 self.assertEquivDatetimes(adt, ldt)
5764 utcoffset = ldt.utcoffset()
5765 self.assertEqual(ldt.replace(tzinfo=None), udt + utcoffset)
5766 # Round trip
5767 self.assertEquivDatetimes(ldt.astimezone(timezone.utc),
5768 udt.replace(tzinfo=timezone.utc))
5769
5770
5771 for x in [-timedelta.resolution, shift]:
5772 udt = dt + x
5773 udt = udt.replace(tzinfo=tz)
5774 ldt = tz.fromutc(udt)
5775 self.assertEqual(ldt.fold, 0)
5776
5777 def test_gaps(self):
5778 tz = self.tz
5779 for dt, shift in tz.gaps():
5780 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
5781 udt = dt + x
5782 udt = udt.replace(tzinfo=tz)
5783 ldt = tz.fromutc(udt)
5784 self.assertEqual(ldt.fold, 0)
5785 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
5786 self.assertEquivDatetimes(adt, ldt)
5787 utcoffset = ldt.utcoffset()
5788 self.assertEqual(ldt.replace(tzinfo=None), udt.replace(tzinfo=None) + utcoffset)
5789 # Create a local time inside the gap
5790 ldt = tz.fromutc(dt.replace(tzinfo=tz)) - shift + x
5791 self.assertLess(ldt.replace(fold=1).utcoffset(),
5792 ldt.replace(fold=0).utcoffset(),
5793 "At %s." % ldt)
5794
5795 for x in [-timedelta.resolution, shift]:
5796 udt = dt + x
5797 ldt = tz.fromutc(udt.replace(tzinfo=tz))
5798 self.assertEqual(ldt.fold, 0)
5799
5800 def test_system_transitions(self):
5801 if ('Riyadh8' in self.zonename or
5802 # From tzdata NEWS file:
5803 # The files solar87, solar88, and solar89 are no longer distributed.
5804 # They were a negative experiment - that is, a demonstration that
5805 # tz data can represent solar time only with some difficulty and error.
5806 # Their presence in the distribution caused confusion, as Riyadh
5807 # civil time was generally not solar time in those years.
5808 self.zonename.startswith('right/')):
5809 self.skipTest("Skipping %s" % self.zonename)
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04005810 tz = self.tz
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005811 TZ = os.environ.get('TZ')
5812 os.environ['TZ'] = self.zonename
5813 try:
5814 _time.tzset()
5815 for udt, shift in tz.transitions():
Alexander Belopolsky10c2dd22016-08-12 19:08:15 -04005816 if udt.year >= 2037:
5817 # System support for times around the end of 32-bit time_t
5818 # and later is flaky on many systems.
5819 break
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005820 s0 = (udt - datetime(1970, 1, 1)) // SEC
5821 ss = shift // SEC # shift seconds
5822 for x in [-40 * 3600, -20*3600, -1, 0,
5823 ss - 1, ss + 20 * 3600, ss + 40 * 3600]:
5824 s = s0 + x
5825 sdt = datetime.fromtimestamp(s)
5826 tzdt = datetime.fromtimestamp(s, tz).replace(tzinfo=None)
5827 self.assertEquivDatetimes(sdt, tzdt)
5828 s1 = sdt.timestamp()
5829 self.assertEqual(s, s1)
5830 if ss > 0: # gap
5831 # Create local time inside the gap
5832 dt = datetime.fromtimestamp(s0) - shift / 2
5833 ts0 = dt.timestamp()
5834 ts1 = dt.replace(fold=1).timestamp()
5835 self.assertEqual(ts0, s0 + ss / 2)
5836 self.assertEqual(ts1, s0 - ss / 2)
5837 finally:
5838 if TZ is None:
5839 del os.environ['TZ']
5840 else:
5841 os.environ['TZ'] = TZ
5842 _time.tzset()
5843
5844
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04005845class ZoneInfoCompleteTest(unittest.TestSuite):
5846 def __init__(self):
5847 tests = []
5848 if is_resource_enabled('tzdata'):
5849 for name in ZoneInfo.zonenames():
5850 Test = type('ZoneInfoTest[%s]' % name, (ZoneInfoTest,), {})
5851 Test.zonename = name
5852 for method in dir(Test):
5853 if method.startswith('test_'):
5854 tests.append(Test(method))
5855 super().__init__(tests)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005856
5857# Iran had a sub-minute UTC offset before 1946.
5858class IranTest(ZoneInfoTest):
Alexander Belopolsky07e2a0a2016-07-30 11:41:02 -04005859 zonename = 'Asia/Tehran'
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005860
Paul Ganssle04af5b12018-01-24 17:29:30 -05005861
5862class CapiTest(unittest.TestCase):
5863 def setUp(self):
5864 # Since the C API is not present in the _Pure tests, skip all tests
5865 if self.__class__.__name__.endswith('Pure'):
5866 self.skipTest('Not relevant in pure Python')
5867
5868 # This *must* be called, and it must be called first, so until either
5869 # restriction is loosened, we'll call it as part of test setup
5870 _testcapi.test_datetime_capi()
5871
5872 def test_utc_capi(self):
5873 for use_macro in (True, False):
5874 capi_utc = _testcapi.get_timezone_utc_capi(use_macro)
5875
5876 with self.subTest(use_macro=use_macro):
5877 self.assertIs(capi_utc, timezone.utc)
5878
5879 def test_timezones_capi(self):
5880 est_capi, est_macro, est_macro_nn = _testcapi.make_timezones_capi()
5881
5882 exp_named = timezone(timedelta(hours=-5), "EST")
5883 exp_unnamed = timezone(timedelta(hours=-5))
5884
5885 cases = [
5886 ('est_capi', est_capi, exp_named),
5887 ('est_macro', est_macro, exp_named),
5888 ('est_macro_nn', est_macro_nn, exp_unnamed)
5889 ]
5890
5891 for name, tz_act, tz_exp in cases:
5892 with self.subTest(name=name):
5893 self.assertEqual(tz_act, tz_exp)
5894
5895 dt1 = datetime(2000, 2, 4, tzinfo=tz_act)
5896 dt2 = datetime(2000, 2, 4, tzinfo=tz_exp)
5897
5898 self.assertEqual(dt1, dt2)
5899 self.assertEqual(dt1.tzname(), dt2.tzname())
5900
5901 dt_utc = datetime(2000, 2, 4, 5, tzinfo=timezone.utc)
5902
5903 self.assertEqual(dt1.astimezone(timezone.utc), dt_utc)
5904
Paul Gansslea049f572018-02-22 15:15:32 -05005905 def test_timezones_offset_zero(self):
5906 utc0, utc1, non_utc = _testcapi.get_timezones_offset_zero()
5907
5908 with self.subTest(testname="utc0"):
5909 self.assertIs(utc0, timezone.utc)
5910
5911 with self.subTest(testname="utc1"):
5912 self.assertIs(utc1, timezone.utc)
5913
5914 with self.subTest(testname="non_utc"):
5915 self.assertIsNot(non_utc, timezone.utc)
5916
5917 non_utc_exp = timezone(timedelta(hours=0), "")
5918
5919 self.assertEqual(non_utc, non_utc_exp)
5920
5921 dt1 = datetime(2000, 2, 4, tzinfo=non_utc)
5922 dt2 = datetime(2000, 2, 4, tzinfo=non_utc_exp)
5923
5924 self.assertEqual(dt1, dt2)
5925 self.assertEqual(dt1.tzname(), dt2.tzname())
5926
Paul Ganssle04af5b12018-01-24 17:29:30 -05005927 def test_check_date(self):
5928 class DateSubclass(date):
5929 pass
5930
5931 d = date(2011, 1, 1)
5932 ds = DateSubclass(2011, 1, 1)
5933 dt = datetime(2011, 1, 1)
5934
5935 is_date = _testcapi.datetime_check_date
5936
5937 # Check the ones that should be valid
5938 self.assertTrue(is_date(d))
5939 self.assertTrue(is_date(dt))
5940 self.assertTrue(is_date(ds))
5941 self.assertTrue(is_date(d, True))
5942
5943 # Check that the subclasses do not match exactly
5944 self.assertFalse(is_date(dt, True))
5945 self.assertFalse(is_date(ds, True))
5946
5947 # Check that various other things are not dates at all
5948 args = [tuple(), list(), 1, '2011-01-01',
5949 timedelta(1), timezone.utc, time(12, 00)]
5950 for arg in args:
5951 for exact in (True, False):
5952 with self.subTest(arg=arg, exact=exact):
5953 self.assertFalse(is_date(arg, exact))
5954
5955 def test_check_time(self):
5956 class TimeSubclass(time):
5957 pass
5958
5959 t = time(12, 30)
5960 ts = TimeSubclass(12, 30)
5961
5962 is_time = _testcapi.datetime_check_time
5963
5964 # Check the ones that should be valid
5965 self.assertTrue(is_time(t))
5966 self.assertTrue(is_time(ts))
5967 self.assertTrue(is_time(t, True))
5968
5969 # Check that the subclass does not match exactly
5970 self.assertFalse(is_time(ts, True))
5971
5972 # Check that various other things are not times
5973 args = [tuple(), list(), 1, '2011-01-01',
5974 timedelta(1), timezone.utc, date(2011, 1, 1)]
5975
5976 for arg in args:
5977 for exact in (True, False):
5978 with self.subTest(arg=arg, exact=exact):
5979 self.assertFalse(is_time(arg, exact))
5980
5981 def test_check_datetime(self):
5982 class DateTimeSubclass(datetime):
5983 pass
5984
5985 dt = datetime(2011, 1, 1, 12, 30)
5986 dts = DateTimeSubclass(2011, 1, 1, 12, 30)
5987
5988 is_datetime = _testcapi.datetime_check_datetime
5989
5990 # Check the ones that should be valid
5991 self.assertTrue(is_datetime(dt))
5992 self.assertTrue(is_datetime(dts))
5993 self.assertTrue(is_datetime(dt, True))
5994
5995 # Check that the subclass does not match exactly
5996 self.assertFalse(is_datetime(dts, True))
5997
5998 # Check that various other things are not datetimes
5999 args = [tuple(), list(), 1, '2011-01-01',
6000 timedelta(1), timezone.utc, date(2011, 1, 1)]
6001
6002 for arg in args:
6003 for exact in (True, False):
6004 with self.subTest(arg=arg, exact=exact):
6005 self.assertFalse(is_datetime(arg, exact))
6006
6007 def test_check_delta(self):
6008 class TimeDeltaSubclass(timedelta):
6009 pass
6010
6011 td = timedelta(1)
6012 tds = TimeDeltaSubclass(1)
6013
6014 is_timedelta = _testcapi.datetime_check_delta
6015
6016 # Check the ones that should be valid
6017 self.assertTrue(is_timedelta(td))
6018 self.assertTrue(is_timedelta(tds))
6019 self.assertTrue(is_timedelta(td, True))
6020
6021 # Check that the subclass does not match exactly
6022 self.assertFalse(is_timedelta(tds, True))
6023
6024 # Check that various other things are not timedeltas
6025 args = [tuple(), list(), 1, '2011-01-01',
6026 timezone.utc, date(2011, 1, 1), datetime(2011, 1, 1)]
6027
6028 for arg in args:
6029 for exact in (True, False):
6030 with self.subTest(arg=arg, exact=exact):
6031 self.assertFalse(is_timedelta(arg, exact))
6032
6033 def test_check_tzinfo(self):
6034 class TZInfoSubclass(tzinfo):
6035 pass
6036
6037 tzi = tzinfo()
6038 tzis = TZInfoSubclass()
6039 tz = timezone(timedelta(hours=-5))
6040
6041 is_tzinfo = _testcapi.datetime_check_tzinfo
6042
6043 # Check the ones that should be valid
6044 self.assertTrue(is_tzinfo(tzi))
6045 self.assertTrue(is_tzinfo(tz))
6046 self.assertTrue(is_tzinfo(tzis))
6047 self.assertTrue(is_tzinfo(tzi, True))
6048
6049 # Check that the subclasses do not match exactly
6050 self.assertFalse(is_tzinfo(tz, True))
6051 self.assertFalse(is_tzinfo(tzis, True))
6052
6053 # Check that various other things are not tzinfos
6054 args = [tuple(), list(), 1, '2011-01-01',
6055 date(2011, 1, 1), datetime(2011, 1, 1)]
6056
6057 for arg in args:
6058 for exact in (True, False):
6059 with self.subTest(arg=arg, exact=exact):
6060 self.assertFalse(is_tzinfo(arg, exact))
6061
Edison A98ff4d52019-05-17 13:28:42 -07006062 def test_date_from_date(self):
6063 exp_date = date(1993, 8, 26)
6064
6065 for macro in [0, 1]:
6066 with self.subTest(macro=macro):
6067 c_api_date = _testcapi.get_date_fromdate(
6068 macro,
6069 exp_date.year,
6070 exp_date.month,
6071 exp_date.day)
6072
6073 self.assertEqual(c_api_date, exp_date)
6074
6075 def test_datetime_from_dateandtime(self):
6076 exp_date = datetime(1993, 8, 26, 22, 12, 55, 99999)
6077
6078 for macro in [0, 1]:
6079 with self.subTest(macro=macro):
6080 c_api_date = _testcapi.get_datetime_fromdateandtime(
6081 macro,
6082 exp_date.year,
6083 exp_date.month,
6084 exp_date.day,
6085 exp_date.hour,
6086 exp_date.minute,
6087 exp_date.second,
6088 exp_date.microsecond)
6089
6090 self.assertEqual(c_api_date, exp_date)
6091
6092 def test_datetime_from_dateandtimeandfold(self):
6093 exp_date = datetime(1993, 8, 26, 22, 12, 55, 99999)
6094
6095 for fold in [0, 1]:
6096 for macro in [0, 1]:
6097 with self.subTest(macro=macro, fold=fold):
6098 c_api_date = _testcapi.get_datetime_fromdateandtimeandfold(
6099 macro,
6100 exp_date.year,
6101 exp_date.month,
6102 exp_date.day,
6103 exp_date.hour,
6104 exp_date.minute,
6105 exp_date.second,
6106 exp_date.microsecond,
6107 exp_date.fold)
6108
6109 self.assertEqual(c_api_date, exp_date)
6110 self.assertEqual(c_api_date.fold, exp_date.fold)
6111
6112 def test_time_from_time(self):
6113 exp_time = time(22, 12, 55, 99999)
6114
6115 for macro in [0, 1]:
6116 with self.subTest(macro=macro):
6117 c_api_time = _testcapi.get_time_fromtime(
6118 macro,
6119 exp_time.hour,
6120 exp_time.minute,
6121 exp_time.second,
6122 exp_time.microsecond)
6123
6124 self.assertEqual(c_api_time, exp_time)
6125
6126 def test_time_from_timeandfold(self):
6127 exp_time = time(22, 12, 55, 99999)
6128
6129 for fold in [0, 1]:
6130 for macro in [0, 1]:
6131 with self.subTest(macro=macro, fold=fold):
6132 c_api_time = _testcapi.get_time_fromtimeandfold(
6133 macro,
6134 exp_time.hour,
6135 exp_time.minute,
6136 exp_time.second,
6137 exp_time.microsecond,
6138 exp_time.fold)
6139
6140 self.assertEqual(c_api_time, exp_time)
6141 self.assertEqual(c_api_time.fold, exp_time.fold)
6142
6143 def test_delta_from_dsu(self):
6144 exp_delta = timedelta(26, 55, 99999)
6145
6146 for macro in [0, 1]:
6147 with self.subTest(macro=macro):
6148 c_api_delta = _testcapi.get_delta_fromdsu(
6149 macro,
6150 exp_delta.days,
6151 exp_delta.seconds,
6152 exp_delta.microseconds)
6153
6154 self.assertEqual(c_api_delta, exp_delta)
6155
Paul Ganssle4d8c8c02019-04-27 15:39:40 -04006156 def test_date_from_timestamp(self):
6157 ts = datetime(1995, 4, 12).timestamp()
6158
6159 for macro in [0, 1]:
6160 with self.subTest(macro=macro):
6161 d = _testcapi.get_date_fromtimestamp(int(ts), macro)
6162
6163 self.assertEqual(d, date(1995, 4, 12))
6164
6165 def test_datetime_from_timestamp(self):
Paul Ganssle4d8c8c02019-04-27 15:39:40 -04006166 cases = [
6167 ((1995, 4, 12), None, False),
6168 ((1995, 4, 12), None, True),
6169 ((1995, 4, 12), timezone(timedelta(hours=1)), True),
6170 ((1995, 4, 12, 14, 30), None, False),
6171 ((1995, 4, 12, 14, 30), None, True),
6172 ((1995, 4, 12, 14, 30), timezone(timedelta(hours=1)), True),
6173 ]
6174
6175 from_timestamp = _testcapi.get_datetime_fromtimestamp
6176 for case in cases:
6177 for macro in [0, 1]:
6178 with self.subTest(case=case, macro=macro):
6179 dtup, tzinfo, usetz = case
6180 dt_orig = datetime(*dtup, tzinfo=tzinfo)
6181 ts = int(dt_orig.timestamp())
6182
6183 dt_rt = from_timestamp(ts, tzinfo, usetz, macro)
6184
6185 self.assertEqual(dt_orig, dt_rt)
6186
6187
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04006188def load_tests(loader, standard_tests, pattern):
6189 standard_tests.addTest(ZoneInfoCompleteTest())
6190 return standard_tests
6191
6192
Alexander Belopolskycf86e362010-07-23 19:25:47 +00006193if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -05006194 unittest.main()