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