blob: 958b33675c37b32c58440033a944d2254bf6f355 [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
Alexander Belopolskycf86e362010-07-23 19:25:47 +000056#############################################################################
57# module tests
58
59class TestModule(unittest.TestCase):
60
61 def test_constants(self):
62 datetime = datetime_module
63 self.assertEqual(datetime.MINYEAR, 1)
64 self.assertEqual(datetime.MAXYEAR, 9999)
65
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -040066 def test_name_cleanup(self):
Utkarsh Upadhyay287c5592017-07-21 02:14:54 +020067 if '_Pure' in self.__class__.__name__:
68 self.skipTest('Only run for Fast C implementation')
69
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -040070 datetime = datetime_module
71 names = set(name for name in dir(datetime)
72 if not name.startswith('__') and not name.endswith('__'))
73 allowed = set(['MAXYEAR', 'MINYEAR', 'date', 'datetime',
74 'datetime_CAPI', 'time', 'timedelta', 'timezone',
Ammar Askar96d1e692018-07-25 09:54:58 -070075 'tzinfo', 'sys'])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -040076 self.assertEqual(names - allowed, set([]))
77
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -050078 def test_divide_and_round(self):
Utkarsh Upadhyay287c5592017-07-21 02:14:54 +020079 if '_Fast' in self.__class__.__name__:
80 self.skipTest('Only run for Pure Python implementation')
81
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -050082 dar = datetime_module._divide_and_round
83
84 self.assertEqual(dar(-10, -3), 3)
85 self.assertEqual(dar(5, -2), -2)
86
87 # four cases: (2 signs of a) x (2 signs of b)
88 self.assertEqual(dar(7, 3), 2)
89 self.assertEqual(dar(-7, 3), -2)
90 self.assertEqual(dar(7, -3), -2)
91 self.assertEqual(dar(-7, -3), 2)
92
93 # ties to even - eight cases:
94 # (2 signs of a) x (2 signs of b) x (even / odd quotient)
95 self.assertEqual(dar(10, 4), 2)
96 self.assertEqual(dar(-10, 4), -2)
97 self.assertEqual(dar(10, -4), -2)
98 self.assertEqual(dar(-10, -4), 2)
99
100 self.assertEqual(dar(6, 4), 2)
101 self.assertEqual(dar(-6, 4), -2)
102 self.assertEqual(dar(6, -4), -2)
103 self.assertEqual(dar(-6, -4), 2)
104
105
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000106#############################################################################
107# tzinfo tests
108
109class FixedOffset(tzinfo):
110
111 def __init__(self, offset, name, dstoffset=42):
112 if isinstance(offset, int):
113 offset = timedelta(minutes=offset)
114 if isinstance(dstoffset, int):
115 dstoffset = timedelta(minutes=dstoffset)
116 self.__offset = offset
117 self.__name = name
118 self.__dstoffset = dstoffset
119 def __repr__(self):
120 return self.__name.lower()
121 def utcoffset(self, dt):
122 return self.__offset
123 def tzname(self, dt):
124 return self.__name
125 def dst(self, dt):
126 return self.__dstoffset
127
128class PicklableFixedOffset(FixedOffset):
129
130 def __init__(self, offset=None, name=None, dstoffset=None):
131 FixedOffset.__init__(self, offset, name, dstoffset)
132
Berker Peksage3385b42016-03-19 13:16:32 +0200133 def __getstate__(self):
134 return self.__dict__
135
Raymond Hettinger5a2146a2014-07-25 14:59:48 -0700136class _TZInfo(tzinfo):
137 def utcoffset(self, datetime_module):
138 return random.random()
139
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000140class TestTZInfo(unittest.TestCase):
141
Raymond Hettinger5a2146a2014-07-25 14:59:48 -0700142 def test_refcnt_crash_bug_22044(self):
143 tz1 = _TZInfo()
144 dt1 = datetime(2014, 7, 21, 11, 32, 3, 0, tz1)
145 with self.assertRaises(TypeError):
146 dt1.utcoffset()
147
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000148 def test_non_abstractness(self):
149 # In order to allow subclasses to get pickled, the C implementation
150 # wasn't able to get away with having __init__ raise
151 # NotImplementedError.
152 useless = tzinfo()
153 dt = datetime.max
154 self.assertRaises(NotImplementedError, useless.tzname, dt)
155 self.assertRaises(NotImplementedError, useless.utcoffset, dt)
156 self.assertRaises(NotImplementedError, useless.dst, dt)
157
158 def test_subclass_must_override(self):
159 class NotEnough(tzinfo):
160 def __init__(self, offset, name):
161 self.__offset = offset
162 self.__name = name
163 self.assertTrue(issubclass(NotEnough, tzinfo))
164 ne = NotEnough(3, "NotByALongShot")
165 self.assertIsInstance(ne, tzinfo)
166
167 dt = datetime.now()
168 self.assertRaises(NotImplementedError, ne.tzname, dt)
169 self.assertRaises(NotImplementedError, ne.utcoffset, dt)
170 self.assertRaises(NotImplementedError, ne.dst, dt)
171
172 def test_normal(self):
173 fo = FixedOffset(3, "Three")
174 self.assertIsInstance(fo, tzinfo)
175 for dt in datetime.now(), None:
176 self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3))
177 self.assertEqual(fo.tzname(dt), "Three")
178 self.assertEqual(fo.dst(dt), timedelta(minutes=42))
179
180 def test_pickling_base(self):
181 # There's no point to pickling tzinfo objects on their own (they
182 # carry no data), but they need to be picklable anyway else
183 # concrete subclasses can't be pickled.
184 orig = tzinfo.__new__(tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200185 self.assertIs(type(orig), tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000186 for pickler, unpickler, proto in pickle_choices:
187 green = pickler.dumps(orig, proto)
188 derived = unpickler.loads(green)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200189 self.assertIs(type(derived), tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000190
191 def test_pickling_subclass(self):
192 # Make sure we can pickle/unpickle an instance of a subclass.
193 offset = timedelta(minutes=-300)
194 for otype, args in [
195 (PicklableFixedOffset, (offset, 'cookie')),
196 (timezone, (offset,)),
197 (timezone, (offset, "EST"))]:
198 orig = otype(*args)
199 oname = orig.tzname(None)
200 self.assertIsInstance(orig, tzinfo)
201 self.assertIs(type(orig), otype)
202 self.assertEqual(orig.utcoffset(None), offset)
203 self.assertEqual(orig.tzname(None), oname)
204 for pickler, unpickler, proto in pickle_choices:
205 green = pickler.dumps(orig, proto)
206 derived = unpickler.loads(green)
207 self.assertIsInstance(derived, tzinfo)
208 self.assertIs(type(derived), otype)
209 self.assertEqual(derived.utcoffset(None), offset)
210 self.assertEqual(derived.tzname(None), oname)
211
Alexander Belopolskyc79447b2015-09-27 21:41:55 -0400212 def test_issue23600(self):
213 DSTDIFF = DSTOFFSET = timedelta(hours=1)
214
215 class UKSummerTime(tzinfo):
216 """Simple time zone which pretends to always be in summer time, since
217 that's what shows the failure.
218 """
219
220 def utcoffset(self, dt):
221 return DSTOFFSET
222
223 def dst(self, dt):
224 return DSTDIFF
225
226 def tzname(self, dt):
227 return 'UKSummerTime'
228
229 tz = UKSummerTime()
230 u = datetime(2014, 4, 26, 12, 1, tzinfo=tz)
231 t = tz.fromutc(u)
232 self.assertEqual(t - t.utcoffset(), u)
233
234
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000235class TestTimeZone(unittest.TestCase):
236
237 def setUp(self):
238 self.ACDT = timezone(timedelta(hours=9.5), 'ACDT')
239 self.EST = timezone(-timedelta(hours=5), 'EST')
240 self.DT = datetime(2010, 1, 1)
241
242 def test_str(self):
243 for tz in [self.ACDT, self.EST, timezone.utc,
244 timezone.min, timezone.max]:
245 self.assertEqual(str(tz), tz.tzname(None))
246
247 def test_repr(self):
248 datetime = datetime_module
249 for tz in [self.ACDT, self.EST, timezone.utc,
250 timezone.min, timezone.max]:
251 # test round-trip
252 tzrep = repr(tz)
253 self.assertEqual(tz, eval(tzrep))
254
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000255 def test_class_members(self):
256 limit = timedelta(hours=23, minutes=59)
257 self.assertEqual(timezone.utc.utcoffset(None), ZERO)
258 self.assertEqual(timezone.min.utcoffset(None), -limit)
259 self.assertEqual(timezone.max.utcoffset(None), limit)
260
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000261 def test_constructor(self):
Alexander Belopolsky1bcbaab2010-10-14 17:03:51 +0000262 self.assertIs(timezone.utc, timezone(timedelta(0)))
263 self.assertIsNot(timezone.utc, timezone(timedelta(0), 'UTC'))
264 self.assertEqual(timezone.utc, timezone(timedelta(0), 'UTC'))
Alexander Belopolsky018d3532017-07-31 10:26:50 -0400265 for subminute in [timedelta(microseconds=1), timedelta(seconds=1)]:
266 tz = timezone(subminute)
267 self.assertNotEqual(tz.utcoffset(None) % timedelta(minutes=1), 0)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000268 # invalid offsets
Alexander Belopolsky018d3532017-07-31 10:26:50 -0400269 for invalid in [timedelta(1, 1), timedelta(1)]:
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000270 self.assertRaises(ValueError, timezone, invalid)
271 self.assertRaises(ValueError, timezone, -invalid)
272
273 with self.assertRaises(TypeError): timezone(None)
274 with self.assertRaises(TypeError): timezone(42)
275 with self.assertRaises(TypeError): timezone(ZERO, None)
276 with self.assertRaises(TypeError): timezone(ZERO, 42)
277 with self.assertRaises(TypeError): timezone(ZERO, 'ABC', 'extra')
278
279 def test_inheritance(self):
280 self.assertIsInstance(timezone.utc, tzinfo)
281 self.assertIsInstance(self.EST, tzinfo)
282
283 def test_utcoffset(self):
284 dummy = self.DT
285 for h in [0, 1.5, 12]:
286 offset = h * HOUR
287 self.assertEqual(offset, timezone(offset).utcoffset(dummy))
288 self.assertEqual(-offset, timezone(-offset).utcoffset(dummy))
289
290 with self.assertRaises(TypeError): self.EST.utcoffset('')
291 with self.assertRaises(TypeError): self.EST.utcoffset(5)
292
293
294 def test_dst(self):
Raymond Hettinger7beae8a2011-01-06 05:34:17 +0000295 self.assertIsNone(timezone.utc.dst(self.DT))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000296
297 with self.assertRaises(TypeError): self.EST.dst('')
298 with self.assertRaises(TypeError): self.EST.dst(5)
299
300 def test_tzname(self):
Alexander Belopolsky7827a5b2015-09-06 13:07:21 -0400301 self.assertEqual('UTC', timezone.utc.tzname(None))
302 self.assertEqual('UTC', timezone(ZERO).tzname(None))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000303 self.assertEqual('UTC-05:00', timezone(-5 * HOUR).tzname(None))
304 self.assertEqual('UTC+09:30', timezone(9.5 * HOUR).tzname(None))
305 self.assertEqual('UTC-00:01', timezone(timedelta(minutes=-1)).tzname(None))
306 self.assertEqual('XYZ', timezone(-5 * HOUR, 'XYZ').tzname(None))
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +0300307 # bpo-34482: Check that surrogates are handled properly.
308 self.assertEqual('\ud800', timezone(ZERO, '\ud800').tzname(None))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000309
Alexander Belopolsky018d3532017-07-31 10:26:50 -0400310 # Sub-minute offsets:
311 self.assertEqual('UTC+01:06:40', timezone(timedelta(0, 4000)).tzname(None))
312 self.assertEqual('UTC-01:06:40',
313 timezone(-timedelta(0, 4000)).tzname(None))
314 self.assertEqual('UTC+01:06:40.000001',
315 timezone(timedelta(0, 4000, 1)).tzname(None))
316 self.assertEqual('UTC-01:06:40.000001',
317 timezone(-timedelta(0, 4000, 1)).tzname(None))
318
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000319 with self.assertRaises(TypeError): self.EST.tzname('')
320 with self.assertRaises(TypeError): self.EST.tzname(5)
321
322 def test_fromutc(self):
323 with self.assertRaises(ValueError):
324 timezone.utc.fromutc(self.DT)
325 with self.assertRaises(TypeError):
326 timezone.utc.fromutc('not datetime')
327 for tz in [self.EST, self.ACDT, Eastern]:
328 utctime = self.DT.replace(tzinfo=tz)
329 local = tz.fromutc(utctime)
330 self.assertEqual(local - utctime, tz.utcoffset(local))
331 self.assertEqual(local,
332 self.DT.replace(tzinfo=timezone.utc))
333
334 def test_comparison(self):
335 self.assertNotEqual(timezone(ZERO), timezone(HOUR))
336 self.assertEqual(timezone(HOUR), timezone(HOUR))
337 self.assertEqual(timezone(-5 * HOUR), timezone(-5 * HOUR, 'EST'))
338 with self.assertRaises(TypeError): timezone(ZERO) < timezone(ZERO)
339 self.assertIn(timezone(ZERO), {timezone(ZERO)})
Georg Brandl0085a242012-09-22 09:23:12 +0200340 self.assertTrue(timezone(ZERO) != None)
341 self.assertFalse(timezone(ZERO) == None)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000342
343 def test_aware_datetime(self):
344 # test that timezone instances can be used by datetime
345 t = datetime(1, 1, 1)
346 for tz in [timezone.min, timezone.max, timezone.utc]:
347 self.assertEqual(tz.tzname(t),
348 t.replace(tzinfo=tz).tzname())
349 self.assertEqual(tz.utcoffset(t),
350 t.replace(tzinfo=tz).utcoffset())
351 self.assertEqual(tz.dst(t),
352 t.replace(tzinfo=tz).dst())
353
Serhiy Storchakae28209f2015-11-16 11:12:58 +0200354 def test_pickle(self):
355 for tz in self.ACDT, self.EST, timezone.min, timezone.max:
356 for pickler, unpickler, proto in pickle_choices:
357 tz_copy = unpickler.loads(pickler.dumps(tz, proto))
358 self.assertEqual(tz_copy, tz)
359 tz = timezone.utc
360 for pickler, unpickler, proto in pickle_choices:
361 tz_copy = unpickler.loads(pickler.dumps(tz, proto))
362 self.assertIs(tz_copy, tz)
363
364 def test_copy(self):
365 for tz in self.ACDT, self.EST, timezone.min, timezone.max:
366 tz_copy = copy.copy(tz)
367 self.assertEqual(tz_copy, tz)
368 tz = timezone.utc
369 tz_copy = copy.copy(tz)
370 self.assertIs(tz_copy, tz)
371
372 def test_deepcopy(self):
373 for tz in self.ACDT, self.EST, timezone.min, timezone.max:
374 tz_copy = copy.deepcopy(tz)
375 self.assertEqual(tz_copy, tz)
376 tz = timezone.utc
377 tz_copy = copy.deepcopy(tz)
378 self.assertIs(tz_copy, tz)
379
380
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000381#############################################################################
Ezio Melotti85a86292013-08-17 16:57:41 +0300382# Base class for testing a particular aspect of timedelta, time, date and
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000383# datetime comparisons.
384
385class HarmlessMixedComparison:
386 # Test that __eq__ and __ne__ don't complain for mixed-type comparisons.
387
388 # Subclasses must define 'theclass', and theclass(1, 1, 1) must be a
389 # legit constructor.
390
391 def test_harmless_mixed_comparison(self):
392 me = self.theclass(1, 1, 1)
393
394 self.assertFalse(me == ())
395 self.assertTrue(me != ())
396 self.assertFalse(() == me)
397 self.assertTrue(() != me)
398
399 self.assertIn(me, [1, 20, [], me])
400 self.assertIn([], [me, 1, 20, []])
401
402 def test_harmful_mixed_comparison(self):
403 me = self.theclass(1, 1, 1)
404
405 self.assertRaises(TypeError, lambda: me < ())
406 self.assertRaises(TypeError, lambda: me <= ())
407 self.assertRaises(TypeError, lambda: me > ())
408 self.assertRaises(TypeError, lambda: me >= ())
409
410 self.assertRaises(TypeError, lambda: () < me)
411 self.assertRaises(TypeError, lambda: () <= me)
412 self.assertRaises(TypeError, lambda: () > me)
413 self.assertRaises(TypeError, lambda: () >= me)
414
415#############################################################################
416# timedelta tests
417
418class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
419
420 theclass = timedelta
421
422 def test_constructor(self):
423 eq = self.assertEqual
424 td = timedelta
425
426 # Check keyword args to constructor
427 eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0,
428 milliseconds=0, microseconds=0))
429 eq(td(1), td(days=1))
430 eq(td(0, 1), td(seconds=1))
431 eq(td(0, 0, 1), td(microseconds=1))
432 eq(td(weeks=1), td(days=7))
433 eq(td(days=1), td(hours=24))
434 eq(td(hours=1), td(minutes=60))
435 eq(td(minutes=1), td(seconds=60))
436 eq(td(seconds=1), td(milliseconds=1000))
437 eq(td(milliseconds=1), td(microseconds=1000))
438
439 # Check float args to constructor
440 eq(td(weeks=1.0/7), td(days=1))
441 eq(td(days=1.0/24), td(hours=1))
442 eq(td(hours=1.0/60), td(minutes=1))
443 eq(td(minutes=1.0/60), td(seconds=1))
444 eq(td(seconds=0.001), td(milliseconds=1))
445 eq(td(milliseconds=0.001), td(microseconds=1))
446
447 def test_computations(self):
448 eq = self.assertEqual
449 td = timedelta
450
451 a = td(7) # One week
452 b = td(0, 60) # One minute
453 c = td(0, 0, 1000) # One millisecond
454 eq(a+b+c, td(7, 60, 1000))
455 eq(a-b, td(6, 24*3600 - 60))
456 eq(b.__rsub__(a), td(6, 24*3600 - 60))
457 eq(-a, td(-7))
458 eq(+a, td(7))
459 eq(-b, td(-1, 24*3600 - 60))
460 eq(-c, td(-1, 24*3600 - 1, 999000))
461 eq(abs(a), a)
462 eq(abs(-a), a)
463 eq(td(6, 24*3600), a)
464 eq(td(0, 0, 60*1000000), b)
465 eq(a*10, td(70))
466 eq(a*10, 10*a)
467 eq(a*10, 10*a)
468 eq(b*10, td(0, 600))
469 eq(10*b, td(0, 600))
470 eq(b*10, td(0, 600))
471 eq(c*10, td(0, 0, 10000))
472 eq(10*c, td(0, 0, 10000))
473 eq(c*10, td(0, 0, 10000))
474 eq(a*-1, -a)
475 eq(b*-2, -b-b)
476 eq(c*-2, -c+-c)
477 eq(b*(60*24), (b*60)*24)
478 eq(b*(60*24), (60*b)*24)
479 eq(c*1000, td(0, 1))
480 eq(1000*c, td(0, 1))
481 eq(a//7, td(1))
482 eq(b//10, td(0, 6))
483 eq(c//1000, td(0, 0, 1))
484 eq(a//10, td(0, 7*24*360))
485 eq(a//3600000, td(0, 0, 7*24*1000))
486 eq(a/0.5, td(14))
487 eq(b/0.5, td(0, 120))
488 eq(a/7, td(1))
489 eq(b/10, td(0, 6))
490 eq(c/1000, td(0, 0, 1))
491 eq(a/10, td(0, 7*24*360))
492 eq(a/3600000, td(0, 0, 7*24*1000))
493
494 # Multiplication by float
495 us = td(microseconds=1)
496 eq((3*us) * 0.5, 2*us)
497 eq((5*us) * 0.5, 2*us)
498 eq(0.5 * (3*us), 2*us)
499 eq(0.5 * (5*us), 2*us)
500 eq((-3*us) * 0.5, -2*us)
501 eq((-5*us) * 0.5, -2*us)
502
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -0500503 # Issue #23521
504 eq(td(seconds=1) * 0.123456, td(microseconds=123456))
505 eq(td(seconds=1) * 0.6112295, td(microseconds=611229))
506
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000507 # Division by int and float
508 eq((3*us) / 2, 2*us)
509 eq((5*us) / 2, 2*us)
510 eq((-3*us) / 2.0, -2*us)
511 eq((-5*us) / 2.0, -2*us)
512 eq((3*us) / -2, -2*us)
513 eq((5*us) / -2, -2*us)
514 eq((3*us) / -2.0, -2*us)
515 eq((5*us) / -2.0, -2*us)
516 for i in range(-10, 10):
517 eq((i*us/3)//us, round(i/3))
518 for i in range(-10, 10):
519 eq((i*us/-3)//us, round(i/-3))
520
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -0500521 # Issue #23521
522 eq(td(seconds=1) / (1 / 0.6112295), td(microseconds=611229))
523
Alexander Belopolskyb6f5ec72011-04-05 20:07:38 -0400524 # Issue #11576
525 eq(td(999999999, 86399, 999999) - td(999999999, 86399, 999998),
526 td(0, 0, 1))
527 eq(td(999999999, 1, 1) - td(999999999, 1, 0),
528 td(0, 0, 1))
529
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000530 def test_disallowed_computations(self):
531 a = timedelta(42)
532
533 # Add/sub ints or floats should be illegal
534 for i in 1, 1.0:
535 self.assertRaises(TypeError, lambda: a+i)
536 self.assertRaises(TypeError, lambda: a-i)
537 self.assertRaises(TypeError, lambda: i+a)
538 self.assertRaises(TypeError, lambda: i-a)
539
540 # Division of int by timedelta doesn't make sense.
541 # Division by zero doesn't make sense.
542 zero = 0
543 self.assertRaises(TypeError, lambda: zero // a)
544 self.assertRaises(ZeroDivisionError, lambda: a // zero)
545 self.assertRaises(ZeroDivisionError, lambda: a / zero)
546 self.assertRaises(ZeroDivisionError, lambda: a / 0.0)
547 self.assertRaises(TypeError, lambda: a / '')
548
Eric Smith3ab08ca2010-12-04 15:17:38 +0000549 @support.requires_IEEE_754
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000550 def test_disallowed_special(self):
551 a = timedelta(42)
552 self.assertRaises(ValueError, a.__mul__, NAN)
553 self.assertRaises(ValueError, a.__truediv__, NAN)
554
555 def test_basic_attributes(self):
556 days, seconds, us = 1, 7, 31
557 td = timedelta(days, seconds, us)
558 self.assertEqual(td.days, days)
559 self.assertEqual(td.seconds, seconds)
560 self.assertEqual(td.microseconds, us)
561
562 def test_total_seconds(self):
563 td = timedelta(days=365)
564 self.assertEqual(td.total_seconds(), 31536000.0)
565 for total_seconds in [123456.789012, -123456.789012, 0.123456, 0, 1e6]:
566 td = timedelta(seconds=total_seconds)
567 self.assertEqual(td.total_seconds(), total_seconds)
568 # Issue8644: Test that td.total_seconds() has the same
569 # accuracy as td / timedelta(seconds=1).
570 for ms in [-1, -2, -123]:
571 td = timedelta(microseconds=ms)
572 self.assertEqual(td.total_seconds(), td / timedelta(seconds=1))
573
574 def test_carries(self):
575 t1 = timedelta(days=100,
576 weeks=-7,
577 hours=-24*(100-49),
578 minutes=-3,
579 seconds=12,
580 microseconds=(3*60 - 12) * 1e6 + 1)
581 t2 = timedelta(microseconds=1)
582 self.assertEqual(t1, t2)
583
584 def test_hash_equality(self):
585 t1 = timedelta(days=100,
586 weeks=-7,
587 hours=-24*(100-49),
588 minutes=-3,
589 seconds=12,
590 microseconds=(3*60 - 12) * 1000000)
591 t2 = timedelta()
592 self.assertEqual(hash(t1), hash(t2))
593
594 t1 += timedelta(weeks=7)
595 t2 += timedelta(days=7*7)
596 self.assertEqual(t1, t2)
597 self.assertEqual(hash(t1), hash(t2))
598
599 d = {t1: 1}
600 d[t2] = 2
601 self.assertEqual(len(d), 1)
602 self.assertEqual(d[t1], 2)
603
604 def test_pickling(self):
605 args = 12, 34, 56
606 orig = timedelta(*args)
607 for pickler, unpickler, proto in pickle_choices:
608 green = pickler.dumps(orig, proto)
609 derived = unpickler.loads(green)
610 self.assertEqual(orig, derived)
611
612 def test_compare(self):
613 t1 = timedelta(2, 3, 4)
614 t2 = timedelta(2, 3, 4)
615 self.assertEqual(t1, t2)
616 self.assertTrue(t1 <= t2)
617 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200618 self.assertFalse(t1 != t2)
619 self.assertFalse(t1 < t2)
620 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000621
622 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
623 t2 = timedelta(*args) # this is larger than t1
624 self.assertTrue(t1 < t2)
625 self.assertTrue(t2 > t1)
626 self.assertTrue(t1 <= t2)
627 self.assertTrue(t2 >= t1)
628 self.assertTrue(t1 != t2)
629 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200630 self.assertFalse(t1 == t2)
631 self.assertFalse(t2 == t1)
632 self.assertFalse(t1 > t2)
633 self.assertFalse(t2 < t1)
634 self.assertFalse(t1 >= t2)
635 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000636
637 for badarg in OTHERSTUFF:
638 self.assertEqual(t1 == badarg, False)
639 self.assertEqual(t1 != badarg, True)
640 self.assertEqual(badarg == t1, False)
641 self.assertEqual(badarg != t1, True)
642
643 self.assertRaises(TypeError, lambda: t1 <= badarg)
644 self.assertRaises(TypeError, lambda: t1 < badarg)
645 self.assertRaises(TypeError, lambda: t1 > badarg)
646 self.assertRaises(TypeError, lambda: t1 >= badarg)
647 self.assertRaises(TypeError, lambda: badarg <= t1)
648 self.assertRaises(TypeError, lambda: badarg < t1)
649 self.assertRaises(TypeError, lambda: badarg > t1)
650 self.assertRaises(TypeError, lambda: badarg >= t1)
651
652 def test_str(self):
653 td = timedelta
654 eq = self.assertEqual
655
656 eq(str(td(1)), "1 day, 0:00:00")
657 eq(str(td(-1)), "-1 day, 0:00:00")
658 eq(str(td(2)), "2 days, 0:00:00")
659 eq(str(td(-2)), "-2 days, 0:00:00")
660
661 eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
662 eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
663 eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
664 "-210 days, 23:12:34")
665
666 eq(str(td(milliseconds=1)), "0:00:00.001000")
667 eq(str(td(microseconds=3)), "0:00:00.000003")
668
669 eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
670 microseconds=999999)),
671 "999999999 days, 23:59:59.999999")
672
673 def test_repr(self):
674 name = 'datetime.' + self.theclass.__name__
675 self.assertEqual(repr(self.theclass(1)),
Utkarsh Upadhyaycc5a65c2017-07-25 23:51:33 +0200676 "%s(days=1)" % name)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000677 self.assertEqual(repr(self.theclass(10, 2)),
Utkarsh Upadhyaycc5a65c2017-07-25 23:51:33 +0200678 "%s(days=10, seconds=2)" % name)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000679 self.assertEqual(repr(self.theclass(-10, 2, 400000)),
Utkarsh Upadhyaycc5a65c2017-07-25 23:51:33 +0200680 "%s(days=-10, seconds=2, microseconds=400000)" % name)
681 self.assertEqual(repr(self.theclass(seconds=60)),
682 "%s(seconds=60)" % name)
683 self.assertEqual(repr(self.theclass()),
684 "%s(0)" % name)
685 self.assertEqual(repr(self.theclass(microseconds=100)),
686 "%s(microseconds=100)" % name)
687 self.assertEqual(repr(self.theclass(days=1, microseconds=100)),
688 "%s(days=1, microseconds=100)" % name)
689 self.assertEqual(repr(self.theclass(seconds=1, microseconds=100)),
690 "%s(seconds=1, microseconds=100)" % name)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000691
692 def test_roundtrip(self):
693 for td in (timedelta(days=999999999, hours=23, minutes=59,
694 seconds=59, microseconds=999999),
695 timedelta(days=-999999999),
696 timedelta(days=-999999999, seconds=1),
697 timedelta(days=1, seconds=2, microseconds=3)):
698
699 # Verify td -> string -> td identity.
700 s = repr(td)
701 self.assertTrue(s.startswith('datetime.'))
702 s = s[9:]
703 td2 = eval(s)
704 self.assertEqual(td, td2)
705
706 # Verify identity via reconstructing from pieces.
707 td2 = timedelta(td.days, td.seconds, td.microseconds)
708 self.assertEqual(td, td2)
709
710 def test_resolution_info(self):
711 self.assertIsInstance(timedelta.min, timedelta)
712 self.assertIsInstance(timedelta.max, timedelta)
713 self.assertIsInstance(timedelta.resolution, timedelta)
714 self.assertTrue(timedelta.max > timedelta.min)
715 self.assertEqual(timedelta.min, timedelta(-999999999))
716 self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
717 self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
718
719 def test_overflow(self):
720 tiny = timedelta.resolution
721
722 td = timedelta.min + tiny
723 td -= tiny # no problem
724 self.assertRaises(OverflowError, td.__sub__, tiny)
725 self.assertRaises(OverflowError, td.__add__, -tiny)
726
727 td = timedelta.max - tiny
728 td += tiny # no problem
729 self.assertRaises(OverflowError, td.__add__, tiny)
730 self.assertRaises(OverflowError, td.__sub__, -tiny)
731
732 self.assertRaises(OverflowError, lambda: -timedelta.max)
733
734 day = timedelta(1)
735 self.assertRaises(OverflowError, day.__mul__, 10**9)
736 self.assertRaises(OverflowError, day.__mul__, 1e9)
737 self.assertRaises(OverflowError, day.__truediv__, 1e-20)
738 self.assertRaises(OverflowError, day.__truediv__, 1e-10)
739 self.assertRaises(OverflowError, day.__truediv__, 9e-10)
740
Eric Smith3ab08ca2010-12-04 15:17:38 +0000741 @support.requires_IEEE_754
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000742 def _test_overflow_special(self):
743 day = timedelta(1)
744 self.assertRaises(OverflowError, day.__mul__, INF)
745 self.assertRaises(OverflowError, day.__mul__, -INF)
746
747 def test_microsecond_rounding(self):
748 td = timedelta
749 eq = self.assertEqual
750
751 # Single-field rounding.
752 eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
753 eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
Victor Stinner69cc4872015-09-08 23:58:54 +0200754 eq(td(milliseconds=0.5/1000), td(microseconds=0))
755 eq(td(milliseconds=-0.5/1000), td(microseconds=-0))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000756 eq(td(milliseconds=0.6/1000), td(microseconds=1))
757 eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
Victor Stinner69cc4872015-09-08 23:58:54 +0200758 eq(td(milliseconds=1.5/1000), td(microseconds=2))
759 eq(td(milliseconds=-1.5/1000), td(microseconds=-2))
760 eq(td(seconds=0.5/10**6), td(microseconds=0))
761 eq(td(seconds=-0.5/10**6), td(microseconds=-0))
762 eq(td(seconds=1/2**7), td(microseconds=7812))
763 eq(td(seconds=-1/2**7), td(microseconds=-7812))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000764
765 # Rounding due to contributions from more than one field.
766 us_per_hour = 3600e6
767 us_per_day = us_per_hour * 24
768 eq(td(days=.4/us_per_day), td(0))
769 eq(td(hours=.2/us_per_hour), td(0))
Victor Stinnercd5d7652015-09-09 01:09:21 +0200770 eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000771
772 eq(td(days=-.4/us_per_day), td(0))
773 eq(td(hours=-.2/us_per_hour), td(0))
774 eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
775
Victor Stinner69cc4872015-09-08 23:58:54 +0200776 # Test for a patch in Issue 8860
777 eq(td(microseconds=0.5), 0.5*td(microseconds=1.0))
778 eq(td(microseconds=0.5)//td.resolution, 0.5*td.resolution//td.resolution)
779
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000780 def test_massive_normalization(self):
781 td = timedelta(microseconds=-1)
782 self.assertEqual((td.days, td.seconds, td.microseconds),
783 (-1, 24*3600-1, 999999))
784
785 def test_bool(self):
786 self.assertTrue(timedelta(1))
787 self.assertTrue(timedelta(0, 1))
788 self.assertTrue(timedelta(0, 0, 1))
789 self.assertTrue(timedelta(microseconds=1))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200790 self.assertFalse(timedelta(0))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000791
792 def test_subclass_timedelta(self):
793
794 class T(timedelta):
795 @staticmethod
796 def from_td(td):
797 return T(td.days, td.seconds, td.microseconds)
798
799 def as_hours(self):
800 sum = (self.days * 24 +
801 self.seconds / 3600.0 +
802 self.microseconds / 3600e6)
803 return round(sum)
804
805 t1 = T(days=1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200806 self.assertIs(type(t1), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000807 self.assertEqual(t1.as_hours(), 24)
808
809 t2 = T(days=-1, seconds=-3600)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200810 self.assertIs(type(t2), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000811 self.assertEqual(t2.as_hours(), -25)
812
813 t3 = t1 + t2
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200814 self.assertIs(type(t3), timedelta)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000815 t4 = T.from_td(t3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200816 self.assertIs(type(t4), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000817 self.assertEqual(t3.days, t4.days)
818 self.assertEqual(t3.seconds, t4.seconds)
819 self.assertEqual(t3.microseconds, t4.microseconds)
820 self.assertEqual(str(t3), str(t4))
821 self.assertEqual(t4.as_hours(), -1)
822
Paul Ganssle89427cd2019-02-04 14:42:04 -0500823 def test_subclass_date(self):
824 class DateSubclass(date):
825 pass
826
827 d1 = DateSubclass(2018, 1, 5)
828 td = timedelta(days=1)
829
830 tests = [
831 ('add', lambda d, t: d + t, DateSubclass(2018, 1, 6)),
832 ('radd', lambda d, t: t + d, DateSubclass(2018, 1, 6)),
833 ('sub', lambda d, t: d - t, DateSubclass(2018, 1, 4)),
834 ]
835
836 for name, func, expected in tests:
837 with self.subTest(name):
838 act = func(d1, td)
839 self.assertEqual(act, expected)
840 self.assertIsInstance(act, DateSubclass)
841
842 def test_subclass_datetime(self):
843 class DateTimeSubclass(datetime):
844 pass
845
846 d1 = DateTimeSubclass(2018, 1, 5, 12, 30)
847 td = timedelta(days=1, minutes=30)
848
849 tests = [
850 ('add', lambda d, t: d + t, DateTimeSubclass(2018, 1, 6, 13)),
851 ('radd', lambda d, t: t + d, DateTimeSubclass(2018, 1, 6, 13)),
852 ('sub', lambda d, t: d - t, DateTimeSubclass(2018, 1, 4, 12)),
853 ]
854
855 for name, func, expected in tests:
856 with self.subTest(name):
857 act = func(d1, td)
858 self.assertEqual(act, expected)
859 self.assertIsInstance(act, DateTimeSubclass)
860
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000861 def test_division(self):
862 t = timedelta(hours=1, minutes=24, seconds=19)
863 second = timedelta(seconds=1)
864 self.assertEqual(t / second, 5059.0)
865 self.assertEqual(t // second, 5059)
866
867 t = timedelta(minutes=2, seconds=30)
868 minute = timedelta(minutes=1)
869 self.assertEqual(t / minute, 2.5)
870 self.assertEqual(t // minute, 2)
871
872 zerotd = timedelta(0)
873 self.assertRaises(ZeroDivisionError, truediv, t, zerotd)
874 self.assertRaises(ZeroDivisionError, floordiv, t, zerotd)
875
876 # self.assertRaises(TypeError, truediv, t, 2)
877 # note: floor division of a timedelta by an integer *is*
878 # currently permitted.
879
880 def test_remainder(self):
881 t = timedelta(minutes=2, seconds=30)
882 minute = timedelta(minutes=1)
883 r = t % minute
884 self.assertEqual(r, timedelta(seconds=30))
885
886 t = timedelta(minutes=-2, seconds=30)
887 r = t % minute
888 self.assertEqual(r, timedelta(seconds=30))
889
890 zerotd = timedelta(0)
891 self.assertRaises(ZeroDivisionError, mod, t, zerotd)
892
893 self.assertRaises(TypeError, mod, t, 10)
894
895 def test_divmod(self):
896 t = timedelta(minutes=2, seconds=30)
897 minute = timedelta(minutes=1)
898 q, r = divmod(t, minute)
899 self.assertEqual(q, 2)
900 self.assertEqual(r, timedelta(seconds=30))
901
902 t = timedelta(minutes=-2, seconds=30)
903 q, r = divmod(t, minute)
904 self.assertEqual(q, -2)
905 self.assertEqual(r, timedelta(seconds=30))
906
907 zerotd = timedelta(0)
908 self.assertRaises(ZeroDivisionError, divmod, t, zerotd)
909
910 self.assertRaises(TypeError, divmod, t, 10)
911
Oren Milman865e4b42017-09-19 15:58:11 +0300912 def test_issue31293(self):
913 # The interpreter shouldn't crash in case a timedelta is divided or
914 # multiplied by a float with a bad as_integer_ratio() method.
915 def get_bad_float(bad_ratio):
916 class BadFloat(float):
917 def as_integer_ratio(self):
918 return bad_ratio
919 return BadFloat()
920
921 with self.assertRaises(TypeError):
922 timedelta() / get_bad_float(1 << 1000)
923 with self.assertRaises(TypeError):
924 timedelta() * get_bad_float(1 << 1000)
925
926 for bad_ratio in [(), (42, ), (1, 2, 3)]:
927 with self.assertRaises(ValueError):
928 timedelta() / get_bad_float(bad_ratio)
929 with self.assertRaises(ValueError):
930 timedelta() * get_bad_float(bad_ratio)
931
Serhiy Storchaka4ffd4652017-10-23 17:12:28 +0300932 def test_issue31752(self):
933 # The interpreter shouldn't crash because divmod() returns negative
934 # remainder.
935 class BadInt(int):
936 def __mul__(self, other):
937 return Prod()
Serhiy Storchaka3ec0f492018-11-20 20:41:09 +0200938 def __rmul__(self, other):
939 return Prod()
940 def __floordiv__(self, other):
941 return Prod()
942 def __rfloordiv__(self, other):
943 return Prod()
Serhiy Storchaka4ffd4652017-10-23 17:12:28 +0300944
945 class Prod:
Serhiy Storchaka3ec0f492018-11-20 20:41:09 +0200946 def __add__(self, other):
947 return Sum()
Serhiy Storchaka4ffd4652017-10-23 17:12:28 +0300948 def __radd__(self, other):
949 return Sum()
950
951 class Sum(int):
952 def __divmod__(self, other):
Serhiy Storchaka3ec0f492018-11-20 20:41:09 +0200953 return divmodresult
Serhiy Storchaka4ffd4652017-10-23 17:12:28 +0300954
Serhiy Storchaka3ec0f492018-11-20 20:41:09 +0200955 for divmodresult in [None, (), (0, 1, 2), (0, -1)]:
956 with self.subTest(divmodresult=divmodresult):
957 # The following examples should not crash.
958 try:
959 timedelta(microseconds=BadInt(1))
960 except TypeError:
961 pass
962 try:
963 timedelta(hours=BadInt(1))
964 except TypeError:
965 pass
966 try:
967 timedelta(weeks=BadInt(1))
968 except (TypeError, ValueError):
969 pass
970 try:
971 timedelta(1) * BadInt(1)
972 except (TypeError, ValueError):
973 pass
974 try:
975 BadInt(1) * timedelta(1)
976 except TypeError:
977 pass
978 try:
979 timedelta(1) // BadInt(1)
980 except TypeError:
981 pass
Serhiy Storchaka4ffd4652017-10-23 17:12:28 +0300982
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000983
984#############################################################################
985# date tests
986
987class TestDateOnly(unittest.TestCase):
988 # Tests here won't pass if also run on datetime objects, so don't
989 # subclass this to test datetimes too.
990
991 def test_delta_non_days_ignored(self):
992 dt = date(2000, 1, 2)
993 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
994 microseconds=5)
995 days = timedelta(delta.days)
996 self.assertEqual(days, timedelta(1))
997
998 dt2 = dt + delta
999 self.assertEqual(dt2, dt + days)
1000
1001 dt2 = delta + dt
1002 self.assertEqual(dt2, dt + days)
1003
1004 dt2 = dt - delta
1005 self.assertEqual(dt2, dt - days)
1006
1007 delta = -delta
1008 days = timedelta(delta.days)
1009 self.assertEqual(days, timedelta(-2))
1010
1011 dt2 = dt + delta
1012 self.assertEqual(dt2, dt + days)
1013
1014 dt2 = delta + dt
1015 self.assertEqual(dt2, dt + days)
1016
1017 dt2 = dt - delta
1018 self.assertEqual(dt2, dt - days)
1019
1020class SubclassDate(date):
1021 sub_var = 1
1022
1023class TestDate(HarmlessMixedComparison, unittest.TestCase):
1024 # Tests here should pass for both dates and datetimes, except for a
1025 # few tests that TestDateTime overrides.
1026
1027 theclass = date
1028
1029 def test_basic_attributes(self):
1030 dt = self.theclass(2002, 3, 1)
1031 self.assertEqual(dt.year, 2002)
1032 self.assertEqual(dt.month, 3)
1033 self.assertEqual(dt.day, 1)
1034
1035 def test_roundtrip(self):
1036 for dt in (self.theclass(1, 2, 3),
1037 self.theclass.today()):
1038 # Verify dt -> string -> date identity.
1039 s = repr(dt)
1040 self.assertTrue(s.startswith('datetime.'))
1041 s = s[9:]
1042 dt2 = eval(s)
1043 self.assertEqual(dt, dt2)
1044
1045 # Verify identity via reconstructing from pieces.
1046 dt2 = self.theclass(dt.year, dt.month, dt.day)
1047 self.assertEqual(dt, dt2)
1048
1049 def test_ordinal_conversions(self):
1050 # Check some fixed values.
1051 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
1052 (1, 12, 31, 365),
1053 (2, 1, 1, 366),
1054 # first example from "Calendrical Calculations"
1055 (1945, 11, 12, 710347)]:
1056 d = self.theclass(y, m, d)
1057 self.assertEqual(n, d.toordinal())
1058 fromord = self.theclass.fromordinal(n)
1059 self.assertEqual(d, fromord)
1060 if hasattr(fromord, "hour"):
1061 # if we're checking something fancier than a date, verify
1062 # the extra fields have been zeroed out
1063 self.assertEqual(fromord.hour, 0)
1064 self.assertEqual(fromord.minute, 0)
1065 self.assertEqual(fromord.second, 0)
1066 self.assertEqual(fromord.microsecond, 0)
1067
1068 # Check first and last days of year spottily across the whole
1069 # range of years supported.
1070 for year in range(MINYEAR, MAXYEAR+1, 7):
1071 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
1072 d = self.theclass(year, 1, 1)
1073 n = d.toordinal()
1074 d2 = self.theclass.fromordinal(n)
1075 self.assertEqual(d, d2)
1076 # Verify that moving back a day gets to the end of year-1.
1077 if year > 1:
1078 d = self.theclass.fromordinal(n-1)
1079 d2 = self.theclass(year-1, 12, 31)
1080 self.assertEqual(d, d2)
1081 self.assertEqual(d2.toordinal(), n-1)
1082
1083 # Test every day in a leap-year and a non-leap year.
1084 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
1085 for year, isleap in (2000, True), (2002, False):
1086 n = self.theclass(year, 1, 1).toordinal()
1087 for month, maxday in zip(range(1, 13), dim):
1088 if month == 2 and isleap:
1089 maxday += 1
1090 for day in range(1, maxday+1):
1091 d = self.theclass(year, month, day)
1092 self.assertEqual(d.toordinal(), n)
1093 self.assertEqual(d, self.theclass.fromordinal(n))
1094 n += 1
1095
1096 def test_extreme_ordinals(self):
1097 a = self.theclass.min
1098 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
1099 aord = a.toordinal()
1100 b = a.fromordinal(aord)
1101 self.assertEqual(a, b)
1102
1103 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
1104
1105 b = a + timedelta(days=1)
1106 self.assertEqual(b.toordinal(), aord + 1)
1107 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
1108
1109 a = self.theclass.max
1110 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
1111 aord = a.toordinal()
1112 b = a.fromordinal(aord)
1113 self.assertEqual(a, b)
1114
1115 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
1116
1117 b = a - timedelta(days=1)
1118 self.assertEqual(b.toordinal(), aord - 1)
1119 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
1120
1121 def test_bad_constructor_arguments(self):
1122 # bad years
1123 self.theclass(MINYEAR, 1, 1) # no exception
1124 self.theclass(MAXYEAR, 1, 1) # no exception
1125 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1126 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1127 # bad months
1128 self.theclass(2000, 1, 1) # no exception
1129 self.theclass(2000, 12, 1) # no exception
1130 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1131 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1132 # bad days
1133 self.theclass(2000, 2, 29) # no exception
1134 self.theclass(2004, 2, 29) # no exception
1135 self.theclass(2400, 2, 29) # no exception
1136 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1137 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1138 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1139 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1140 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1141 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1142
1143 def test_hash_equality(self):
1144 d = self.theclass(2000, 12, 31)
1145 # same thing
1146 e = self.theclass(2000, 12, 31)
1147 self.assertEqual(d, e)
1148 self.assertEqual(hash(d), hash(e))
1149
1150 dic = {d: 1}
1151 dic[e] = 2
1152 self.assertEqual(len(dic), 1)
1153 self.assertEqual(dic[d], 2)
1154 self.assertEqual(dic[e], 2)
1155
1156 d = self.theclass(2001, 1, 1)
1157 # same thing
1158 e = self.theclass(2001, 1, 1)
1159 self.assertEqual(d, e)
1160 self.assertEqual(hash(d), hash(e))
1161
1162 dic = {d: 1}
1163 dic[e] = 2
1164 self.assertEqual(len(dic), 1)
1165 self.assertEqual(dic[d], 2)
1166 self.assertEqual(dic[e], 2)
1167
1168 def test_computations(self):
1169 a = self.theclass(2002, 1, 31)
1170 b = self.theclass(1956, 1, 31)
1171 c = self.theclass(2001,2,1)
1172
1173 diff = a-b
1174 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1175 self.assertEqual(diff.seconds, 0)
1176 self.assertEqual(diff.microseconds, 0)
1177
1178 day = timedelta(1)
1179 week = timedelta(7)
1180 a = self.theclass(2002, 3, 2)
1181 self.assertEqual(a + day, self.theclass(2002, 3, 3))
1182 self.assertEqual(day + a, self.theclass(2002, 3, 3))
1183 self.assertEqual(a - day, self.theclass(2002, 3, 1))
1184 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
1185 self.assertEqual(a + week, self.theclass(2002, 3, 9))
1186 self.assertEqual(a - week, self.theclass(2002, 2, 23))
1187 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
1188 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
1189 self.assertEqual((a + week) - a, week)
1190 self.assertEqual((a + day) - a, day)
1191 self.assertEqual((a - week) - a, -week)
1192 self.assertEqual((a - day) - a, -day)
1193 self.assertEqual(a - (a + week), -week)
1194 self.assertEqual(a - (a + day), -day)
1195 self.assertEqual(a - (a - week), week)
1196 self.assertEqual(a - (a - day), day)
1197 self.assertEqual(c - (c - day), day)
1198
1199 # Add/sub ints or floats should be illegal
1200 for i in 1, 1.0:
1201 self.assertRaises(TypeError, lambda: a+i)
1202 self.assertRaises(TypeError, lambda: a-i)
1203 self.assertRaises(TypeError, lambda: i+a)
1204 self.assertRaises(TypeError, lambda: i-a)
1205
1206 # delta - date is senseless.
1207 self.assertRaises(TypeError, lambda: day - a)
1208 # mixing date and (delta or date) via * or // is senseless
1209 self.assertRaises(TypeError, lambda: day * a)
1210 self.assertRaises(TypeError, lambda: a * day)
1211 self.assertRaises(TypeError, lambda: day // a)
1212 self.assertRaises(TypeError, lambda: a // day)
1213 self.assertRaises(TypeError, lambda: a * a)
1214 self.assertRaises(TypeError, lambda: a // a)
1215 # date + date is senseless
1216 self.assertRaises(TypeError, lambda: a + a)
1217
1218 def test_overflow(self):
1219 tiny = self.theclass.resolution
1220
1221 for delta in [tiny, timedelta(1), timedelta(2)]:
1222 dt = self.theclass.min + delta
1223 dt -= delta # no problem
1224 self.assertRaises(OverflowError, dt.__sub__, delta)
1225 self.assertRaises(OverflowError, dt.__add__, -delta)
1226
1227 dt = self.theclass.max - delta
1228 dt += delta # no problem
1229 self.assertRaises(OverflowError, dt.__add__, delta)
1230 self.assertRaises(OverflowError, dt.__sub__, -delta)
1231
1232 def test_fromtimestamp(self):
1233 import time
1234
1235 # Try an arbitrary fixed value.
1236 year, month, day = 1999, 9, 19
1237 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
1238 d = self.theclass.fromtimestamp(ts)
1239 self.assertEqual(d.year, year)
1240 self.assertEqual(d.month, month)
1241 self.assertEqual(d.day, day)
1242
1243 def test_insane_fromtimestamp(self):
1244 # It's possible that some platform maps time_t to double,
1245 # and that this test will fail there. This test should
1246 # exempt such platforms (provided they return reasonable
1247 # results!).
1248 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001249 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001250 insane)
1251
1252 def test_today(self):
1253 import time
1254
1255 # We claim that today() is like fromtimestamp(time.time()), so
1256 # prove it.
1257 for dummy in range(3):
1258 today = self.theclass.today()
1259 ts = time.time()
1260 todayagain = self.theclass.fromtimestamp(ts)
1261 if today == todayagain:
1262 break
1263 # There are several legit reasons that could fail:
1264 # 1. It recently became midnight, between the today() and the
1265 # time() calls.
1266 # 2. The platform time() has such fine resolution that we'll
1267 # never get the same value twice.
1268 # 3. The platform time() has poor resolution, and we just
1269 # happened to call today() right before a resolution quantum
1270 # boundary.
1271 # 4. The system clock got fiddled between calls.
1272 # In any case, wait a little while and try again.
1273 time.sleep(0.1)
1274
1275 # It worked or it didn't. If it didn't, assume it's reason #2, and
1276 # let the test pass if they're within half a second of each other.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001277 if today != todayagain:
1278 self.assertAlmostEqual(todayagain, today,
1279 delta=timedelta(seconds=0.5))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001280
1281 def test_weekday(self):
1282 for i in range(7):
1283 # March 4, 2002 is a Monday
1284 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
1285 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
1286 # January 2, 1956 is a Monday
1287 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
1288 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
1289
1290 def test_isocalendar(self):
1291 # Check examples from
1292 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1293 for i in range(7):
1294 d = self.theclass(2003, 12, 22+i)
1295 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
1296 d = self.theclass(2003, 12, 29) + timedelta(i)
1297 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
1298 d = self.theclass(2004, 1, 5+i)
1299 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
1300 d = self.theclass(2009, 12, 21+i)
1301 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
1302 d = self.theclass(2009, 12, 28) + timedelta(i)
1303 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
1304 d = self.theclass(2010, 1, 4+i)
1305 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
1306
1307 def test_iso_long_years(self):
1308 # Calculate long ISO years and compare to table from
1309 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1310 ISO_LONG_YEARS_TABLE = """
1311 4 32 60 88
1312 9 37 65 93
1313 15 43 71 99
1314 20 48 76
1315 26 54 82
1316
1317 105 133 161 189
1318 111 139 167 195
1319 116 144 172
1320 122 150 178
1321 128 156 184
1322
1323 201 229 257 285
1324 207 235 263 291
1325 212 240 268 296
1326 218 246 274
1327 224 252 280
1328
1329 303 331 359 387
1330 308 336 364 392
1331 314 342 370 398
1332 320 348 376
1333 325 353 381
1334 """
1335 iso_long_years = sorted(map(int, ISO_LONG_YEARS_TABLE.split()))
1336 L = []
1337 for i in range(400):
1338 d = self.theclass(2000+i, 12, 31)
1339 d1 = self.theclass(1600+i, 12, 31)
1340 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
1341 if d.isocalendar()[1] == 53:
1342 L.append(i)
1343 self.assertEqual(L, iso_long_years)
1344
1345 def test_isoformat(self):
1346 t = self.theclass(2, 3, 2)
1347 self.assertEqual(t.isoformat(), "0002-03-02")
1348
1349 def test_ctime(self):
1350 t = self.theclass(2002, 3, 2)
1351 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
1352
1353 def test_strftime(self):
1354 t = self.theclass(2005, 3, 2)
1355 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
1356 self.assertEqual(t.strftime(""), "") # SF bug #761337
1357 self.assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784
1358
1359 self.assertRaises(TypeError, t.strftime) # needs an arg
1360 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
1361 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
1362
1363 # test that unicode input is allowed (issue 2782)
1364 self.assertEqual(t.strftime("%m"), "03")
1365
1366 # A naive object replaces %z and %Z w/ empty strings.
1367 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1368
1369 #make sure that invalid format specifiers are handled correctly
1370 #self.assertRaises(ValueError, t.strftime, "%e")
1371 #self.assertRaises(ValueError, t.strftime, "%")
1372 #self.assertRaises(ValueError, t.strftime, "%#")
1373
1374 #oh well, some systems just ignore those invalid ones.
Martin Panter46f50722016-05-26 05:35:26 +00001375 #at least, exercise them to make sure that no crashes
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001376 #are generated
1377 for f in ["%e", "%", "%#"]:
1378 try:
1379 t.strftime(f)
1380 except ValueError:
1381 pass
1382
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03001383 # bpo-34482: Check that surrogates don't cause a crash.
1384 try:
1385 t.strftime('%y\ud800%m')
1386 except UnicodeEncodeError:
1387 pass
1388
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001389 #check that this standard extension works
1390 t.strftime("%f")
1391
MichaelSaah454b3d42019-01-14 05:23:39 -05001392 def test_strftime_trailing_percent(self):
1393 # bpo-35066: make sure trailing '%' doesn't cause
1394 # datetime's strftime to complain
1395 t = self.theclass(2005, 3, 2)
1396 try:
1397 _time.strftime('%')
1398 except ValueError:
1399 self.skipTest('time module does not support trailing %')
1400 self.assertEqual(t.strftime('%'), '%')
1401 self.assertEqual(t.strftime("m:%m d:%d y:%y %"), "m:03 d:02 y:05 %")
1402
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001403 def test_format(self):
1404 dt = self.theclass(2007, 9, 10)
1405 self.assertEqual(dt.__format__(''), str(dt))
1406
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02001407 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001408 dt.__format__(123)
1409
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001410 # check that a derived class's __str__() gets called
1411 class A(self.theclass):
1412 def __str__(self):
1413 return 'A'
1414 a = A(2007, 9, 10)
1415 self.assertEqual(a.__format__(''), 'A')
1416
1417 # check that a derived class's strftime gets called
1418 class B(self.theclass):
1419 def strftime(self, format_spec):
1420 return 'B'
1421 b = B(2007, 9, 10)
1422 self.assertEqual(b.__format__(''), str(dt))
1423
1424 for fmt in ["m:%m d:%d y:%y",
1425 "m:%m d:%d y:%y H:%H M:%M S:%S",
1426 "%z %Z",
1427 ]:
1428 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1429 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1430 self.assertEqual(b.__format__(fmt), 'B')
1431
1432 def test_resolution_info(self):
1433 # XXX: Should min and max respect subclassing?
1434 if issubclass(self.theclass, datetime):
1435 expected_class = datetime
1436 else:
1437 expected_class = date
1438 self.assertIsInstance(self.theclass.min, expected_class)
1439 self.assertIsInstance(self.theclass.max, expected_class)
1440 self.assertIsInstance(self.theclass.resolution, timedelta)
1441 self.assertTrue(self.theclass.max > self.theclass.min)
1442
1443 def test_extreme_timedelta(self):
1444 big = self.theclass.max - self.theclass.min
1445 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
1446 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
1447 # n == 315537897599999999 ~= 2**58.13
1448 justasbig = timedelta(0, 0, n)
1449 self.assertEqual(big, justasbig)
1450 self.assertEqual(self.theclass.min + big, self.theclass.max)
1451 self.assertEqual(self.theclass.max - big, self.theclass.min)
1452
1453 def test_timetuple(self):
1454 for i in range(7):
1455 # January 2, 1956 is a Monday (0)
1456 d = self.theclass(1956, 1, 2+i)
1457 t = d.timetuple()
1458 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
1459 # February 1, 1956 is a Wednesday (2)
1460 d = self.theclass(1956, 2, 1+i)
1461 t = d.timetuple()
1462 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
1463 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
1464 # of the year.
1465 d = self.theclass(1956, 3, 1+i)
1466 t = d.timetuple()
1467 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
1468 self.assertEqual(t.tm_year, 1956)
1469 self.assertEqual(t.tm_mon, 3)
1470 self.assertEqual(t.tm_mday, 1+i)
1471 self.assertEqual(t.tm_hour, 0)
1472 self.assertEqual(t.tm_min, 0)
1473 self.assertEqual(t.tm_sec, 0)
1474 self.assertEqual(t.tm_wday, (3+i)%7)
1475 self.assertEqual(t.tm_yday, 61+i)
1476 self.assertEqual(t.tm_isdst, -1)
1477
1478 def test_pickling(self):
1479 args = 6, 7, 23
1480 orig = self.theclass(*args)
1481 for pickler, unpickler, proto in pickle_choices:
1482 green = pickler.dumps(orig, proto)
1483 derived = unpickler.loads(green)
1484 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02001485 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001486
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02001487 def test_compat_unpickle(self):
1488 tests = [
1489 b"cdatetime\ndate\n(S'\\x07\\xdf\\x0b\\x1b'\ntR.",
1490 b'cdatetime\ndate\n(U\x04\x07\xdf\x0b\x1btR.',
1491 b'\x80\x02cdatetime\ndate\nU\x04\x07\xdf\x0b\x1b\x85R.',
1492 ]
1493 args = 2015, 11, 27
1494 expected = self.theclass(*args)
1495 for data in tests:
1496 for loads in pickle_loads:
1497 derived = loads(data, encoding='latin1')
1498 self.assertEqual(derived, expected)
1499
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001500 def test_compare(self):
1501 t1 = self.theclass(2, 3, 4)
1502 t2 = self.theclass(2, 3, 4)
1503 self.assertEqual(t1, t2)
1504 self.assertTrue(t1 <= t2)
1505 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001506 self.assertFalse(t1 != t2)
1507 self.assertFalse(t1 < t2)
1508 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001509
1510 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
1511 t2 = self.theclass(*args) # this is larger than t1
1512 self.assertTrue(t1 < t2)
1513 self.assertTrue(t2 > t1)
1514 self.assertTrue(t1 <= t2)
1515 self.assertTrue(t2 >= t1)
1516 self.assertTrue(t1 != t2)
1517 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001518 self.assertFalse(t1 == t2)
1519 self.assertFalse(t2 == t1)
1520 self.assertFalse(t1 > t2)
1521 self.assertFalse(t2 < t1)
1522 self.assertFalse(t1 >= t2)
1523 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001524
1525 for badarg in OTHERSTUFF:
1526 self.assertEqual(t1 == badarg, False)
1527 self.assertEqual(t1 != badarg, True)
1528 self.assertEqual(badarg == t1, False)
1529 self.assertEqual(badarg != t1, True)
1530
1531 self.assertRaises(TypeError, lambda: t1 < badarg)
1532 self.assertRaises(TypeError, lambda: t1 > badarg)
1533 self.assertRaises(TypeError, lambda: t1 >= badarg)
1534 self.assertRaises(TypeError, lambda: badarg <= t1)
1535 self.assertRaises(TypeError, lambda: badarg < t1)
1536 self.assertRaises(TypeError, lambda: badarg > t1)
1537 self.assertRaises(TypeError, lambda: badarg >= t1)
1538
1539 def test_mixed_compare(self):
1540 our = self.theclass(2000, 4, 5)
1541
1542 # Our class can be compared for equality to other classes
1543 self.assertEqual(our == 1, False)
1544 self.assertEqual(1 == our, False)
1545 self.assertEqual(our != 1, True)
1546 self.assertEqual(1 != our, True)
1547
1548 # But the ordering is undefined
1549 self.assertRaises(TypeError, lambda: our < 1)
1550 self.assertRaises(TypeError, lambda: 1 < our)
1551
1552 # Repeat those tests with a different class
1553
1554 class SomeClass:
1555 pass
1556
1557 their = SomeClass()
1558 self.assertEqual(our == their, False)
1559 self.assertEqual(their == our, False)
1560 self.assertEqual(our != their, True)
1561 self.assertEqual(their != our, True)
1562 self.assertRaises(TypeError, lambda: our < their)
1563 self.assertRaises(TypeError, lambda: their < our)
1564
1565 # However, if the other class explicitly defines ordering
1566 # relative to our class, it is allowed to do so
1567
1568 class LargerThanAnything:
1569 def __lt__(self, other):
1570 return False
1571 def __le__(self, other):
1572 return isinstance(other, LargerThanAnything)
1573 def __eq__(self, other):
1574 return isinstance(other, LargerThanAnything)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001575 def __gt__(self, other):
1576 return not isinstance(other, LargerThanAnything)
1577 def __ge__(self, other):
1578 return True
1579
1580 their = LargerThanAnything()
1581 self.assertEqual(our == their, False)
1582 self.assertEqual(their == our, False)
1583 self.assertEqual(our != their, True)
1584 self.assertEqual(their != our, True)
1585 self.assertEqual(our < their, True)
1586 self.assertEqual(their < our, False)
1587
1588 def test_bool(self):
1589 # All dates are considered true.
1590 self.assertTrue(self.theclass.min)
1591 self.assertTrue(self.theclass.max)
1592
Alexander Belopolsky89da3492011-05-02 13:14:24 -04001593 def test_strftime_y2k(self):
1594 for y in (1, 49, 70, 99, 100, 999, 1000, 1970):
Florent Xicluna49ce0682011-11-01 12:56:14 +01001595 d = self.theclass(y, 1, 1)
1596 # Issue 13305: For years < 1000, the value is not always
1597 # padded to 4 digits across platforms. The C standard
1598 # assumes year >= 1900, so it does not specify the number
1599 # of digits.
1600 if d.strftime("%Y") != '%04d' % y:
1601 # Year 42 returns '42', not padded
1602 self.assertEqual(d.strftime("%Y"), '%d' % y)
Victor Stinner15a83e82016-03-12 08:16:48 +01001603 # '0042' is obtained anyway
1604 self.assertEqual(d.strftime("%4Y"), '%04d' % y)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001605
1606 def test_replace(self):
1607 cls = self.theclass
1608 args = [1, 2, 3]
1609 base = cls(*args)
1610 self.assertEqual(base, base.replace())
1611
1612 i = 0
1613 for name, newval in (("year", 2),
1614 ("month", 3),
1615 ("day", 4)):
1616 newargs = args[:]
1617 newargs[i] = newval
1618 expected = cls(*newargs)
1619 got = base.replace(**{name: newval})
1620 self.assertEqual(expected, got)
1621 i += 1
1622
1623 # Out of bounds.
1624 base = cls(2000, 2, 29)
1625 self.assertRaises(ValueError, base.replace, year=2001)
1626
Paul Ganssle191e9932017-11-09 16:34:29 -05001627 def test_subclass_replace(self):
1628 class DateSubclass(self.theclass):
1629 pass
1630
1631 dt = DateSubclass(2012, 1, 1)
1632 self.assertIs(type(dt.replace(year=2013)), DateSubclass)
1633
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001634 def test_subclass_date(self):
1635
1636 class C(self.theclass):
1637 theAnswer = 42
1638
1639 def __new__(cls, *args, **kws):
1640 temp = kws.copy()
1641 extra = temp.pop('extra')
1642 result = self.theclass.__new__(cls, *args, **temp)
1643 result.extra = extra
1644 return result
1645
1646 def newmeth(self, start):
1647 return start + self.year + self.month
1648
1649 args = 2003, 4, 14
1650
1651 dt1 = self.theclass(*args)
1652 dt2 = C(*args, **{'extra': 7})
1653
1654 self.assertEqual(dt2.__class__, C)
1655 self.assertEqual(dt2.theAnswer, 42)
1656 self.assertEqual(dt2.extra, 7)
1657 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1658 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1659
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05001660 def test_subclass_alternate_constructors(self):
1661 # Test that alternate constructors call the constructor
1662 class DateSubclass(self.theclass):
1663 def __new__(cls, *args, **kwargs):
1664 result = self.theclass.__new__(cls, *args, **kwargs)
1665 result.extra = 7
1666
1667 return result
1668
1669 args = (2003, 4, 14)
1670 d_ord = 731319 # Equivalent ordinal date
1671 d_isoformat = '2003-04-14' # Equivalent isoformat()
1672
1673 base_d = DateSubclass(*args)
1674 self.assertIsInstance(base_d, DateSubclass)
1675 self.assertEqual(base_d.extra, 7)
1676
1677 # Timestamp depends on time zone, so we'll calculate the equivalent here
1678 ts = datetime.combine(base_d, time(0)).timestamp()
1679
1680 test_cases = [
1681 ('fromordinal', (d_ord,)),
1682 ('fromtimestamp', (ts,)),
1683 ('fromisoformat', (d_isoformat,)),
1684 ]
1685
1686 for constr_name, constr_args in test_cases:
1687 for base_obj in (DateSubclass, base_d):
1688 # Test both the classmethod and method
1689 with self.subTest(base_obj_type=type(base_obj),
1690 constr_name=constr_name):
1691 constr = getattr(base_obj, constr_name)
1692
1693 dt = constr(*constr_args)
1694
1695 # Test that it creates the right subclass
1696 self.assertIsInstance(dt, DateSubclass)
1697
1698 # Test that it's equal to the base object
1699 self.assertEqual(dt, base_d)
1700
1701 # Test that it called the constructor
1702 self.assertEqual(dt.extra, 7)
1703
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001704 def test_pickling_subclass_date(self):
1705
1706 args = 6, 7, 23
1707 orig = SubclassDate(*args)
1708 for pickler, unpickler, proto in pickle_choices:
1709 green = pickler.dumps(orig, proto)
1710 derived = unpickler.loads(green)
1711 self.assertEqual(orig, derived)
1712
1713 def test_backdoor_resistance(self):
1714 # For fast unpickling, the constructor accepts a pickle byte string.
1715 # This is a low-overhead backdoor. A user can (by intent or
1716 # mistake) pass a string directly, which (if it's the right length)
1717 # will get treated like a pickle, and bypass the normal sanity
1718 # checks in the constructor. This can create insane objects.
1719 # The constructor doesn't want to burn the time to validate all
1720 # fields, but does check the month field. This stops, e.g.,
1721 # datetime.datetime('1995-03-25') from yielding an insane object.
1722 base = b'1995-03-25'
1723 if not issubclass(self.theclass, datetime):
1724 base = base[:4]
1725 for month_byte in b'9', b'\0', b'\r', b'\xff':
1726 self.assertRaises(TypeError, self.theclass,
1727 base[:2] + month_byte + base[3:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001728 if issubclass(self.theclass, datetime):
1729 # Good bytes, but bad tzinfo:
1730 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
1731 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001732
1733 for ord_byte in range(1, 13):
1734 # This shouldn't blow up because of the month byte alone. If
1735 # the implementation changes to do more-careful checking, it may
1736 # blow up because other fields are insane.
1737 self.theclass(base[:2] + bytes([ord_byte]) + base[3:])
1738
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001739 def test_fromisoformat(self):
1740 # Test that isoformat() is reversible
1741 base_dates = [
1742 (1, 1, 1),
1743 (1000, 2, 14),
1744 (1900, 1, 1),
1745 (2000, 2, 29),
1746 (2004, 11, 12),
1747 (2004, 4, 3),
1748 (2017, 5, 30)
1749 ]
1750
1751 for dt_tuple in base_dates:
1752 dt = self.theclass(*dt_tuple)
1753 dt_str = dt.isoformat()
1754 with self.subTest(dt_str=dt_str):
1755 dt_rt = self.theclass.fromisoformat(dt.isoformat())
1756
1757 self.assertEqual(dt, dt_rt)
1758
1759 def test_fromisoformat_subclass(self):
1760 class DateSubclass(self.theclass):
1761 pass
1762
1763 dt = DateSubclass(2014, 12, 14)
1764
1765 dt_rt = DateSubclass.fromisoformat(dt.isoformat())
1766
1767 self.assertIsInstance(dt_rt, DateSubclass)
1768
1769 def test_fromisoformat_fails(self):
1770 # Test that fromisoformat() fails on invalid values
1771 bad_strs = [
1772 '', # Empty string
Paul Ganssle096329f2018-08-23 11:06:20 -04001773 '\ud800', # bpo-34454: Surrogate code point
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001774 '009-03-04', # Not 10 characters
1775 '123456789', # Not a date
1776 '200a-12-04', # Invalid character in year
1777 '2009-1a-04', # Invalid character in month
1778 '2009-12-0a', # Invalid character in day
1779 '2009-01-32', # Invalid day
1780 '2009-02-29', # Invalid leap day
1781 '20090228', # Valid ISO8601 output not from isoformat()
Paul Ganssle096329f2018-08-23 11:06:20 -04001782 '2009\ud80002\ud80028', # Separators are surrogate codepoints
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001783 ]
1784
1785 for bad_str in bad_strs:
1786 with self.assertRaises(ValueError):
1787 self.theclass.fromisoformat(bad_str)
1788
1789 def test_fromisoformat_fails_typeerror(self):
1790 # Test that fromisoformat fails when passed the wrong type
1791 import io
1792
1793 bad_types = [b'2009-03-01', None, io.StringIO('2009-03-01')]
1794 for bad_type in bad_types:
1795 with self.assertRaises(TypeError):
1796 self.theclass.fromisoformat(bad_type)
1797
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001798#############################################################################
1799# datetime tests
1800
1801class SubclassDatetime(datetime):
1802 sub_var = 1
1803
1804class TestDateTime(TestDate):
1805
1806 theclass = datetime
1807
1808 def test_basic_attributes(self):
1809 dt = self.theclass(2002, 3, 1, 12, 0)
1810 self.assertEqual(dt.year, 2002)
1811 self.assertEqual(dt.month, 3)
1812 self.assertEqual(dt.day, 1)
1813 self.assertEqual(dt.hour, 12)
1814 self.assertEqual(dt.minute, 0)
1815 self.assertEqual(dt.second, 0)
1816 self.assertEqual(dt.microsecond, 0)
1817
1818 def test_basic_attributes_nonzero(self):
1819 # Make sure all attributes are non-zero so bugs in
1820 # bit-shifting access show up.
1821 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1822 self.assertEqual(dt.year, 2002)
1823 self.assertEqual(dt.month, 3)
1824 self.assertEqual(dt.day, 1)
1825 self.assertEqual(dt.hour, 12)
1826 self.assertEqual(dt.minute, 59)
1827 self.assertEqual(dt.second, 59)
1828 self.assertEqual(dt.microsecond, 8000)
1829
1830 def test_roundtrip(self):
1831 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1832 self.theclass.now()):
1833 # Verify dt -> string -> datetime identity.
1834 s = repr(dt)
1835 self.assertTrue(s.startswith('datetime.'))
1836 s = s[9:]
1837 dt2 = eval(s)
1838 self.assertEqual(dt, dt2)
1839
1840 # Verify identity via reconstructing from pieces.
1841 dt2 = self.theclass(dt.year, dt.month, dt.day,
1842 dt.hour, dt.minute, dt.second,
1843 dt.microsecond)
1844 self.assertEqual(dt, dt2)
1845
1846 def test_isoformat(self):
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001847 t = self.theclass(1, 2, 3, 4, 5, 1, 123)
1848 self.assertEqual(t.isoformat(), "0001-02-03T04:05:01.000123")
1849 self.assertEqual(t.isoformat('T'), "0001-02-03T04:05:01.000123")
1850 self.assertEqual(t.isoformat(' '), "0001-02-03 04:05:01.000123")
1851 self.assertEqual(t.isoformat('\x00'), "0001-02-03\x0004:05:01.000123")
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03001852 # bpo-34482: Check that surrogates are handled properly.
1853 self.assertEqual(t.isoformat('\ud800'),
1854 "0001-02-03\ud80004:05:01.000123")
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001855 self.assertEqual(t.isoformat(timespec='hours'), "0001-02-03T04")
1856 self.assertEqual(t.isoformat(timespec='minutes'), "0001-02-03T04:05")
1857 self.assertEqual(t.isoformat(timespec='seconds'), "0001-02-03T04:05:01")
1858 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1859 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000123")
1860 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01.000123")
1861 self.assertEqual(t.isoformat(sep=' ', timespec='minutes'), "0001-02-03 04:05")
1862 self.assertRaises(ValueError, t.isoformat, timespec='foo')
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03001863 # bpo-34482: Check that surrogates are handled properly.
1864 self.assertRaises(ValueError, t.isoformat, timespec='\ud800')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001865 # str is ISO format with the separator forced to a blank.
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001866 self.assertEqual(str(t), "0001-02-03 04:05:01.000123")
1867
1868 t = self.theclass(1, 2, 3, 4, 5, 1, 999500, tzinfo=timezone.utc)
1869 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999+00:00")
1870
1871 t = self.theclass(1, 2, 3, 4, 5, 1, 999500)
1872 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999")
1873
1874 t = self.theclass(1, 2, 3, 4, 5, 1)
1875 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01")
1876 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1877 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000000")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001878
1879 t = self.theclass(2, 3, 2)
1880 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1881 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1882 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1883 # str is ISO format with the separator forced to a blank.
1884 self.assertEqual(str(t), "0002-03-02 00:00:00")
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001885 # ISO format with timezone
1886 tz = FixedOffset(timedelta(seconds=16), 'XXX')
1887 t = self.theclass(2, 3, 2, tzinfo=tz)
1888 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00+00:00:16")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001889
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001890 def test_isoformat_timezone(self):
1891 tzoffsets = [
1892 ('05:00', timedelta(hours=5)),
1893 ('02:00', timedelta(hours=2)),
1894 ('06:27', timedelta(hours=6, minutes=27)),
1895 ('12:32:30', timedelta(hours=12, minutes=32, seconds=30)),
1896 ('02:04:09.123456', timedelta(hours=2, minutes=4, seconds=9, microseconds=123456))
1897 ]
1898
1899 tzinfos = [
1900 ('', None),
1901 ('+00:00', timezone.utc),
1902 ('+00:00', timezone(timedelta(0))),
1903 ]
1904
1905 tzinfos += [
1906 (prefix + expected, timezone(sign * td))
1907 for expected, td in tzoffsets
1908 for prefix, sign in [('-', -1), ('+', 1)]
1909 ]
1910
1911 dt_base = self.theclass(2016, 4, 1, 12, 37, 9)
1912 exp_base = '2016-04-01T12:37:09'
1913
1914 for exp_tz, tzi in tzinfos:
1915 dt = dt_base.replace(tzinfo=tzi)
1916 exp = exp_base + exp_tz
1917 with self.subTest(tzi=tzi):
1918 assert dt.isoformat() == exp
1919
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001920 def test_format(self):
1921 dt = self.theclass(2007, 9, 10, 4, 5, 1, 123)
1922 self.assertEqual(dt.__format__(''), str(dt))
1923
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02001924 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001925 dt.__format__(123)
1926
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001927 # check that a derived class's __str__() gets called
1928 class A(self.theclass):
1929 def __str__(self):
1930 return 'A'
1931 a = A(2007, 9, 10, 4, 5, 1, 123)
1932 self.assertEqual(a.__format__(''), 'A')
1933
1934 # check that a derived class's strftime gets called
1935 class B(self.theclass):
1936 def strftime(self, format_spec):
1937 return 'B'
1938 b = B(2007, 9, 10, 4, 5, 1, 123)
1939 self.assertEqual(b.__format__(''), str(dt))
1940
1941 for fmt in ["m:%m d:%d y:%y",
1942 "m:%m d:%d y:%y H:%H M:%M S:%S",
1943 "%z %Z",
1944 ]:
1945 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1946 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1947 self.assertEqual(b.__format__(fmt), 'B')
1948
1949 def test_more_ctime(self):
1950 # Test fields that TestDate doesn't touch.
1951 import time
1952
1953 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1954 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1955 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1956 # out. The difference is that t.ctime() produces " 2" for the day,
1957 # but platform ctime() produces "02" for the day. According to
1958 # C99, t.ctime() is correct here.
1959 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1960
1961 # So test a case where that difference doesn't matter.
1962 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1963 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1964
1965 def test_tz_independent_comparing(self):
1966 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1967 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1968 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1969 self.assertEqual(dt1, dt3)
1970 self.assertTrue(dt2 > dt3)
1971
1972 # Make sure comparison doesn't forget microseconds, and isn't done
1973 # via comparing a float timestamp (an IEEE double doesn't have enough
Leo Ariasc3d95082018-02-03 18:36:10 -06001974 # precision to span microsecond resolution across years 1 through 9999,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001975 # so comparing via timestamp necessarily calls some distinct values
1976 # equal).
1977 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1978 us = timedelta(microseconds=1)
1979 dt2 = dt1 + us
1980 self.assertEqual(dt2 - dt1, us)
1981 self.assertTrue(dt1 < dt2)
1982
1983 def test_strftime_with_bad_tzname_replace(self):
1984 # verify ok if tzinfo.tzname().replace() returns a non-string
1985 class MyTzInfo(FixedOffset):
1986 def tzname(self, dt):
1987 class MyStr(str):
1988 def replace(self, *args):
1989 return None
1990 return MyStr('name')
1991 t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
1992 self.assertRaises(TypeError, t.strftime, '%Z')
1993
1994 def test_bad_constructor_arguments(self):
1995 # bad years
1996 self.theclass(MINYEAR, 1, 1) # no exception
1997 self.theclass(MAXYEAR, 1, 1) # no exception
1998 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1999 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
2000 # bad months
2001 self.theclass(2000, 1, 1) # no exception
2002 self.theclass(2000, 12, 1) # no exception
2003 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
2004 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
2005 # bad days
2006 self.theclass(2000, 2, 29) # no exception
2007 self.theclass(2004, 2, 29) # no exception
2008 self.theclass(2400, 2, 29) # no exception
2009 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
2010 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
2011 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
2012 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
2013 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
2014 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
2015 # bad hours
2016 self.theclass(2000, 1, 31, 0) # no exception
2017 self.theclass(2000, 1, 31, 23) # no exception
2018 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
2019 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
2020 # bad minutes
2021 self.theclass(2000, 1, 31, 23, 0) # no exception
2022 self.theclass(2000, 1, 31, 23, 59) # no exception
2023 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
2024 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
2025 # bad seconds
2026 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
2027 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
2028 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
2029 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
2030 # bad microseconds
2031 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
2032 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
2033 self.assertRaises(ValueError, self.theclass,
2034 2000, 1, 31, 23, 59, 59, -1)
2035 self.assertRaises(ValueError, self.theclass,
2036 2000, 1, 31, 23, 59, 59,
2037 1000000)
Alexander Belopolsky47649ab2016-08-08 17:05:40 -04002038 # bad fold
2039 self.assertRaises(ValueError, self.theclass,
2040 2000, 1, 31, fold=-1)
2041 self.assertRaises(ValueError, self.theclass,
2042 2000, 1, 31, fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002043 # Positional fold:
2044 self.assertRaises(TypeError, self.theclass,
2045 2000, 1, 31, 23, 59, 59, 0, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04002046
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002047 def test_hash_equality(self):
2048 d = self.theclass(2000, 12, 31, 23, 30, 17)
2049 e = self.theclass(2000, 12, 31, 23, 30, 17)
2050 self.assertEqual(d, e)
2051 self.assertEqual(hash(d), hash(e))
2052
2053 dic = {d: 1}
2054 dic[e] = 2
2055 self.assertEqual(len(dic), 1)
2056 self.assertEqual(dic[d], 2)
2057 self.assertEqual(dic[e], 2)
2058
2059 d = self.theclass(2001, 1, 1, 0, 5, 17)
2060 e = self.theclass(2001, 1, 1, 0, 5, 17)
2061 self.assertEqual(d, e)
2062 self.assertEqual(hash(d), hash(e))
2063
2064 dic = {d: 1}
2065 dic[e] = 2
2066 self.assertEqual(len(dic), 1)
2067 self.assertEqual(dic[d], 2)
2068 self.assertEqual(dic[e], 2)
2069
2070 def test_computations(self):
2071 a = self.theclass(2002, 1, 31)
2072 b = self.theclass(1956, 1, 31)
2073 diff = a-b
2074 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
2075 self.assertEqual(diff.seconds, 0)
2076 self.assertEqual(diff.microseconds, 0)
2077 a = self.theclass(2002, 3, 2, 17, 6)
2078 millisec = timedelta(0, 0, 1000)
2079 hour = timedelta(0, 3600)
2080 day = timedelta(1)
2081 week = timedelta(7)
2082 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
2083 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
2084 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
2085 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
2086 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
2087 self.assertEqual(a - hour, a + -hour)
2088 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
2089 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
2090 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
2091 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
2092 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
2093 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
2094 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
2095 self.assertEqual((a + week) - a, week)
2096 self.assertEqual((a + day) - a, day)
2097 self.assertEqual((a + hour) - a, hour)
2098 self.assertEqual((a + millisec) - a, millisec)
2099 self.assertEqual((a - week) - a, -week)
2100 self.assertEqual((a - day) - a, -day)
2101 self.assertEqual((a - hour) - a, -hour)
2102 self.assertEqual((a - millisec) - a, -millisec)
2103 self.assertEqual(a - (a + week), -week)
2104 self.assertEqual(a - (a + day), -day)
2105 self.assertEqual(a - (a + hour), -hour)
2106 self.assertEqual(a - (a + millisec), -millisec)
2107 self.assertEqual(a - (a - week), week)
2108 self.assertEqual(a - (a - day), day)
2109 self.assertEqual(a - (a - hour), hour)
2110 self.assertEqual(a - (a - millisec), millisec)
2111 self.assertEqual(a + (week + day + hour + millisec),
2112 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
2113 self.assertEqual(a + (week + day + hour + millisec),
2114 (((a + week) + day) + hour) + millisec)
2115 self.assertEqual(a - (week + day + hour + millisec),
2116 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
2117 self.assertEqual(a - (week + day + hour + millisec),
2118 (((a - week) - day) - hour) - millisec)
2119 # Add/sub ints or floats should be illegal
2120 for i in 1, 1.0:
2121 self.assertRaises(TypeError, lambda: a+i)
2122 self.assertRaises(TypeError, lambda: a-i)
2123 self.assertRaises(TypeError, lambda: i+a)
2124 self.assertRaises(TypeError, lambda: i-a)
2125
2126 # delta - datetime is senseless.
2127 self.assertRaises(TypeError, lambda: day - a)
2128 # mixing datetime and (delta or datetime) via * or // is senseless
2129 self.assertRaises(TypeError, lambda: day * a)
2130 self.assertRaises(TypeError, lambda: a * day)
2131 self.assertRaises(TypeError, lambda: day // a)
2132 self.assertRaises(TypeError, lambda: a // day)
2133 self.assertRaises(TypeError, lambda: a * a)
2134 self.assertRaises(TypeError, lambda: a // a)
2135 # datetime + datetime is senseless
2136 self.assertRaises(TypeError, lambda: a + a)
2137
2138 def test_pickling(self):
2139 args = 6, 7, 23, 20, 59, 1, 64**2
2140 orig = self.theclass(*args)
2141 for pickler, unpickler, proto in pickle_choices:
2142 green = pickler.dumps(orig, proto)
2143 derived = unpickler.loads(green)
2144 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02002145 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002146
2147 def test_more_pickling(self):
2148 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
Serhiy Storchakabad12572014-12-15 14:03:42 +02002149 for proto in range(pickle.HIGHEST_PROTOCOL + 1):
2150 s = pickle.dumps(a, proto)
2151 b = pickle.loads(s)
2152 self.assertEqual(b.year, 2003)
2153 self.assertEqual(b.month, 2)
2154 self.assertEqual(b.day, 7)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002155
2156 def test_pickling_subclass_datetime(self):
2157 args = 6, 7, 23, 20, 59, 1, 64**2
2158 orig = SubclassDatetime(*args)
2159 for pickler, unpickler, proto in pickle_choices:
2160 green = pickler.dumps(orig, proto)
2161 derived = unpickler.loads(green)
2162 self.assertEqual(orig, derived)
2163
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02002164 def test_compat_unpickle(self):
2165 tests = [
2166 b'cdatetime\ndatetime\n('
2167 b"S'\\x07\\xdf\\x0b\\x1b\\x14;\\x01\\x00\\x10\\x00'\ntR.",
2168
2169 b'cdatetime\ndatetime\n('
2170 b'U\n\x07\xdf\x0b\x1b\x14;\x01\x00\x10\x00tR.',
2171
2172 b'\x80\x02cdatetime\ndatetime\n'
2173 b'U\n\x07\xdf\x0b\x1b\x14;\x01\x00\x10\x00\x85R.',
2174 ]
2175 args = 2015, 11, 27, 20, 59, 1, 64**2
2176 expected = self.theclass(*args)
2177 for data in tests:
2178 for loads in pickle_loads:
2179 derived = loads(data, encoding='latin1')
2180 self.assertEqual(derived, expected)
2181
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002182 def test_more_compare(self):
2183 # The test_compare() inherited from TestDate covers the error cases.
2184 # We just want to test lexicographic ordering on the members datetime
2185 # has that date lacks.
2186 args = [2000, 11, 29, 20, 58, 16, 999998]
2187 t1 = self.theclass(*args)
2188 t2 = self.theclass(*args)
2189 self.assertEqual(t1, t2)
2190 self.assertTrue(t1 <= t2)
2191 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002192 self.assertFalse(t1 != t2)
2193 self.assertFalse(t1 < t2)
2194 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002195
2196 for i in range(len(args)):
2197 newargs = args[:]
2198 newargs[i] = args[i] + 1
2199 t2 = self.theclass(*newargs) # this is larger than t1
2200 self.assertTrue(t1 < t2)
2201 self.assertTrue(t2 > t1)
2202 self.assertTrue(t1 <= t2)
2203 self.assertTrue(t2 >= t1)
2204 self.assertTrue(t1 != t2)
2205 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002206 self.assertFalse(t1 == t2)
2207 self.assertFalse(t2 == t1)
2208 self.assertFalse(t1 > t2)
2209 self.assertFalse(t2 < t1)
2210 self.assertFalse(t1 >= t2)
2211 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002212
2213
2214 # A helper for timestamp constructor tests.
2215 def verify_field_equality(self, expected, got):
2216 self.assertEqual(expected.tm_year, got.year)
2217 self.assertEqual(expected.tm_mon, got.month)
2218 self.assertEqual(expected.tm_mday, got.day)
2219 self.assertEqual(expected.tm_hour, got.hour)
2220 self.assertEqual(expected.tm_min, got.minute)
2221 self.assertEqual(expected.tm_sec, got.second)
2222
2223 def test_fromtimestamp(self):
2224 import time
2225
2226 ts = time.time()
2227 expected = time.localtime(ts)
2228 got = self.theclass.fromtimestamp(ts)
2229 self.verify_field_equality(expected, got)
2230
2231 def test_utcfromtimestamp(self):
2232 import time
2233
2234 ts = time.time()
2235 expected = time.gmtime(ts)
2236 got = self.theclass.utcfromtimestamp(ts)
2237 self.verify_field_equality(expected, got)
2238
Alexander Belopolskya4415142012-06-08 12:33:09 -04002239 # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in
2240 # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0).
2241 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
2242 def test_timestamp_naive(self):
2243 t = self.theclass(1970, 1, 1)
2244 self.assertEqual(t.timestamp(), 18000.0)
2245 t = self.theclass(1970, 1, 1, 1, 2, 3, 4)
2246 self.assertEqual(t.timestamp(),
2247 18000.0 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002248 # Missing hour
2249 t0 = self.theclass(2012, 3, 11, 2, 30)
2250 t1 = t0.replace(fold=1)
2251 self.assertEqual(self.theclass.fromtimestamp(t1.timestamp()),
2252 t0 - timedelta(hours=1))
2253 self.assertEqual(self.theclass.fromtimestamp(t0.timestamp()),
2254 t1 + timedelta(hours=1))
Alexander Belopolskya4415142012-06-08 12:33:09 -04002255 # Ambiguous hour defaults to DST
2256 t = self.theclass(2012, 11, 4, 1, 30)
2257 self.assertEqual(self.theclass.fromtimestamp(t.timestamp()), t)
2258
2259 # Timestamp may raise an overflow error on some platforms
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002260 # XXX: Do we care to support the first and last year?
2261 for t in [self.theclass(2,1,1), self.theclass(9998,12,12)]:
Alexander Belopolskya4415142012-06-08 12:33:09 -04002262 try:
2263 s = t.timestamp()
2264 except OverflowError:
2265 pass
2266 else:
2267 self.assertEqual(self.theclass.fromtimestamp(s), t)
2268
2269 def test_timestamp_aware(self):
2270 t = self.theclass(1970, 1, 1, tzinfo=timezone.utc)
2271 self.assertEqual(t.timestamp(), 0.0)
2272 t = self.theclass(1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone.utc)
2273 self.assertEqual(t.timestamp(),
2274 3600 + 2*60 + 3 + 4*1e-6)
2275 t = self.theclass(1970, 1, 1, 1, 2, 3, 4,
2276 tzinfo=timezone(timedelta(hours=-5), 'EST'))
2277 self.assertEqual(t.timestamp(),
2278 18000 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002279
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002280 @support.run_with_tz('MSK-03') # Something east of Greenwich
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002281 def test_microsecond_rounding(self):
Victor Stinner5ebfe422015-09-18 14:52:15 +02002282 for fts in [self.theclass.fromtimestamp,
2283 self.theclass.utcfromtimestamp]:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002284 zero = fts(0)
2285 self.assertEqual(zero.second, 0)
2286 self.assertEqual(zero.microsecond, 0)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002287 one = fts(1e-6)
Victor Stinner8050ca92012-03-14 00:17:05 +01002288 try:
2289 minus_one = fts(-1e-6)
2290 except OSError:
2291 # localtime(-1) and gmtime(-1) is not supported on Windows
2292 pass
2293 else:
2294 self.assertEqual(minus_one.second, 59)
2295 self.assertEqual(minus_one.microsecond, 999999)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002296
Victor Stinner8050ca92012-03-14 00:17:05 +01002297 t = fts(-1e-8)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002298 self.assertEqual(t, zero)
Victor Stinner8050ca92012-03-14 00:17:05 +01002299 t = fts(-9e-7)
2300 self.assertEqual(t, minus_one)
2301 t = fts(-1e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002302 self.assertEqual(t, zero)
Victor Stinner8820a352015-09-05 10:50:20 +02002303 t = fts(-1/2**7)
2304 self.assertEqual(t.second, 59)
Victor Stinner7667f582015-09-09 01:02:23 +02002305 self.assertEqual(t.microsecond, 992188)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002306
2307 t = fts(1e-7)
2308 self.assertEqual(t, zero)
2309 t = fts(9e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002310 self.assertEqual(t, one)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002311 t = fts(0.99999949)
2312 self.assertEqual(t.second, 0)
2313 self.assertEqual(t.microsecond, 999999)
2314 t = fts(0.9999999)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002315 self.assertEqual(t.second, 1)
2316 self.assertEqual(t.microsecond, 0)
Victor Stinneradfefa52015-09-04 23:57:25 +02002317 t = fts(1/2**7)
2318 self.assertEqual(t.second, 0)
Victor Stinner7667f582015-09-09 01:02:23 +02002319 self.assertEqual(t.microsecond, 7812)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002320
Victor Stinnerb67f0962017-02-10 10:34:02 +01002321 def test_timestamp_limits(self):
2322 # minimum timestamp
2323 min_dt = self.theclass.min.replace(tzinfo=timezone.utc)
2324 min_ts = min_dt.timestamp()
Victor Stinner6f37e362017-02-10 11:45:14 +01002325 try:
2326 # date 0001-01-01 00:00:00+00:00: timestamp=-62135596800
2327 self.assertEqual(self.theclass.fromtimestamp(min_ts, tz=timezone.utc),
2328 min_dt)
Victor Stinner2a35c932017-02-10 12:37:21 +01002329 except (OverflowError, OSError) as exc:
2330 # the date 0001-01-01 doesn't fit into 32-bit time_t,
2331 # or platform doesn't support such very old date
Victor Stinner6f37e362017-02-10 11:45:14 +01002332 self.skipTest(str(exc))
Victor Stinnerb67f0962017-02-10 10:34:02 +01002333
2334 # maximum timestamp: set seconds to zero to avoid rounding issues
2335 max_dt = self.theclass.max.replace(tzinfo=timezone.utc,
2336 second=0, microsecond=0)
2337 max_ts = max_dt.timestamp()
2338 # date 9999-12-31 23:59:00+00:00: timestamp 253402300740
2339 self.assertEqual(self.theclass.fromtimestamp(max_ts, tz=timezone.utc),
2340 max_dt)
2341
2342 # number of seconds greater than 1 year: make sure that the new date
2343 # is not valid in datetime.datetime limits
2344 delta = 3600 * 24 * 400
2345
2346 # too small
2347 ts = min_ts - delta
2348 # converting a Python int to C time_t can raise a OverflowError,
2349 # especially on 32-bit platforms.
2350 with self.assertRaises((ValueError, OverflowError)):
2351 self.theclass.fromtimestamp(ts)
2352 with self.assertRaises((ValueError, OverflowError)):
2353 self.theclass.utcfromtimestamp(ts)
2354
2355 # too big
2356 ts = max_dt.timestamp() + delta
2357 with self.assertRaises((ValueError, OverflowError)):
2358 self.theclass.fromtimestamp(ts)
2359 with self.assertRaises((ValueError, OverflowError)):
2360 self.theclass.utcfromtimestamp(ts)
2361
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002362 def test_insane_fromtimestamp(self):
2363 # It's possible that some platform maps time_t to double,
2364 # and that this test will fail there. This test should
2365 # exempt such platforms (provided they return reasonable
2366 # results!).
2367 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002368 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002369 insane)
2370
2371 def test_insane_utcfromtimestamp(self):
2372 # It's possible that some platform maps time_t to double,
2373 # and that this test will fail there. This test should
2374 # exempt such platforms (provided they return reasonable
2375 # results!).
2376 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002377 self.assertRaises(OverflowError, self.theclass.utcfromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002378 insane)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002379
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002380 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2381 def test_negative_float_fromtimestamp(self):
2382 # The result is tz-dependent; at least test that this doesn't
2383 # fail (like it did before bug 1646728 was fixed).
2384 self.theclass.fromtimestamp(-1.05)
2385
2386 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2387 def test_negative_float_utcfromtimestamp(self):
2388 d = self.theclass.utcfromtimestamp(-1.05)
2389 self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
2390
2391 def test_utcnow(self):
2392 import time
2393
2394 # Call it a success if utcnow() and utcfromtimestamp() are within
2395 # a second of each other.
2396 tolerance = timedelta(seconds=1)
2397 for dummy in range(3):
2398 from_now = self.theclass.utcnow()
2399 from_timestamp = self.theclass.utcfromtimestamp(time.time())
2400 if abs(from_timestamp - from_now) <= tolerance:
2401 break
2402 # Else try again a few times.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002403 self.assertLessEqual(abs(from_timestamp - from_now), tolerance)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002404
2405 def test_strptime(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002406 string = '2004-12-01 13:02:47.197'
2407 format = '%Y-%m-%d %H:%M:%S.%f'
2408 expected = _strptime._strptime_datetime(self.theclass, string, format)
2409 got = self.theclass.strptime(string, format)
2410 self.assertEqual(expected, got)
2411 self.assertIs(type(expected), self.theclass)
2412 self.assertIs(type(got), self.theclass)
2413
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03002414 # bpo-34482: Check that surrogates are handled properly.
2415 inputs = [
2416 ('2004-12-01\ud80013:02:47.197', '%Y-%m-%d\ud800%H:%M:%S.%f'),
2417 ('2004\ud80012-01 13:02:47.197', '%Y\ud800%m-%d %H:%M:%S.%f'),
2418 ('2004-12-01 13:02\ud80047.197', '%Y-%m-%d %H:%M\ud800%S.%f'),
2419 ]
2420 for string, format in inputs:
2421 with self.subTest(string=string, format=format):
2422 expected = _strptime._strptime_datetime(self.theclass, string,
2423 format)
2424 got = self.theclass.strptime(string, format)
2425 self.assertEqual(expected, got)
2426
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002427 strptime = self.theclass.strptime
2428 self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
2429 self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
Mario Corchero32318932017-10-26 01:35:41 +01002430 self.assertEqual(
2431 strptime("-00:02:01.000003", "%z").utcoffset(),
2432 -timedelta(minutes=2, seconds=1, microseconds=3)
2433 )
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002434 # Only local timezone and UTC are supported
2435 for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
2436 (-_time.timezone, _time.tzname[0])):
2437 if tzseconds < 0:
2438 sign = '-'
2439 seconds = -tzseconds
2440 else:
2441 sign ='+'
2442 seconds = tzseconds
2443 hours, minutes = divmod(seconds//60, 60)
2444 dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
Martin Panterfca22322015-11-16 09:22:19 +00002445 dt = strptime(dtstr, "%z %Z")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002446 self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
2447 self.assertEqual(dt.tzname(), tzname)
2448 # Can produce inconsistent datetime
2449 dtstr, fmt = "+1234 UTC", "%z %Z"
2450 dt = strptime(dtstr, fmt)
2451 self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE)
2452 self.assertEqual(dt.tzname(), 'UTC')
2453 # yet will roundtrip
2454 self.assertEqual(dt.strftime(fmt), dtstr)
2455
2456 # Produce naive datetime if no %z is provided
2457 self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
2458
2459 with self.assertRaises(ValueError): strptime("-2400", "%z")
2460 with self.assertRaises(ValueError): strptime("-000", "%z")
2461
2462 def test_more_timetuple(self):
2463 # This tests fields beyond those tested by the TestDate.test_timetuple.
2464 t = self.theclass(2004, 12, 31, 6, 22, 33)
2465 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
2466 self.assertEqual(t.timetuple(),
2467 (t.year, t.month, t.day,
2468 t.hour, t.minute, t.second,
2469 t.weekday(),
2470 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
2471 -1))
2472 tt = t.timetuple()
2473 self.assertEqual(tt.tm_year, t.year)
2474 self.assertEqual(tt.tm_mon, t.month)
2475 self.assertEqual(tt.tm_mday, t.day)
2476 self.assertEqual(tt.tm_hour, t.hour)
2477 self.assertEqual(tt.tm_min, t.minute)
2478 self.assertEqual(tt.tm_sec, t.second)
2479 self.assertEqual(tt.tm_wday, t.weekday())
2480 self.assertEqual(tt.tm_yday, t.toordinal() -
2481 date(t.year, 1, 1).toordinal() + 1)
2482 self.assertEqual(tt.tm_isdst, -1)
2483
2484 def test_more_strftime(self):
2485 # This tests fields beyond those tested by the TestDate.test_strftime.
2486 t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
2487 self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
2488 "12 31 04 000047 33 22 06 366")
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04002489 for (s, us), z in [((33, 123), "33.000123"), ((33, 0), "33"),]:
2490 tz = timezone(-timedelta(hours=2, seconds=s, microseconds=us))
2491 t = t.replace(tzinfo=tz)
2492 self.assertEqual(t.strftime("%z"), "-0200" + z)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002493
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03002494 # bpo-34482: Check that surrogates don't cause a crash.
2495 try:
2496 t.strftime('%y\ud800%m %H\ud800%M')
2497 except UnicodeEncodeError:
2498 pass
2499
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002500 def test_extract(self):
2501 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2502 self.assertEqual(dt.date(), date(2002, 3, 4))
2503 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2504
2505 def test_combine(self):
2506 d = date(2002, 3, 4)
2507 t = time(18, 45, 3, 1234)
2508 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2509 combine = self.theclass.combine
2510 dt = combine(d, t)
2511 self.assertEqual(dt, expected)
2512
2513 dt = combine(time=t, date=d)
2514 self.assertEqual(dt, expected)
2515
2516 self.assertEqual(d, dt.date())
2517 self.assertEqual(t, dt.time())
2518 self.assertEqual(dt, combine(dt.date(), dt.time()))
2519
2520 self.assertRaises(TypeError, combine) # need an arg
2521 self.assertRaises(TypeError, combine, d) # need two args
2522 self.assertRaises(TypeError, combine, t, d) # args reversed
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002523 self.assertRaises(TypeError, combine, d, t, 1) # wrong tzinfo type
2524 self.assertRaises(TypeError, combine, d, t, 1, 2) # too many args
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002525 self.assertRaises(TypeError, combine, "date", "time") # wrong types
2526 self.assertRaises(TypeError, combine, d, "time") # wrong type
2527 self.assertRaises(TypeError, combine, "date", t) # wrong type
2528
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002529 # tzinfo= argument
2530 dt = combine(d, t, timezone.utc)
2531 self.assertIs(dt.tzinfo, timezone.utc)
2532 dt = combine(d, t, tzinfo=timezone.utc)
2533 self.assertIs(dt.tzinfo, timezone.utc)
2534 t = time()
2535 dt = combine(dt, t)
2536 self.assertEqual(dt.date(), d)
2537 self.assertEqual(dt.time(), t)
2538
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002539 def test_replace(self):
2540 cls = self.theclass
2541 args = [1, 2, 3, 4, 5, 6, 7]
2542 base = cls(*args)
2543 self.assertEqual(base, base.replace())
2544
2545 i = 0
2546 for name, newval in (("year", 2),
2547 ("month", 3),
2548 ("day", 4),
2549 ("hour", 5),
2550 ("minute", 6),
2551 ("second", 7),
2552 ("microsecond", 8)):
2553 newargs = args[:]
2554 newargs[i] = newval
2555 expected = cls(*newargs)
2556 got = base.replace(**{name: newval})
2557 self.assertEqual(expected, got)
2558 i += 1
2559
2560 # Out of bounds.
2561 base = cls(2000, 2, 29)
2562 self.assertRaises(ValueError, base.replace, year=2001)
2563
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002564 @support.run_with_tz('EDT4')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002565 def test_astimezone(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002566 dt = self.theclass.now()
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002567 f = FixedOffset(44, "0044")
2568 dt_utc = dt.replace(tzinfo=timezone(timedelta(hours=-4), 'EDT'))
2569 self.assertEqual(dt.astimezone(), dt_utc) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002570 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
2571 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002572 dt_f = dt.replace(tzinfo=f) + timedelta(hours=4, minutes=44)
2573 self.assertEqual(dt.astimezone(f), dt_f) # naive
2574 self.assertEqual(dt.astimezone(tz=f), dt_f) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002575
2576 class Bogus(tzinfo):
2577 def utcoffset(self, dt): return None
2578 def dst(self, dt): return timedelta(0)
2579 bog = Bogus()
2580 self.assertRaises(ValueError, dt.astimezone, bog) # naive
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002581 self.assertEqual(dt.replace(tzinfo=bog).astimezone(f), dt_f)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002582
2583 class AlsoBogus(tzinfo):
2584 def utcoffset(self, dt): return timedelta(0)
2585 def dst(self, dt): return None
2586 alsobog = AlsoBogus()
2587 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
2588
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002589 class Broken(tzinfo):
2590 def utcoffset(self, dt): return 1
2591 def dst(self, dt): return 1
2592 broken = Broken()
2593 dt_broken = dt.replace(tzinfo=broken)
2594 with self.assertRaises(TypeError):
2595 dt_broken.astimezone()
2596
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002597 def test_subclass_datetime(self):
2598
2599 class C(self.theclass):
2600 theAnswer = 42
2601
2602 def __new__(cls, *args, **kws):
2603 temp = kws.copy()
2604 extra = temp.pop('extra')
2605 result = self.theclass.__new__(cls, *args, **temp)
2606 result.extra = extra
2607 return result
2608
2609 def newmeth(self, start):
2610 return start + self.year + self.month + self.second
2611
2612 args = 2003, 4, 14, 12, 13, 41
2613
2614 dt1 = self.theclass(*args)
2615 dt2 = C(*args, **{'extra': 7})
2616
2617 self.assertEqual(dt2.__class__, C)
2618 self.assertEqual(dt2.theAnswer, 42)
2619 self.assertEqual(dt2.extra, 7)
2620 self.assertEqual(dt1.toordinal(), dt2.toordinal())
2621 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
2622 dt1.second - 7)
2623
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002624 def test_subclass_alternate_constructors_datetime(self):
2625 # Test that alternate constructors call the constructor
2626 class DateTimeSubclass(self.theclass):
2627 def __new__(cls, *args, **kwargs):
2628 result = self.theclass.__new__(cls, *args, **kwargs)
2629 result.extra = 7
2630
2631 return result
2632
2633 args = (2003, 4, 14, 12, 30, 15, 123456)
2634 d_isoformat = '2003-04-14T12:30:15.123456' # Equivalent isoformat()
2635 utc_ts = 1050323415.123456 # UTC timestamp
2636
2637 base_d = DateTimeSubclass(*args)
2638 self.assertIsInstance(base_d, DateTimeSubclass)
2639 self.assertEqual(base_d.extra, 7)
2640
2641 # Timestamp depends on time zone, so we'll calculate the equivalent here
2642 ts = base_d.timestamp()
2643
2644 test_cases = [
Paul Ganssle89427cd2019-02-04 14:42:04 -05002645 ('fromtimestamp', (ts,), base_d),
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002646 # See https://bugs.python.org/issue32417
Paul Ganssle89427cd2019-02-04 14:42:04 -05002647 ('fromtimestamp', (ts, timezone.utc),
2648 base_d.astimezone(timezone.utc)),
2649 ('utcfromtimestamp', (utc_ts,), base_d),
2650 ('fromisoformat', (d_isoformat,), base_d),
2651 ('strptime', (d_isoformat, '%Y-%m-%dT%H:%M:%S.%f'), base_d),
2652 ('combine', (date(*args[0:3]), time(*args[3:])), base_d),
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002653 ]
2654
Paul Ganssle89427cd2019-02-04 14:42:04 -05002655 for constr_name, constr_args, expected in test_cases:
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002656 for base_obj in (DateTimeSubclass, base_d):
2657 # Test both the classmethod and method
2658 with self.subTest(base_obj_type=type(base_obj),
2659 constr_name=constr_name):
Paul Ganssle89427cd2019-02-04 14:42:04 -05002660 constructor = getattr(base_obj, constr_name)
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002661
Paul Ganssle89427cd2019-02-04 14:42:04 -05002662 dt = constructor(*constr_args)
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002663
2664 # Test that it creates the right subclass
2665 self.assertIsInstance(dt, DateTimeSubclass)
2666
2667 # Test that it's equal to the base object
Paul Ganssle89427cd2019-02-04 14:42:04 -05002668 self.assertEqual(dt, expected)
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002669
2670 # Test that it called the constructor
2671 self.assertEqual(dt.extra, 7)
2672
Paul Ganssle89427cd2019-02-04 14:42:04 -05002673 def test_subclass_now(self):
2674 # Test that alternate constructors call the constructor
2675 class DateTimeSubclass(self.theclass):
2676 def __new__(cls, *args, **kwargs):
2677 result = self.theclass.__new__(cls, *args, **kwargs)
2678 result.extra = 7
2679
2680 return result
2681
2682 test_cases = [
2683 ('now', 'now', {}),
2684 ('utcnow', 'utcnow', {}),
2685 ('now_utc', 'now', {'tz': timezone.utc}),
2686 ('now_fixed', 'now', {'tz': timezone(timedelta(hours=-5), "EST")}),
2687 ]
2688
2689 for name, meth_name, kwargs in test_cases:
2690 with self.subTest(name):
2691 constr = getattr(DateTimeSubclass, meth_name)
2692 dt = constr(**kwargs)
2693
2694 self.assertIsInstance(dt, DateTimeSubclass)
2695 self.assertEqual(dt.extra, 7)
2696
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002697 def test_fromisoformat_datetime(self):
2698 # Test that isoformat() is reversible
2699 base_dates = [
2700 (1, 1, 1),
2701 (1900, 1, 1),
2702 (2004, 11, 12),
2703 (2017, 5, 30)
2704 ]
2705
2706 base_times = [
2707 (0, 0, 0, 0),
2708 (0, 0, 0, 241000),
2709 (0, 0, 0, 234567),
2710 (12, 30, 45, 234567)
2711 ]
2712
2713 separators = [' ', 'T']
2714
2715 tzinfos = [None, timezone.utc,
2716 timezone(timedelta(hours=-5)),
2717 timezone(timedelta(hours=2))]
2718
2719 dts = [self.theclass(*date_tuple, *time_tuple, tzinfo=tzi)
2720 for date_tuple in base_dates
2721 for time_tuple in base_times
2722 for tzi in tzinfos]
2723
2724 for dt in dts:
2725 for sep in separators:
2726 dtstr = dt.isoformat(sep=sep)
2727
2728 with self.subTest(dtstr=dtstr):
2729 dt_rt = self.theclass.fromisoformat(dtstr)
2730 self.assertEqual(dt, dt_rt)
2731
2732 def test_fromisoformat_timezone(self):
2733 base_dt = self.theclass(2014, 12, 30, 12, 30, 45, 217456)
2734
2735 tzoffsets = [
2736 timedelta(hours=5), timedelta(hours=2),
2737 timedelta(hours=6, minutes=27),
2738 timedelta(hours=12, minutes=32, seconds=30),
2739 timedelta(hours=2, minutes=4, seconds=9, microseconds=123456)
2740 ]
2741
2742 tzoffsets += [-1 * td for td in tzoffsets]
2743
2744 tzinfos = [None, timezone.utc,
2745 timezone(timedelta(hours=0))]
2746
2747 tzinfos += [timezone(td) for td in tzoffsets]
2748
2749 for tzi in tzinfos:
2750 dt = base_dt.replace(tzinfo=tzi)
2751 dtstr = dt.isoformat()
2752
2753 with self.subTest(tstr=dtstr):
2754 dt_rt = self.theclass.fromisoformat(dtstr)
2755 assert dt == dt_rt, dt_rt
2756
2757 def test_fromisoformat_separators(self):
2758 separators = [
2759 ' ', 'T', '\u007f', # 1-bit widths
2760 '\u0080', 'ʁ', # 2-bit widths
2761 'ᛇ', '時', # 3-bit widths
Paul Ganssle096329f2018-08-23 11:06:20 -04002762 '🐍', # 4-bit widths
2763 '\ud800', # bpo-34454: Surrogate code point
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002764 ]
2765
2766 for sep in separators:
2767 dt = self.theclass(2018, 1, 31, 23, 59, 47, 124789)
2768 dtstr = dt.isoformat(sep=sep)
2769
2770 with self.subTest(dtstr=dtstr):
2771 dt_rt = self.theclass.fromisoformat(dtstr)
2772 self.assertEqual(dt, dt_rt)
2773
2774 def test_fromisoformat_ambiguous(self):
2775 # Test strings like 2018-01-31+12:15 (where +12:15 is not a time zone)
2776 separators = ['+', '-']
2777 for sep in separators:
2778 dt = self.theclass(2018, 1, 31, 12, 15)
2779 dtstr = dt.isoformat(sep=sep)
2780
2781 with self.subTest(dtstr=dtstr):
2782 dt_rt = self.theclass.fromisoformat(dtstr)
2783 self.assertEqual(dt, dt_rt)
2784
2785 def test_fromisoformat_timespecs(self):
2786 datetime_bases = [
2787 (2009, 12, 4, 8, 17, 45, 123456),
2788 (2009, 12, 4, 8, 17, 45, 0)]
2789
2790 tzinfos = [None, timezone.utc,
2791 timezone(timedelta(hours=-5)),
2792 timezone(timedelta(hours=2)),
2793 timezone(timedelta(hours=6, minutes=27))]
2794
2795 timespecs = ['hours', 'minutes', 'seconds',
2796 'milliseconds', 'microseconds']
2797
2798 for ip, ts in enumerate(timespecs):
2799 for tzi in tzinfos:
2800 for dt_tuple in datetime_bases:
2801 if ts == 'milliseconds':
2802 new_microseconds = 1000 * (dt_tuple[6] // 1000)
2803 dt_tuple = dt_tuple[0:6] + (new_microseconds,)
2804
2805 dt = self.theclass(*(dt_tuple[0:(4 + ip)]), tzinfo=tzi)
2806 dtstr = dt.isoformat(timespec=ts)
2807 with self.subTest(dtstr=dtstr):
2808 dt_rt = self.theclass.fromisoformat(dtstr)
2809 self.assertEqual(dt, dt_rt)
2810
2811 def test_fromisoformat_fails_datetime(self):
2812 # Test that fromisoformat() fails on invalid values
2813 bad_strs = [
2814 '', # Empty string
Paul Ganssle096329f2018-08-23 11:06:20 -04002815 '\ud800', # bpo-34454: Surrogate code point
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002816 '2009.04-19T03', # Wrong first separator
2817 '2009-04.19T03', # Wrong second separator
2818 '2009-04-19T0a', # Invalid hours
2819 '2009-04-19T03:1a:45', # Invalid minutes
2820 '2009-04-19T03:15:4a', # Invalid seconds
2821 '2009-04-19T03;15:45', # Bad first time separator
2822 '2009-04-19T03:15;45', # Bad second time separator
2823 '2009-04-19T03:15:4500:00', # Bad time zone separator
2824 '2009-04-19T03:15:45.2345', # Too many digits for milliseconds
2825 '2009-04-19T03:15:45.1234567', # Too many digits for microseconds
2826 '2009-04-19T03:15:45.123456+24:30', # Invalid time zone offset
2827 '2009-04-19T03:15:45.123456-24:30', # Invalid negative offset
2828 '2009-04-10ᛇᛇᛇᛇᛇ12:15', # Too many unicode separators
Paul Ganssle096329f2018-08-23 11:06:20 -04002829 '2009-04\ud80010T12:15', # Surrogate char in date
2830 '2009-04-10T12\ud80015', # Surrogate char in time
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002831 '2009-04-19T1', # Incomplete hours
2832 '2009-04-19T12:3', # Incomplete minutes
2833 '2009-04-19T12:30:4', # Incomplete seconds
2834 '2009-04-19T12:', # Ends with time separator
2835 '2009-04-19T12:30:', # Ends with time separator
2836 '2009-04-19T12:30:45.', # Ends with time separator
2837 '2009-04-19T12:30:45.123456+', # Ends with timzone separator
2838 '2009-04-19T12:30:45.123456-', # Ends with timzone separator
2839 '2009-04-19T12:30:45.123456-05:00a', # Extra text
2840 '2009-04-19T12:30:45.123-05:00a', # Extra text
2841 '2009-04-19T12:30:45-05:00a', # Extra text
2842 ]
2843
2844 for bad_str in bad_strs:
2845 with self.subTest(bad_str=bad_str):
2846 with self.assertRaises(ValueError):
2847 self.theclass.fromisoformat(bad_str)
2848
Paul Ganssle3df85402018-10-22 12:32:52 -04002849 def test_fromisoformat_fails_surrogate(self):
2850 # Test that when fromisoformat() fails with a surrogate character as
2851 # the separator, the error message contains the original string
2852 dtstr = "2018-01-03\ud80001:0113"
2853
2854 with self.assertRaisesRegex(ValueError, re.escape(repr(dtstr))):
2855 self.theclass.fromisoformat(dtstr)
2856
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002857 def test_fromisoformat_utc(self):
2858 dt_str = '2014-04-19T13:21:13+00:00'
2859 dt = self.theclass.fromisoformat(dt_str)
2860
2861 self.assertIs(dt.tzinfo, timezone.utc)
2862
2863 def test_fromisoformat_subclass(self):
2864 class DateTimeSubclass(self.theclass):
2865 pass
2866
2867 dt = DateTimeSubclass(2014, 12, 14, 9, 30, 45, 457390,
2868 tzinfo=timezone(timedelta(hours=10, minutes=45)))
2869
2870 dt_rt = DateTimeSubclass.fromisoformat(dt.isoformat())
2871
2872 self.assertEqual(dt, dt_rt)
2873 self.assertIsInstance(dt_rt, DateTimeSubclass)
2874
2875
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002876class TestSubclassDateTime(TestDateTime):
2877 theclass = SubclassDatetime
2878 # Override tests not designed for subclass
Zachary Ware9fe6d862013-12-08 00:20:35 -06002879 @unittest.skip('not appropriate for subclasses')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002880 def test_roundtrip(self):
2881 pass
2882
2883class SubclassTime(time):
2884 sub_var = 1
2885
2886class TestTime(HarmlessMixedComparison, unittest.TestCase):
2887
2888 theclass = time
2889
2890 def test_basic_attributes(self):
2891 t = self.theclass(12, 0)
2892 self.assertEqual(t.hour, 12)
2893 self.assertEqual(t.minute, 0)
2894 self.assertEqual(t.second, 0)
2895 self.assertEqual(t.microsecond, 0)
2896
2897 def test_basic_attributes_nonzero(self):
2898 # Make sure all attributes are non-zero so bugs in
2899 # bit-shifting access show up.
2900 t = self.theclass(12, 59, 59, 8000)
2901 self.assertEqual(t.hour, 12)
2902 self.assertEqual(t.minute, 59)
2903 self.assertEqual(t.second, 59)
2904 self.assertEqual(t.microsecond, 8000)
2905
2906 def test_roundtrip(self):
2907 t = self.theclass(1, 2, 3, 4)
2908
2909 # Verify t -> string -> time identity.
2910 s = repr(t)
2911 self.assertTrue(s.startswith('datetime.'))
2912 s = s[9:]
2913 t2 = eval(s)
2914 self.assertEqual(t, t2)
2915
2916 # Verify identity via reconstructing from pieces.
2917 t2 = self.theclass(t.hour, t.minute, t.second,
2918 t.microsecond)
2919 self.assertEqual(t, t2)
2920
2921 def test_comparing(self):
2922 args = [1, 2, 3, 4]
2923 t1 = self.theclass(*args)
2924 t2 = self.theclass(*args)
2925 self.assertEqual(t1, t2)
2926 self.assertTrue(t1 <= t2)
2927 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002928 self.assertFalse(t1 != t2)
2929 self.assertFalse(t1 < t2)
2930 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002931
2932 for i in range(len(args)):
2933 newargs = args[:]
2934 newargs[i] = args[i] + 1
2935 t2 = self.theclass(*newargs) # this is larger than t1
2936 self.assertTrue(t1 < t2)
2937 self.assertTrue(t2 > t1)
2938 self.assertTrue(t1 <= t2)
2939 self.assertTrue(t2 >= t1)
2940 self.assertTrue(t1 != t2)
2941 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002942 self.assertFalse(t1 == t2)
2943 self.assertFalse(t2 == t1)
2944 self.assertFalse(t1 > t2)
2945 self.assertFalse(t2 < t1)
2946 self.assertFalse(t1 >= t2)
2947 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002948
2949 for badarg in OTHERSTUFF:
2950 self.assertEqual(t1 == badarg, False)
2951 self.assertEqual(t1 != badarg, True)
2952 self.assertEqual(badarg == t1, False)
2953 self.assertEqual(badarg != t1, True)
2954
2955 self.assertRaises(TypeError, lambda: t1 <= badarg)
2956 self.assertRaises(TypeError, lambda: t1 < badarg)
2957 self.assertRaises(TypeError, lambda: t1 > badarg)
2958 self.assertRaises(TypeError, lambda: t1 >= badarg)
2959 self.assertRaises(TypeError, lambda: badarg <= t1)
2960 self.assertRaises(TypeError, lambda: badarg < t1)
2961 self.assertRaises(TypeError, lambda: badarg > t1)
2962 self.assertRaises(TypeError, lambda: badarg >= t1)
2963
2964 def test_bad_constructor_arguments(self):
2965 # bad hours
2966 self.theclass(0, 0) # no exception
2967 self.theclass(23, 0) # no exception
2968 self.assertRaises(ValueError, self.theclass, -1, 0)
2969 self.assertRaises(ValueError, self.theclass, 24, 0)
2970 # bad minutes
2971 self.theclass(23, 0) # no exception
2972 self.theclass(23, 59) # no exception
2973 self.assertRaises(ValueError, self.theclass, 23, -1)
2974 self.assertRaises(ValueError, self.theclass, 23, 60)
2975 # bad seconds
2976 self.theclass(23, 59, 0) # no exception
2977 self.theclass(23, 59, 59) # no exception
2978 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
2979 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
2980 # bad microseconds
2981 self.theclass(23, 59, 59, 0) # no exception
2982 self.theclass(23, 59, 59, 999999) # no exception
2983 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
2984 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
2985
2986 def test_hash_equality(self):
2987 d = self.theclass(23, 30, 17)
2988 e = self.theclass(23, 30, 17)
2989 self.assertEqual(d, e)
2990 self.assertEqual(hash(d), hash(e))
2991
2992 dic = {d: 1}
2993 dic[e] = 2
2994 self.assertEqual(len(dic), 1)
2995 self.assertEqual(dic[d], 2)
2996 self.assertEqual(dic[e], 2)
2997
2998 d = self.theclass(0, 5, 17)
2999 e = self.theclass(0, 5, 17)
3000 self.assertEqual(d, e)
3001 self.assertEqual(hash(d), hash(e))
3002
3003 dic = {d: 1}
3004 dic[e] = 2
3005 self.assertEqual(len(dic), 1)
3006 self.assertEqual(dic[d], 2)
3007 self.assertEqual(dic[e], 2)
3008
3009 def test_isoformat(self):
3010 t = self.theclass(4, 5, 1, 123)
3011 self.assertEqual(t.isoformat(), "04:05:01.000123")
3012 self.assertEqual(t.isoformat(), str(t))
3013
3014 t = self.theclass()
3015 self.assertEqual(t.isoformat(), "00:00:00")
3016 self.assertEqual(t.isoformat(), str(t))
3017
3018 t = self.theclass(microsecond=1)
3019 self.assertEqual(t.isoformat(), "00:00:00.000001")
3020 self.assertEqual(t.isoformat(), str(t))
3021
3022 t = self.theclass(microsecond=10)
3023 self.assertEqual(t.isoformat(), "00:00:00.000010")
3024 self.assertEqual(t.isoformat(), str(t))
3025
3026 t = self.theclass(microsecond=100)
3027 self.assertEqual(t.isoformat(), "00:00:00.000100")
3028 self.assertEqual(t.isoformat(), str(t))
3029
3030 t = self.theclass(microsecond=1000)
3031 self.assertEqual(t.isoformat(), "00:00:00.001000")
3032 self.assertEqual(t.isoformat(), str(t))
3033
3034 t = self.theclass(microsecond=10000)
3035 self.assertEqual(t.isoformat(), "00:00:00.010000")
3036 self.assertEqual(t.isoformat(), str(t))
3037
3038 t = self.theclass(microsecond=100000)
3039 self.assertEqual(t.isoformat(), "00:00:00.100000")
3040 self.assertEqual(t.isoformat(), str(t))
3041
Alexander Belopolskya2998a62016-03-06 14:58:43 -05003042 t = self.theclass(hour=12, minute=34, second=56, microsecond=123456)
3043 self.assertEqual(t.isoformat(timespec='hours'), "12")
3044 self.assertEqual(t.isoformat(timespec='minutes'), "12:34")
3045 self.assertEqual(t.isoformat(timespec='seconds'), "12:34:56")
3046 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.123")
3047 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.123456")
3048 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56.123456")
3049 self.assertRaises(ValueError, t.isoformat, timespec='monkey')
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03003050 # bpo-34482: Check that surrogates are handled properly.
3051 self.assertRaises(ValueError, t.isoformat, timespec='\ud800')
Alexander Belopolskya2998a62016-03-06 14:58:43 -05003052
3053 t = self.theclass(hour=12, minute=34, second=56, microsecond=999500)
3054 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.999")
3055
3056 t = self.theclass(hour=12, minute=34, second=56, microsecond=0)
3057 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.000")
3058 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.000000")
3059 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56")
3060
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003061 def test_isoformat_timezone(self):
3062 tzoffsets = [
3063 ('05:00', timedelta(hours=5)),
3064 ('02:00', timedelta(hours=2)),
3065 ('06:27', timedelta(hours=6, minutes=27)),
3066 ('12:32:30', timedelta(hours=12, minutes=32, seconds=30)),
3067 ('02:04:09.123456', timedelta(hours=2, minutes=4, seconds=9, microseconds=123456))
3068 ]
3069
3070 tzinfos = [
3071 ('', None),
3072 ('+00:00', timezone.utc),
3073 ('+00:00', timezone(timedelta(0))),
3074 ]
3075
3076 tzinfos += [
3077 (prefix + expected, timezone(sign * td))
3078 for expected, td in tzoffsets
3079 for prefix, sign in [('-', -1), ('+', 1)]
3080 ]
3081
3082 t_base = self.theclass(12, 37, 9)
3083 exp_base = '12:37:09'
3084
3085 for exp_tz, tzi in tzinfos:
3086 t = t_base.replace(tzinfo=tzi)
3087 exp = exp_base + exp_tz
3088 with self.subTest(tzi=tzi):
3089 assert t.isoformat() == exp
3090
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003091 def test_1653736(self):
3092 # verify it doesn't accept extra keyword arguments
3093 t = self.theclass(second=1)
3094 self.assertRaises(TypeError, t.isoformat, foo=3)
3095
3096 def test_strftime(self):
3097 t = self.theclass(1, 2, 3, 4)
3098 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
3099 # A naive object replaces %z and %Z with empty strings.
3100 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
3101
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03003102 # bpo-34482: Check that surrogates don't cause a crash.
3103 try:
3104 t.strftime('%H\ud800%M')
3105 except UnicodeEncodeError:
3106 pass
3107
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003108 def test_format(self):
3109 t = self.theclass(1, 2, 3, 4)
3110 self.assertEqual(t.__format__(''), str(t))
3111
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02003112 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04003113 t.__format__(123)
3114
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003115 # check that a derived class's __str__() gets called
3116 class A(self.theclass):
3117 def __str__(self):
3118 return 'A'
3119 a = A(1, 2, 3, 4)
3120 self.assertEqual(a.__format__(''), 'A')
3121
3122 # check that a derived class's strftime gets called
3123 class B(self.theclass):
3124 def strftime(self, format_spec):
3125 return 'B'
3126 b = B(1, 2, 3, 4)
3127 self.assertEqual(b.__format__(''), str(t))
3128
3129 for fmt in ['%H %M %S',
3130 ]:
3131 self.assertEqual(t.__format__(fmt), t.strftime(fmt))
3132 self.assertEqual(a.__format__(fmt), t.strftime(fmt))
3133 self.assertEqual(b.__format__(fmt), 'B')
3134
3135 def test_str(self):
3136 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
3137 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
3138 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
3139 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
3140 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
3141
3142 def test_repr(self):
3143 name = 'datetime.' + self.theclass.__name__
3144 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
3145 "%s(1, 2, 3, 4)" % name)
3146 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
3147 "%s(10, 2, 3, 4000)" % name)
3148 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
3149 "%s(0, 2, 3, 400000)" % name)
3150 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
3151 "%s(12, 2, 3)" % name)
3152 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
3153 "%s(23, 15)" % name)
3154
3155 def test_resolution_info(self):
3156 self.assertIsInstance(self.theclass.min, self.theclass)
3157 self.assertIsInstance(self.theclass.max, self.theclass)
3158 self.assertIsInstance(self.theclass.resolution, timedelta)
3159 self.assertTrue(self.theclass.max > self.theclass.min)
3160
3161 def test_pickling(self):
3162 args = 20, 59, 16, 64**2
3163 orig = self.theclass(*args)
3164 for pickler, unpickler, proto in pickle_choices:
3165 green = pickler.dumps(orig, proto)
3166 derived = unpickler.loads(green)
3167 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003168 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003169
3170 def test_pickling_subclass_time(self):
3171 args = 20, 59, 16, 64**2
3172 orig = SubclassTime(*args)
3173 for pickler, unpickler, proto in pickle_choices:
3174 green = pickler.dumps(orig, proto)
3175 derived = unpickler.loads(green)
3176 self.assertEqual(orig, derived)
3177
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02003178 def test_compat_unpickle(self):
3179 tests = [
3180 b"cdatetime\ntime\n(S'\\x14;\\x10\\x00\\x10\\x00'\ntR.",
3181 b'cdatetime\ntime\n(U\x06\x14;\x10\x00\x10\x00tR.',
3182 b'\x80\x02cdatetime\ntime\nU\x06\x14;\x10\x00\x10\x00\x85R.',
3183 ]
3184 args = 20, 59, 16, 64**2
3185 expected = self.theclass(*args)
3186 for data in tests:
3187 for loads in pickle_loads:
3188 derived = loads(data, encoding='latin1')
3189 self.assertEqual(derived, expected)
3190
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003191 def test_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003192 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003193 cls = self.theclass
3194 self.assertTrue(cls(1))
3195 self.assertTrue(cls(0, 1))
3196 self.assertTrue(cls(0, 0, 1))
3197 self.assertTrue(cls(0, 0, 0, 1))
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003198 self.assertTrue(cls(0))
3199 self.assertTrue(cls())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003200
3201 def test_replace(self):
3202 cls = self.theclass
3203 args = [1, 2, 3, 4]
3204 base = cls(*args)
3205 self.assertEqual(base, base.replace())
3206
3207 i = 0
3208 for name, newval in (("hour", 5),
3209 ("minute", 6),
3210 ("second", 7),
3211 ("microsecond", 8)):
3212 newargs = args[:]
3213 newargs[i] = newval
3214 expected = cls(*newargs)
3215 got = base.replace(**{name: newval})
3216 self.assertEqual(expected, got)
3217 i += 1
3218
3219 # Out of bounds.
3220 base = cls(1)
3221 self.assertRaises(ValueError, base.replace, hour=24)
3222 self.assertRaises(ValueError, base.replace, minute=-1)
3223 self.assertRaises(ValueError, base.replace, second=100)
3224 self.assertRaises(ValueError, base.replace, microsecond=1000000)
3225
Paul Ganssle191e9932017-11-09 16:34:29 -05003226 def test_subclass_replace(self):
3227 class TimeSubclass(self.theclass):
3228 pass
3229
3230 ctime = TimeSubclass(12, 30)
3231 self.assertIs(type(ctime.replace(hour=10)), TimeSubclass)
3232
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003233 def test_subclass_time(self):
3234
3235 class C(self.theclass):
3236 theAnswer = 42
3237
3238 def __new__(cls, *args, **kws):
3239 temp = kws.copy()
3240 extra = temp.pop('extra')
3241 result = self.theclass.__new__(cls, *args, **temp)
3242 result.extra = extra
3243 return result
3244
3245 def newmeth(self, start):
3246 return start + self.hour + self.second
3247
3248 args = 4, 5, 6
3249
3250 dt1 = self.theclass(*args)
3251 dt2 = C(*args, **{'extra': 7})
3252
3253 self.assertEqual(dt2.__class__, C)
3254 self.assertEqual(dt2.theAnswer, 42)
3255 self.assertEqual(dt2.extra, 7)
3256 self.assertEqual(dt1.isoformat(), dt2.isoformat())
3257 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
3258
3259 def test_backdoor_resistance(self):
3260 # see TestDate.test_backdoor_resistance().
3261 base = '2:59.0'
3262 for hour_byte in ' ', '9', chr(24), '\xff':
3263 self.assertRaises(TypeError, self.theclass,
3264 hour_byte + base[1:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04003265 # Good bytes, but bad tzinfo:
3266 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
3267 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003268
3269# A mixin for classes with a tzinfo= argument. Subclasses must define
Martin Panter46f50722016-05-26 05:35:26 +00003270# theclass as a class attribute, and theclass(1, 1, 1, tzinfo=whatever)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003271# must be legit (which is true for time and datetime).
3272class TZInfoBase:
3273
3274 def test_argument_passing(self):
3275 cls = self.theclass
3276 # A datetime passes itself on, a time passes None.
3277 class introspective(tzinfo):
3278 def tzname(self, dt): return dt and "real" or "none"
3279 def utcoffset(self, dt):
3280 return timedelta(minutes = dt and 42 or -42)
3281 dst = utcoffset
3282
3283 obj = cls(1, 2, 3, tzinfo=introspective())
3284
3285 expected = cls is time and "none" or "real"
3286 self.assertEqual(obj.tzname(), expected)
3287
3288 expected = timedelta(minutes=(cls is time and -42 or 42))
3289 self.assertEqual(obj.utcoffset(), expected)
3290 self.assertEqual(obj.dst(), expected)
3291
3292 def test_bad_tzinfo_classes(self):
3293 cls = self.theclass
3294 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
3295
3296 class NiceTry(object):
3297 def __init__(self): pass
3298 def utcoffset(self, dt): pass
3299 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
3300
3301 class BetterTry(tzinfo):
3302 def __init__(self): pass
3303 def utcoffset(self, dt): pass
3304 b = BetterTry()
3305 t = cls(1, 1, 1, tzinfo=b)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003306 self.assertIs(t.tzinfo, b)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003307
3308 def test_utc_offset_out_of_bounds(self):
3309 class Edgy(tzinfo):
3310 def __init__(self, offset):
3311 self.offset = timedelta(minutes=offset)
3312 def utcoffset(self, dt):
3313 return self.offset
3314
3315 cls = self.theclass
3316 for offset, legit in ((-1440, False),
3317 (-1439, True),
3318 (1439, True),
3319 (1440, False)):
3320 if cls is time:
3321 t = cls(1, 2, 3, tzinfo=Edgy(offset))
3322 elif cls is datetime:
3323 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
3324 else:
3325 assert 0, "impossible"
3326 if legit:
3327 aofs = abs(offset)
3328 h, m = divmod(aofs, 60)
3329 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
3330 if isinstance(t, datetime):
3331 t = t.timetz()
3332 self.assertEqual(str(t), "01:02:03" + tag)
3333 else:
3334 self.assertRaises(ValueError, str, t)
3335
3336 def test_tzinfo_classes(self):
3337 cls = self.theclass
3338 class C1(tzinfo):
3339 def utcoffset(self, dt): return None
3340 def dst(self, dt): return None
3341 def tzname(self, dt): return None
3342 for t in (cls(1, 1, 1),
3343 cls(1, 1, 1, tzinfo=None),
3344 cls(1, 1, 1, tzinfo=C1())):
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003345 self.assertIsNone(t.utcoffset())
3346 self.assertIsNone(t.dst())
3347 self.assertIsNone(t.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003348
3349 class C3(tzinfo):
3350 def utcoffset(self, dt): return timedelta(minutes=-1439)
3351 def dst(self, dt): return timedelta(minutes=1439)
3352 def tzname(self, dt): return "aname"
3353 t = cls(1, 1, 1, tzinfo=C3())
3354 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
3355 self.assertEqual(t.dst(), timedelta(minutes=1439))
3356 self.assertEqual(t.tzname(), "aname")
3357
3358 # Wrong types.
3359 class C4(tzinfo):
3360 def utcoffset(self, dt): return "aname"
3361 def dst(self, dt): return 7
3362 def tzname(self, dt): return 0
3363 t = cls(1, 1, 1, tzinfo=C4())
3364 self.assertRaises(TypeError, t.utcoffset)
3365 self.assertRaises(TypeError, t.dst)
3366 self.assertRaises(TypeError, t.tzname)
3367
3368 # Offset out of range.
3369 class C6(tzinfo):
3370 def utcoffset(self, dt): return timedelta(hours=-24)
3371 def dst(self, dt): return timedelta(hours=24)
3372 t = cls(1, 1, 1, tzinfo=C6())
3373 self.assertRaises(ValueError, t.utcoffset)
3374 self.assertRaises(ValueError, t.dst)
3375
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04003376 # Not a whole number of seconds.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003377 class C7(tzinfo):
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04003378 def utcoffset(self, dt): return timedelta(microseconds=61)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003379 def dst(self, dt): return timedelta(microseconds=-81)
3380 t = cls(1, 1, 1, tzinfo=C7())
Alexander Belopolsky018d3532017-07-31 10:26:50 -04003381 self.assertEqual(t.utcoffset(), timedelta(microseconds=61))
3382 self.assertEqual(t.dst(), timedelta(microseconds=-81))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003383
3384 def test_aware_compare(self):
3385 cls = self.theclass
3386
3387 # Ensure that utcoffset() gets ignored if the comparands have
3388 # the same tzinfo member.
3389 class OperandDependentOffset(tzinfo):
3390 def utcoffset(self, t):
3391 if t.minute < 10:
3392 # d0 and d1 equal after adjustment
3393 return timedelta(minutes=t.minute)
3394 else:
3395 # d2 off in the weeds
3396 return timedelta(minutes=59)
3397
3398 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
3399 d0 = base.replace(minute=3)
3400 d1 = base.replace(minute=9)
3401 d2 = base.replace(minute=11)
3402 for x in d0, d1, d2:
3403 for y in d0, d1, d2:
3404 for op in lt, le, gt, ge, eq, ne:
3405 got = op(x, y)
3406 expected = op(x.minute, y.minute)
3407 self.assertEqual(got, expected)
3408
3409 # However, if they're different members, uctoffset is not ignored.
3410 # Note that a time can't actually have an operand-depedent offset,
3411 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
3412 # so skip this test for time.
3413 if cls is not time:
3414 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
3415 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
3416 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
3417 for x in d0, d1, d2:
3418 for y in d0, d1, d2:
3419 got = (x > y) - (x < y)
3420 if (x is d0 or x is d1) and (y is d0 or y is d1):
3421 expected = 0
3422 elif x is y is d2:
3423 expected = 0
3424 elif x is d2:
3425 expected = -1
3426 else:
3427 assert y is d2
3428 expected = 1
3429 self.assertEqual(got, expected)
3430
3431
3432# Testing time objects with a non-None tzinfo.
3433class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
3434 theclass = time
3435
3436 def test_empty(self):
3437 t = self.theclass()
3438 self.assertEqual(t.hour, 0)
3439 self.assertEqual(t.minute, 0)
3440 self.assertEqual(t.second, 0)
3441 self.assertEqual(t.microsecond, 0)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003442 self.assertIsNone(t.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003443
3444 def test_zones(self):
3445 est = FixedOffset(-300, "EST", 1)
3446 utc = FixedOffset(0, "UTC", -2)
3447 met = FixedOffset(60, "MET", 3)
3448 t1 = time( 7, 47, tzinfo=est)
3449 t2 = time(12, 47, tzinfo=utc)
3450 t3 = time(13, 47, tzinfo=met)
3451 t4 = time(microsecond=40)
3452 t5 = time(microsecond=40, tzinfo=utc)
3453
3454 self.assertEqual(t1.tzinfo, est)
3455 self.assertEqual(t2.tzinfo, utc)
3456 self.assertEqual(t3.tzinfo, met)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003457 self.assertIsNone(t4.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003458 self.assertEqual(t5.tzinfo, utc)
3459
3460 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
3461 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
3462 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003463 self.assertIsNone(t4.utcoffset())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003464 self.assertRaises(TypeError, t1.utcoffset, "no args")
3465
3466 self.assertEqual(t1.tzname(), "EST")
3467 self.assertEqual(t2.tzname(), "UTC")
3468 self.assertEqual(t3.tzname(), "MET")
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003469 self.assertIsNone(t4.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003470 self.assertRaises(TypeError, t1.tzname, "no args")
3471
3472 self.assertEqual(t1.dst(), timedelta(minutes=1))
3473 self.assertEqual(t2.dst(), timedelta(minutes=-2))
3474 self.assertEqual(t3.dst(), timedelta(minutes=3))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003475 self.assertIsNone(t4.dst())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003476 self.assertRaises(TypeError, t1.dst, "no args")
3477
3478 self.assertEqual(hash(t1), hash(t2))
3479 self.assertEqual(hash(t1), hash(t3))
3480 self.assertEqual(hash(t2), hash(t3))
3481
3482 self.assertEqual(t1, t2)
3483 self.assertEqual(t1, t3)
3484 self.assertEqual(t2, t3)
Alexander Belopolsky08313822012-06-15 20:19:47 -04003485 self.assertNotEqual(t4, t5) # mixed tz-aware & naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003486 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
3487 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
3488
3489 self.assertEqual(str(t1), "07:47:00-05:00")
3490 self.assertEqual(str(t2), "12:47:00+00:00")
3491 self.assertEqual(str(t3), "13:47:00+01:00")
3492 self.assertEqual(str(t4), "00:00:00.000040")
3493 self.assertEqual(str(t5), "00:00:00.000040+00:00")
3494
3495 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
3496 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
3497 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
3498 self.assertEqual(t4.isoformat(), "00:00:00.000040")
3499 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
3500
3501 d = 'datetime.time'
3502 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
3503 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
3504 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
3505 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
3506 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
3507
3508 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
3509 "07:47:00 %Z=EST %z=-0500")
3510 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
3511 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
3512
3513 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
3514 t1 = time(23, 59, tzinfo=yuck)
3515 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
3516 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
3517
3518 # Check that an invalid tzname result raises an exception.
3519 class Badtzname(tzinfo):
Alexander Belopolskye239d232010-12-08 23:31:48 +00003520 tz = 42
3521 def tzname(self, dt): return self.tz
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003522 t = time(2, 3, 4, tzinfo=Badtzname())
3523 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
3524 self.assertRaises(TypeError, t.strftime, "%Z")
3525
Alexander Belopolskye239d232010-12-08 23:31:48 +00003526 # Issue #6697:
Utkarsh Upadhyay287c5592017-07-21 02:14:54 +02003527 if '_Fast' in self.__class__.__name__:
Alexander Belopolskye239d232010-12-08 23:31:48 +00003528 Badtzname.tz = '\ud800'
3529 self.assertRaises(ValueError, t.strftime, "%Z")
3530
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003531 def test_hash_edge_cases(self):
3532 # Offsets that overflow a basic time.
3533 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
3534 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
3535 self.assertEqual(hash(t1), hash(t2))
3536
3537 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
3538 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
3539 self.assertEqual(hash(t1), hash(t2))
3540
3541 def test_pickling(self):
3542 # Try one without a tzinfo.
3543 args = 20, 59, 16, 64**2
3544 orig = self.theclass(*args)
3545 for pickler, unpickler, proto in pickle_choices:
3546 green = pickler.dumps(orig, proto)
3547 derived = unpickler.loads(green)
3548 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003549 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003550
3551 # Try one with a tzinfo.
3552 tinfo = PicklableFixedOffset(-300, 'cookie')
3553 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
3554 for pickler, unpickler, proto in pickle_choices:
3555 green = pickler.dumps(orig, proto)
3556 derived = unpickler.loads(green)
3557 self.assertEqual(orig, derived)
3558 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3559 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3560 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003561 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003562
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02003563 def test_compat_unpickle(self):
3564 tests = [
3565 b"cdatetime\ntime\n(S'\\x05\\x06\\x07\\x01\\xe2@'\n"
3566 b"ctest.datetimetester\nPicklableFixedOffset\n(tR"
3567 b"(dS'_FixedOffset__offset'\ncdatetime\ntimedelta\n"
3568 b"(I-1\nI68400\nI0\ntRs"
3569 b"S'_FixedOffset__dstoffset'\nNs"
3570 b"S'_FixedOffset__name'\nS'cookie'\nsbtR.",
3571
3572 b'cdatetime\ntime\n(U\x06\x05\x06\x07\x01\xe2@'
3573 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
3574 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
3575 b'(J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00tR'
3576 b'U\x17_FixedOffset__dstoffsetN'
3577 b'U\x12_FixedOffset__nameU\x06cookieubtR.',
3578
3579 b'\x80\x02cdatetime\ntime\nU\x06\x05\x06\x07\x01\xe2@'
3580 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
3581 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
3582 b'J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00\x87R'
3583 b'U\x17_FixedOffset__dstoffsetN'
3584 b'U\x12_FixedOffset__nameU\x06cookieub\x86R.',
3585 ]
3586
3587 tinfo = PicklableFixedOffset(-300, 'cookie')
3588 expected = self.theclass(5, 6, 7, 123456, tzinfo=tinfo)
3589 for data in tests:
3590 for loads in pickle_loads:
3591 derived = loads(data, encoding='latin1')
3592 self.assertEqual(derived, expected, repr(data))
3593 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3594 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3595 self.assertEqual(derived.tzname(), 'cookie')
3596
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003597 def test_more_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003598 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003599 cls = self.theclass
3600
3601 t = cls(0, tzinfo=FixedOffset(-300, ""))
3602 self.assertTrue(t)
3603
3604 t = cls(5, tzinfo=FixedOffset(-300, ""))
3605 self.assertTrue(t)
3606
3607 t = cls(5, tzinfo=FixedOffset(300, ""))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003608 self.assertTrue(t)
3609
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003610 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
3611 self.assertTrue(t)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003612
3613 def test_replace(self):
3614 cls = self.theclass
3615 z100 = FixedOffset(100, "+100")
3616 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3617 args = [1, 2, 3, 4, z100]
3618 base = cls(*args)
3619 self.assertEqual(base, base.replace())
3620
3621 i = 0
3622 for name, newval in (("hour", 5),
3623 ("minute", 6),
3624 ("second", 7),
3625 ("microsecond", 8),
3626 ("tzinfo", zm200)):
3627 newargs = args[:]
3628 newargs[i] = newval
3629 expected = cls(*newargs)
3630 got = base.replace(**{name: newval})
3631 self.assertEqual(expected, got)
3632 i += 1
3633
3634 # Ensure we can get rid of a tzinfo.
3635 self.assertEqual(base.tzname(), "+100")
3636 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003637 self.assertIsNone(base2.tzinfo)
3638 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003639
3640 # Ensure we can add one.
3641 base3 = base2.replace(tzinfo=z100)
3642 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003643 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003644
3645 # Out of bounds.
3646 base = cls(1)
3647 self.assertRaises(ValueError, base.replace, hour=24)
3648 self.assertRaises(ValueError, base.replace, minute=-1)
3649 self.assertRaises(ValueError, base.replace, second=100)
3650 self.assertRaises(ValueError, base.replace, microsecond=1000000)
3651
3652 def test_mixed_compare(self):
3653 t1 = time(1, 2, 3)
3654 t2 = time(1, 2, 3)
3655 self.assertEqual(t1, t2)
3656 t2 = t2.replace(tzinfo=None)
3657 self.assertEqual(t1, t2)
3658 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3659 self.assertEqual(t1, t2)
3660 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04003661 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003662
3663 # In time w/ identical tzinfo objects, utcoffset is ignored.
3664 class Varies(tzinfo):
3665 def __init__(self):
3666 self.offset = timedelta(minutes=22)
3667 def utcoffset(self, t):
3668 self.offset += timedelta(minutes=1)
3669 return self.offset
3670
3671 v = Varies()
3672 t1 = t2.replace(tzinfo=v)
3673 t2 = t2.replace(tzinfo=v)
3674 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3675 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3676 self.assertEqual(t1, t2)
3677
3678 # But if they're not identical, it isn't ignored.
3679 t2 = t2.replace(tzinfo=Varies())
3680 self.assertTrue(t1 < t2) # t1's offset counter still going up
3681
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003682 def test_fromisoformat(self):
3683 time_examples = [
3684 (0, 0, 0, 0),
3685 (23, 59, 59, 999999),
3686 ]
3687
3688 hh = (9, 12, 20)
3689 mm = (5, 30)
3690 ss = (4, 45)
3691 usec = (0, 245000, 678901)
3692
3693 time_examples += list(itertools.product(hh, mm, ss, usec))
3694
3695 tzinfos = [None, timezone.utc,
3696 timezone(timedelta(hours=2)),
3697 timezone(timedelta(hours=6, minutes=27))]
3698
3699 for ttup in time_examples:
3700 for tzi in tzinfos:
3701 t = self.theclass(*ttup, tzinfo=tzi)
3702 tstr = t.isoformat()
3703
3704 with self.subTest(tstr=tstr):
3705 t_rt = self.theclass.fromisoformat(tstr)
3706 self.assertEqual(t, t_rt)
3707
3708 def test_fromisoformat_timezone(self):
3709 base_time = self.theclass(12, 30, 45, 217456)
3710
3711 tzoffsets = [
3712 timedelta(hours=5), timedelta(hours=2),
3713 timedelta(hours=6, minutes=27),
3714 timedelta(hours=12, minutes=32, seconds=30),
3715 timedelta(hours=2, minutes=4, seconds=9, microseconds=123456)
3716 ]
3717
3718 tzoffsets += [-1 * td for td in tzoffsets]
3719
3720 tzinfos = [None, timezone.utc,
3721 timezone(timedelta(hours=0))]
3722
3723 tzinfos += [timezone(td) for td in tzoffsets]
3724
3725 for tzi in tzinfos:
3726 t = base_time.replace(tzinfo=tzi)
3727 tstr = t.isoformat()
3728
3729 with self.subTest(tstr=tstr):
3730 t_rt = self.theclass.fromisoformat(tstr)
3731 assert t == t_rt, t_rt
3732
3733 def test_fromisoformat_timespecs(self):
3734 time_bases = [
3735 (8, 17, 45, 123456),
3736 (8, 17, 45, 0)
3737 ]
3738
3739 tzinfos = [None, timezone.utc,
3740 timezone(timedelta(hours=-5)),
3741 timezone(timedelta(hours=2)),
3742 timezone(timedelta(hours=6, minutes=27))]
3743
3744 timespecs = ['hours', 'minutes', 'seconds',
3745 'milliseconds', 'microseconds']
3746
3747 for ip, ts in enumerate(timespecs):
3748 for tzi in tzinfos:
3749 for t_tuple in time_bases:
3750 if ts == 'milliseconds':
3751 new_microseconds = 1000 * (t_tuple[-1] // 1000)
3752 t_tuple = t_tuple[0:-1] + (new_microseconds,)
3753
3754 t = self.theclass(*(t_tuple[0:(1 + ip)]), tzinfo=tzi)
3755 tstr = t.isoformat(timespec=ts)
3756 with self.subTest(tstr=tstr):
3757 t_rt = self.theclass.fromisoformat(tstr)
3758 self.assertEqual(t, t_rt)
3759
3760 def test_fromisoformat_fails(self):
3761 bad_strs = [
3762 '', # Empty string
Paul Ganssle096329f2018-08-23 11:06:20 -04003763 '12\ud80000', # Invalid separator - surrogate char
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003764 '12:', # Ends on a separator
3765 '12:30:', # Ends on a separator
3766 '12:30:15.', # Ends on a separator
3767 '1', # Incomplete hours
3768 '12:3', # Incomplete minutes
3769 '12:30:1', # Incomplete seconds
3770 '1a:30:45.334034', # Invalid character in hours
3771 '12:a0:45.334034', # Invalid character in minutes
3772 '12:30:a5.334034', # Invalid character in seconds
3773 '12:30:45.1234', # Too many digits for milliseconds
3774 '12:30:45.1234567', # Too many digits for microseconds
3775 '12:30:45.123456+24:30', # Invalid time zone offset
3776 '12:30:45.123456-24:30', # Invalid negative offset
3777 '12:30:45', # Uses full-width unicode colons
3778 '12:30:45․123456', # Uses \u2024 in place of decimal point
3779 '12:30:45a', # Extra at tend of basic time
3780 '12:30:45.123a', # Extra at end of millisecond time
3781 '12:30:45.123456a', # Extra at end of microsecond time
3782 '12:30:45.123456+12:00:30a', # Extra at end of full time
3783 ]
3784
3785 for bad_str in bad_strs:
3786 with self.subTest(bad_str=bad_str):
3787 with self.assertRaises(ValueError):
3788 self.theclass.fromisoformat(bad_str)
3789
3790 def test_fromisoformat_fails_typeerror(self):
3791 # Test the fromisoformat fails when passed the wrong type
3792 import io
3793
3794 bad_types = [b'12:30:45', None, io.StringIO('12:30:45')]
3795
3796 for bad_type in bad_types:
3797 with self.assertRaises(TypeError):
3798 self.theclass.fromisoformat(bad_type)
3799
3800 def test_fromisoformat_subclass(self):
3801 class TimeSubclass(self.theclass):
3802 pass
3803
3804 tsc = TimeSubclass(12, 14, 45, 203745, tzinfo=timezone.utc)
3805 tsc_rt = TimeSubclass.fromisoformat(tsc.isoformat())
3806
3807 self.assertEqual(tsc, tsc_rt)
3808 self.assertIsInstance(tsc_rt, TimeSubclass)
3809
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003810 def test_subclass_timetz(self):
3811
3812 class C(self.theclass):
3813 theAnswer = 42
3814
3815 def __new__(cls, *args, **kws):
3816 temp = kws.copy()
3817 extra = temp.pop('extra')
3818 result = self.theclass.__new__(cls, *args, **temp)
3819 result.extra = extra
3820 return result
3821
3822 def newmeth(self, start):
3823 return start + self.hour + self.second
3824
3825 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3826
3827 dt1 = self.theclass(*args)
3828 dt2 = C(*args, **{'extra': 7})
3829
3830 self.assertEqual(dt2.__class__, C)
3831 self.assertEqual(dt2.theAnswer, 42)
3832 self.assertEqual(dt2.extra, 7)
3833 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3834 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
3835
3836
3837# Testing datetime objects with a non-None tzinfo.
3838
3839class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
3840 theclass = datetime
3841
3842 def test_trivial(self):
3843 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
3844 self.assertEqual(dt.year, 1)
3845 self.assertEqual(dt.month, 2)
3846 self.assertEqual(dt.day, 3)
3847 self.assertEqual(dt.hour, 4)
3848 self.assertEqual(dt.minute, 5)
3849 self.assertEqual(dt.second, 6)
3850 self.assertEqual(dt.microsecond, 7)
3851 self.assertEqual(dt.tzinfo, None)
3852
3853 def test_even_more_compare(self):
3854 # The test_compare() and test_more_compare() inherited from TestDate
3855 # and TestDateTime covered non-tzinfo cases.
3856
3857 # Smallest possible after UTC adjustment.
3858 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
3859 # Largest possible after UTC adjustment.
3860 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3861 tzinfo=FixedOffset(-1439, ""))
3862
3863 # Make sure those compare correctly, and w/o overflow.
3864 self.assertTrue(t1 < t2)
3865 self.assertTrue(t1 != t2)
3866 self.assertTrue(t2 > t1)
3867
3868 self.assertEqual(t1, t1)
3869 self.assertEqual(t2, t2)
3870
3871 # Equal afer adjustment.
3872 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
3873 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
3874 self.assertEqual(t1, t2)
3875
3876 # Change t1 not to subtract a minute, and t1 should be larger.
3877 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
3878 self.assertTrue(t1 > t2)
3879
3880 # Change t1 to subtract 2 minutes, and t1 should be smaller.
3881 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
3882 self.assertTrue(t1 < t2)
3883
3884 # Back to the original t1, but make seconds resolve it.
3885 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3886 second=1)
3887 self.assertTrue(t1 > t2)
3888
3889 # Likewise, but make microseconds resolve it.
3890 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3891 microsecond=1)
3892 self.assertTrue(t1 > t2)
3893
Alexander Belopolsky08313822012-06-15 20:19:47 -04003894 # Make t2 naive and it should differ.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003895 t2 = self.theclass.min
Alexander Belopolsky08313822012-06-15 20:19:47 -04003896 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003897 self.assertEqual(t2, t2)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04003898 # and > comparison should fail
3899 with self.assertRaises(TypeError):
3900 t1 > t2
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003901
3902 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
3903 class Naive(tzinfo):
3904 def utcoffset(self, dt): return None
3905 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
Alexander Belopolsky08313822012-06-15 20:19:47 -04003906 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003907 self.assertEqual(t2, t2)
3908
3909 # OTOH, it's OK to compare two of these mixing the two ways of being
3910 # naive.
3911 t1 = self.theclass(5, 6, 7)
3912 self.assertEqual(t1, t2)
3913
3914 # Try a bogus uctoffset.
3915 class Bogus(tzinfo):
3916 def utcoffset(self, dt):
3917 return timedelta(minutes=1440) # out of bounds
3918 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
3919 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
3920 self.assertRaises(ValueError, lambda: t1 == t2)
3921
3922 def test_pickling(self):
3923 # Try one without a tzinfo.
3924 args = 6, 7, 23, 20, 59, 1, 64**2
3925 orig = self.theclass(*args)
3926 for pickler, unpickler, proto in pickle_choices:
3927 green = pickler.dumps(orig, proto)
3928 derived = unpickler.loads(green)
3929 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003930 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003931
3932 # Try one with a tzinfo.
3933 tinfo = PicklableFixedOffset(-300, 'cookie')
3934 orig = self.theclass(*args, **{'tzinfo': tinfo})
3935 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
3936 for pickler, unpickler, proto in pickle_choices:
3937 green = pickler.dumps(orig, proto)
3938 derived = unpickler.loads(green)
3939 self.assertEqual(orig, derived)
3940 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3941 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3942 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003943 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003944
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02003945 def test_compat_unpickle(self):
3946 tests = [
3947 b'cdatetime\ndatetime\n'
3948 b"(S'\\x07\\xdf\\x0b\\x1b\\x14;\\x01\\x01\\xe2@'\n"
3949 b'ctest.datetimetester\nPicklableFixedOffset\n(tR'
3950 b"(dS'_FixedOffset__offset'\ncdatetime\ntimedelta\n"
3951 b'(I-1\nI68400\nI0\ntRs'
3952 b"S'_FixedOffset__dstoffset'\nNs"
3953 b"S'_FixedOffset__name'\nS'cookie'\nsbtR.",
3954
3955 b'cdatetime\ndatetime\n'
3956 b'(U\n\x07\xdf\x0b\x1b\x14;\x01\x01\xe2@'
3957 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
3958 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
3959 b'(J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00tR'
3960 b'U\x17_FixedOffset__dstoffsetN'
3961 b'U\x12_FixedOffset__nameU\x06cookieubtR.',
3962
3963 b'\x80\x02cdatetime\ndatetime\n'
3964 b'U\n\x07\xdf\x0b\x1b\x14;\x01\x01\xe2@'
3965 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
3966 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
3967 b'J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00\x87R'
3968 b'U\x17_FixedOffset__dstoffsetN'
3969 b'U\x12_FixedOffset__nameU\x06cookieub\x86R.',
3970 ]
3971 args = 2015, 11, 27, 20, 59, 1, 123456
3972 tinfo = PicklableFixedOffset(-300, 'cookie')
3973 expected = self.theclass(*args, **{'tzinfo': tinfo})
3974 for data in tests:
3975 for loads in pickle_loads:
3976 derived = loads(data, encoding='latin1')
3977 self.assertEqual(derived, expected)
3978 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3979 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3980 self.assertEqual(derived.tzname(), 'cookie')
3981
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003982 def test_extreme_hashes(self):
3983 # If an attempt is made to hash these via subtracting the offset
3984 # then hashing a datetime object, OverflowError results. The
3985 # Python implementation used to blow up here.
3986 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
3987 hash(t)
3988 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3989 tzinfo=FixedOffset(-1439, ""))
3990 hash(t)
3991
3992 # OTOH, an OOB offset should blow up.
3993 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
3994 self.assertRaises(ValueError, hash, t)
3995
3996 def test_zones(self):
3997 est = FixedOffset(-300, "EST")
3998 utc = FixedOffset(0, "UTC")
3999 met = FixedOffset(60, "MET")
4000 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
4001 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
4002 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
4003 self.assertEqual(t1.tzinfo, est)
4004 self.assertEqual(t2.tzinfo, utc)
4005 self.assertEqual(t3.tzinfo, met)
4006 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
4007 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
4008 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
4009 self.assertEqual(t1.tzname(), "EST")
4010 self.assertEqual(t2.tzname(), "UTC")
4011 self.assertEqual(t3.tzname(), "MET")
4012 self.assertEqual(hash(t1), hash(t2))
4013 self.assertEqual(hash(t1), hash(t3))
4014 self.assertEqual(hash(t2), hash(t3))
4015 self.assertEqual(t1, t2)
4016 self.assertEqual(t1, t3)
4017 self.assertEqual(t2, t3)
4018 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
4019 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
4020 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
4021 d = 'datetime.datetime(2002, 3, 19, '
4022 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
4023 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
4024 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
4025
4026 def test_combine(self):
4027 met = FixedOffset(60, "MET")
4028 d = date(2002, 3, 4)
4029 tz = time(18, 45, 3, 1234, tzinfo=met)
4030 dt = datetime.combine(d, tz)
4031 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
4032 tzinfo=met))
4033
4034 def test_extract(self):
4035 met = FixedOffset(60, "MET")
4036 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
4037 self.assertEqual(dt.date(), date(2002, 3, 4))
4038 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
4039 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
4040
4041 def test_tz_aware_arithmetic(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004042 now = self.theclass.now()
4043 tz55 = FixedOffset(-330, "west 5:30")
4044 timeaware = now.time().replace(tzinfo=tz55)
4045 nowaware = self.theclass.combine(now.date(), timeaware)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004046 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004047 self.assertEqual(nowaware.timetz(), timeaware)
4048
4049 # Can't mix aware and non-aware.
4050 self.assertRaises(TypeError, lambda: now - nowaware)
4051 self.assertRaises(TypeError, lambda: nowaware - now)
4052
4053 # And adding datetime's doesn't make sense, aware or not.
4054 self.assertRaises(TypeError, lambda: now + nowaware)
4055 self.assertRaises(TypeError, lambda: nowaware + now)
4056 self.assertRaises(TypeError, lambda: nowaware + nowaware)
4057
4058 # Subtracting should yield 0.
4059 self.assertEqual(now - now, timedelta(0))
4060 self.assertEqual(nowaware - nowaware, timedelta(0))
4061
4062 # Adding a delta should preserve tzinfo.
4063 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
4064 nowawareplus = nowaware + delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004065 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004066 nowawareplus2 = delta + nowaware
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004067 self.assertIs(nowawareplus2.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004068 self.assertEqual(nowawareplus, nowawareplus2)
4069
4070 # that - delta should be what we started with, and that - what we
4071 # started with should be delta.
4072 diff = nowawareplus - delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004073 self.assertIs(diff.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004074 self.assertEqual(nowaware, diff)
4075 self.assertRaises(TypeError, lambda: delta - nowawareplus)
4076 self.assertEqual(nowawareplus - nowaware, delta)
4077
4078 # Make up a random timezone.
4079 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
4080 # Attach it to nowawareplus.
4081 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004082 self.assertIs(nowawareplus.tzinfo, tzr)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004083 # Make sure the difference takes the timezone adjustments into account.
4084 got = nowaware - nowawareplus
4085 # Expected: (nowaware base - nowaware offset) -
4086 # (nowawareplus base - nowawareplus offset) =
4087 # (nowaware base - nowawareplus base) +
4088 # (nowawareplus offset - nowaware offset) =
4089 # -delta + nowawareplus offset - nowaware offset
4090 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
4091 self.assertEqual(got, expected)
4092
4093 # Try max possible difference.
4094 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
4095 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
4096 tzinfo=FixedOffset(-1439, "max"))
4097 maxdiff = max - min
4098 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
4099 timedelta(minutes=2*1439))
4100 # Different tzinfo, but the same offset
4101 tza = timezone(HOUR, 'A')
4102 tzb = timezone(HOUR, 'B')
4103 delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
4104 self.assertEqual(delta, self.theclass.min - self.theclass.max)
4105
4106 def test_tzinfo_now(self):
4107 meth = self.theclass.now
4108 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4109 base = meth()
4110 # Try with and without naming the keyword.
4111 off42 = FixedOffset(42, "42")
4112 another = meth(off42)
4113 again = meth(tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004114 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004115 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
4116 # Bad argument with and w/o naming the keyword.
4117 self.assertRaises(TypeError, meth, 16)
4118 self.assertRaises(TypeError, meth, tzinfo=16)
4119 # Bad keyword name.
4120 self.assertRaises(TypeError, meth, tinfo=off42)
4121 # Too many args.
4122 self.assertRaises(TypeError, meth, off42, off42)
4123
4124 # We don't know which time zone we're in, and don't have a tzinfo
4125 # class to represent it, so seeing whether a tz argument actually
4126 # does a conversion is tricky.
4127 utc = FixedOffset(0, "utc", 0)
4128 for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
4129 timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
4130 for dummy in range(3):
4131 now = datetime.now(weirdtz)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004132 self.assertIs(now.tzinfo, weirdtz)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004133 utcnow = datetime.utcnow().replace(tzinfo=utc)
4134 now2 = utcnow.astimezone(weirdtz)
4135 if abs(now - now2) < timedelta(seconds=30):
4136 break
4137 # Else the code is broken, or more than 30 seconds passed between
4138 # calls; assuming the latter, just try again.
4139 else:
4140 # Three strikes and we're out.
4141 self.fail("utcnow(), now(tz), or astimezone() may be broken")
4142
4143 def test_tzinfo_fromtimestamp(self):
4144 import time
4145 meth = self.theclass.fromtimestamp
4146 ts = time.time()
4147 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4148 base = meth(ts)
4149 # Try with and without naming the keyword.
4150 off42 = FixedOffset(42, "42")
4151 another = meth(ts, off42)
4152 again = meth(ts, tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004153 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004154 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
4155 # Bad argument with and w/o naming the keyword.
4156 self.assertRaises(TypeError, meth, ts, 16)
4157 self.assertRaises(TypeError, meth, ts, tzinfo=16)
4158 # Bad keyword name.
4159 self.assertRaises(TypeError, meth, ts, tinfo=off42)
4160 # Too many args.
4161 self.assertRaises(TypeError, meth, ts, off42, off42)
4162 # Too few args.
4163 self.assertRaises(TypeError, meth)
4164
4165 # Try to make sure tz= actually does some conversion.
4166 timestamp = 1000000000
4167 utcdatetime = datetime.utcfromtimestamp(timestamp)
4168 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
4169 # But on some flavor of Mac, it's nowhere near that. So we can't have
4170 # any idea here what time that actually is, we can only test that
4171 # relative changes match.
4172 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
4173 tz = FixedOffset(utcoffset, "tz", 0)
4174 expected = utcdatetime + utcoffset
4175 got = datetime.fromtimestamp(timestamp, tz)
4176 self.assertEqual(expected, got.replace(tzinfo=None))
4177
4178 def test_tzinfo_utcnow(self):
4179 meth = self.theclass.utcnow
4180 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4181 base = meth()
4182 # Try with and without naming the keyword; for whatever reason,
4183 # utcnow() doesn't accept a tzinfo argument.
4184 off42 = FixedOffset(42, "42")
4185 self.assertRaises(TypeError, meth, off42)
4186 self.assertRaises(TypeError, meth, tzinfo=off42)
4187
4188 def test_tzinfo_utcfromtimestamp(self):
4189 import time
4190 meth = self.theclass.utcfromtimestamp
4191 ts = time.time()
4192 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4193 base = meth(ts)
4194 # Try with and without naming the keyword; for whatever reason,
4195 # utcfromtimestamp() doesn't accept a tzinfo argument.
4196 off42 = FixedOffset(42, "42")
4197 self.assertRaises(TypeError, meth, ts, off42)
4198 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
4199
4200 def test_tzinfo_timetuple(self):
4201 # TestDateTime tested most of this. datetime adds a twist to the
4202 # DST flag.
4203 class DST(tzinfo):
4204 def __init__(self, dstvalue):
4205 if isinstance(dstvalue, int):
4206 dstvalue = timedelta(minutes=dstvalue)
4207 self.dstvalue = dstvalue
4208 def dst(self, dt):
4209 return self.dstvalue
4210
4211 cls = self.theclass
4212 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
4213 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
4214 t = d.timetuple()
4215 self.assertEqual(1, t.tm_year)
4216 self.assertEqual(1, t.tm_mon)
4217 self.assertEqual(1, t.tm_mday)
4218 self.assertEqual(10, t.tm_hour)
4219 self.assertEqual(20, t.tm_min)
4220 self.assertEqual(30, t.tm_sec)
4221 self.assertEqual(0, t.tm_wday)
4222 self.assertEqual(1, t.tm_yday)
4223 self.assertEqual(flag, t.tm_isdst)
4224
4225 # dst() returns wrong type.
4226 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
4227
4228 # dst() at the edge.
4229 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
4230 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
4231
4232 # dst() out of range.
4233 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
4234 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
4235
4236 def test_utctimetuple(self):
4237 class DST(tzinfo):
4238 def __init__(self, dstvalue=0):
4239 if isinstance(dstvalue, int):
4240 dstvalue = timedelta(minutes=dstvalue)
4241 self.dstvalue = dstvalue
4242 def dst(self, dt):
4243 return self.dstvalue
4244
4245 cls = self.theclass
4246 # This can't work: DST didn't implement utcoffset.
4247 self.assertRaises(NotImplementedError,
4248 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
4249
4250 class UOFS(DST):
4251 def __init__(self, uofs, dofs=None):
4252 DST.__init__(self, dofs)
4253 self.uofs = timedelta(minutes=uofs)
4254 def utcoffset(self, dt):
4255 return self.uofs
4256
4257 for dstvalue in -33, 33, 0, None:
4258 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
4259 t = d.utctimetuple()
4260 self.assertEqual(d.year, t.tm_year)
4261 self.assertEqual(d.month, t.tm_mon)
4262 self.assertEqual(d.day, t.tm_mday)
4263 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
4264 self.assertEqual(13, t.tm_min)
4265 self.assertEqual(d.second, t.tm_sec)
4266 self.assertEqual(d.weekday(), t.tm_wday)
4267 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
4268 t.tm_yday)
4269 # Ensure tm_isdst is 0 regardless of what dst() says: DST
4270 # is never in effect for a UTC time.
4271 self.assertEqual(0, t.tm_isdst)
4272
4273 # For naive datetime, utctimetuple == timetuple except for isdst
4274 d = cls(1, 2, 3, 10, 20, 30, 40)
4275 t = d.utctimetuple()
4276 self.assertEqual(t[:-1], d.timetuple()[:-1])
4277 self.assertEqual(0, t.tm_isdst)
4278 # Same if utcoffset is None
4279 class NOFS(DST):
4280 def utcoffset(self, dt):
4281 return None
4282 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
4283 t = d.utctimetuple()
4284 self.assertEqual(t[:-1], d.timetuple()[:-1])
4285 self.assertEqual(0, t.tm_isdst)
4286 # Check that bad tzinfo is detected
4287 class BOFS(DST):
4288 def utcoffset(self, dt):
4289 return "EST"
4290 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
4291 self.assertRaises(TypeError, d.utctimetuple)
4292
4293 # Check that utctimetuple() is the same as
4294 # astimezone(utc).timetuple()
4295 d = cls(2010, 11, 13, 14, 15, 16, 171819)
4296 for tz in [timezone.min, timezone.utc, timezone.max]:
4297 dtz = d.replace(tzinfo=tz)
4298 self.assertEqual(dtz.utctimetuple()[:-1],
4299 dtz.astimezone(timezone.utc).timetuple()[:-1])
4300 # At the edges, UTC adjustment can produce years out-of-range
4301 # for a datetime object. Ensure that an OverflowError is
4302 # raised.
4303 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
4304 # That goes back 1 minute less than a full day.
4305 self.assertRaises(OverflowError, tiny.utctimetuple)
4306
4307 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
4308 # That goes forward 1 minute less than a full day.
4309 self.assertRaises(OverflowError, huge.utctimetuple)
4310 # More overflow cases
4311 tiny = cls.min.replace(tzinfo=timezone(MINUTE))
4312 self.assertRaises(OverflowError, tiny.utctimetuple)
4313 huge = cls.max.replace(tzinfo=timezone(-MINUTE))
4314 self.assertRaises(OverflowError, huge.utctimetuple)
4315
4316 def test_tzinfo_isoformat(self):
4317 zero = FixedOffset(0, "+00:00")
4318 plus = FixedOffset(220, "+03:40")
4319 minus = FixedOffset(-231, "-03:51")
4320 unknown = FixedOffset(None, "")
4321
4322 cls = self.theclass
4323 datestr = '0001-02-03'
4324 for ofs in None, zero, plus, minus, unknown:
4325 for us in 0, 987001:
4326 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
4327 timestr = '04:05:59' + (us and '.987001' or '')
4328 ofsstr = ofs is not None and d.tzname() or ''
4329 tailstr = timestr + ofsstr
4330 iso = d.isoformat()
4331 self.assertEqual(iso, datestr + 'T' + tailstr)
4332 self.assertEqual(iso, d.isoformat('T'))
4333 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
4334 self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
4335 self.assertEqual(str(d), datestr + ' ' + tailstr)
4336
4337 def test_replace(self):
4338 cls = self.theclass
4339 z100 = FixedOffset(100, "+100")
4340 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
4341 args = [1, 2, 3, 4, 5, 6, 7, z100]
4342 base = cls(*args)
4343 self.assertEqual(base, base.replace())
4344
4345 i = 0
4346 for name, newval in (("year", 2),
4347 ("month", 3),
4348 ("day", 4),
4349 ("hour", 5),
4350 ("minute", 6),
4351 ("second", 7),
4352 ("microsecond", 8),
4353 ("tzinfo", zm200)):
4354 newargs = args[:]
4355 newargs[i] = newval
4356 expected = cls(*newargs)
4357 got = base.replace(**{name: newval})
4358 self.assertEqual(expected, got)
4359 i += 1
4360
4361 # Ensure we can get rid of a tzinfo.
4362 self.assertEqual(base.tzname(), "+100")
4363 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004364 self.assertIsNone(base2.tzinfo)
4365 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004366
4367 # Ensure we can add one.
4368 base3 = base2.replace(tzinfo=z100)
4369 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004370 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004371
4372 # Out of bounds.
4373 base = cls(2000, 2, 29)
4374 self.assertRaises(ValueError, base.replace, year=2001)
4375
4376 def test_more_astimezone(self):
4377 # The inherited test_astimezone covered some trivial and error cases.
4378 fnone = FixedOffset(None, "None")
4379 f44m = FixedOffset(44, "44")
4380 fm5h = FixedOffset(-timedelta(hours=5), "m300")
4381
4382 dt = self.theclass.now(tz=f44m)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004383 self.assertIs(dt.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004384 # Replacing with degenerate tzinfo raises an exception.
4385 self.assertRaises(ValueError, dt.astimezone, fnone)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004386 # Replacing with same tzinfo makes no change.
4387 x = dt.astimezone(dt.tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004388 self.assertIs(x.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004389 self.assertEqual(x.date(), dt.date())
4390 self.assertEqual(x.time(), dt.time())
4391
4392 # Replacing with different tzinfo does adjust.
4393 got = dt.astimezone(fm5h)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004394 self.assertIs(got.tzinfo, fm5h)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004395 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
4396 expected = dt - dt.utcoffset() # in effect, convert to UTC
4397 expected += fm5h.utcoffset(dt) # and from there to local time
4398 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
4399 self.assertEqual(got.date(), expected.date())
4400 self.assertEqual(got.time(), expected.time())
4401 self.assertEqual(got.timetz(), expected.timetz())
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004402 self.assertIs(got.tzinfo, expected.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004403 self.assertEqual(got, expected)
4404
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004405 @support.run_with_tz('UTC')
4406 def test_astimezone_default_utc(self):
4407 dt = self.theclass.now(timezone.utc)
4408 self.assertEqual(dt.astimezone(None), dt)
4409 self.assertEqual(dt.astimezone(), dt)
4410
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004411 # Note that offset in TZ variable has the opposite sign to that
4412 # produced by %z directive.
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004413 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4414 def test_astimezone_default_eastern(self):
4415 dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
4416 local = dt.astimezone()
4417 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004418 self.assertEqual(local.strftime("%z %Z"), "-0500 EST")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004419 dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc)
4420 local = dt.astimezone()
4421 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004422 self.assertEqual(local.strftime("%z %Z"), "-0400 EDT")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004423
Alexander Belopolsky1dcf4f92016-03-25 15:42:59 -04004424 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4425 def test_astimezone_default_near_fold(self):
4426 # Issue #26616.
4427 u = datetime(2015, 11, 1, 5, tzinfo=timezone.utc)
4428 t = u.astimezone()
4429 s = t.astimezone()
4430 self.assertEqual(t.tzinfo, s.tzinfo)
4431
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004432 def test_aware_subtract(self):
4433 cls = self.theclass
4434
4435 # Ensure that utcoffset() is ignored when the operands have the
4436 # same tzinfo member.
4437 class OperandDependentOffset(tzinfo):
4438 def utcoffset(self, t):
4439 if t.minute < 10:
4440 # d0 and d1 equal after adjustment
4441 return timedelta(minutes=t.minute)
4442 else:
4443 # d2 off in the weeds
4444 return timedelta(minutes=59)
4445
4446 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
4447 d0 = base.replace(minute=3)
4448 d1 = base.replace(minute=9)
4449 d2 = base.replace(minute=11)
4450 for x in d0, d1, d2:
4451 for y in d0, d1, d2:
4452 got = x - y
4453 expected = timedelta(minutes=x.minute - y.minute)
4454 self.assertEqual(got, expected)
4455
4456 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
4457 # ignored.
4458 base = cls(8, 9, 10, 11, 12, 13, 14)
4459 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
4460 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
4461 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
4462 for x in d0, d1, d2:
4463 for y in d0, d1, d2:
4464 got = x - y
4465 if (x is d0 or x is d1) and (y is d0 or y is d1):
4466 expected = timedelta(0)
4467 elif x is y is d2:
4468 expected = timedelta(0)
4469 elif x is d2:
4470 expected = timedelta(minutes=(11-59)-0)
4471 else:
4472 assert y is d2
4473 expected = timedelta(minutes=0-(11-59))
4474 self.assertEqual(got, expected)
4475
4476 def test_mixed_compare(self):
4477 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
4478 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
4479 self.assertEqual(t1, t2)
4480 t2 = t2.replace(tzinfo=None)
4481 self.assertEqual(t1, t2)
4482 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
4483 self.assertEqual(t1, t2)
4484 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04004485 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004486
4487 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
4488 class Varies(tzinfo):
4489 def __init__(self):
4490 self.offset = timedelta(minutes=22)
4491 def utcoffset(self, t):
4492 self.offset += timedelta(minutes=1)
4493 return self.offset
4494
4495 v = Varies()
4496 t1 = t2.replace(tzinfo=v)
4497 t2 = t2.replace(tzinfo=v)
4498 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
4499 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
4500 self.assertEqual(t1, t2)
4501
4502 # But if they're not identical, it isn't ignored.
4503 t2 = t2.replace(tzinfo=Varies())
4504 self.assertTrue(t1 < t2) # t1's offset counter still going up
4505
4506 def test_subclass_datetimetz(self):
4507
4508 class C(self.theclass):
4509 theAnswer = 42
4510
4511 def __new__(cls, *args, **kws):
4512 temp = kws.copy()
4513 extra = temp.pop('extra')
4514 result = self.theclass.__new__(cls, *args, **temp)
4515 result.extra = extra
4516 return result
4517
4518 def newmeth(self, start):
4519 return start + self.hour + self.year
4520
4521 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
4522
4523 dt1 = self.theclass(*args)
4524 dt2 = C(*args, **{'extra': 7})
4525
4526 self.assertEqual(dt2.__class__, C)
4527 self.assertEqual(dt2.theAnswer, 42)
4528 self.assertEqual(dt2.extra, 7)
4529 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
4530 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
4531
4532# Pain to set up DST-aware tzinfo classes.
4533
4534def first_sunday_on_or_after(dt):
4535 days_to_go = 6 - dt.weekday()
4536 if days_to_go:
4537 dt += timedelta(days_to_go)
4538 return dt
4539
4540ZERO = timedelta(0)
4541MINUTE = timedelta(minutes=1)
4542HOUR = timedelta(hours=1)
4543DAY = timedelta(days=1)
4544# In the US, DST starts at 2am (standard time) on the first Sunday in April.
4545DSTSTART = datetime(1, 4, 1, 2)
4546# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
4547# which is the first Sunday on or after Oct 25. Because we view 1:MM as
4548# being standard time on that day, there is no spelling in local time of
4549# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
4550DSTEND = datetime(1, 10, 25, 1)
4551
4552class USTimeZone(tzinfo):
4553
4554 def __init__(self, hours, reprname, stdname, dstname):
4555 self.stdoffset = timedelta(hours=hours)
4556 self.reprname = reprname
4557 self.stdname = stdname
4558 self.dstname = dstname
4559
4560 def __repr__(self):
4561 return self.reprname
4562
4563 def tzname(self, dt):
4564 if self.dst(dt):
4565 return self.dstname
4566 else:
4567 return self.stdname
4568
4569 def utcoffset(self, dt):
4570 return self.stdoffset + self.dst(dt)
4571
4572 def dst(self, dt):
4573 if dt is None or dt.tzinfo is None:
4574 # An exception instead may be sensible here, in one or more of
4575 # the cases.
4576 return ZERO
4577 assert dt.tzinfo is self
4578
4579 # Find first Sunday in April.
4580 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
4581 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
4582
4583 # Find last Sunday in October.
4584 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
4585 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
4586
4587 # Can't compare naive to aware objects, so strip the timezone from
4588 # dt first.
4589 if start <= dt.replace(tzinfo=None) < end:
4590 return HOUR
4591 else:
4592 return ZERO
4593
4594Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
4595Central = USTimeZone(-6, "Central", "CST", "CDT")
4596Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
4597Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
4598utc_real = FixedOffset(0, "UTC", 0)
4599# For better test coverage, we want another flavor of UTC that's west of
4600# the Eastern and Pacific timezones.
4601utc_fake = FixedOffset(-12*60, "UTCfake", 0)
4602
4603class TestTimezoneConversions(unittest.TestCase):
4604 # The DST switch times for 2002, in std time.
4605 dston = datetime(2002, 4, 7, 2)
4606 dstoff = datetime(2002, 10, 27, 1)
4607
4608 theclass = datetime
4609
4610 # Check a time that's inside DST.
4611 def checkinside(self, dt, tz, utc, dston, dstoff):
4612 self.assertEqual(dt.dst(), HOUR)
4613
4614 # Conversion to our own timezone is always an identity.
4615 self.assertEqual(dt.astimezone(tz), dt)
4616
4617 asutc = dt.astimezone(utc)
4618 there_and_back = asutc.astimezone(tz)
4619
4620 # Conversion to UTC and back isn't always an identity here,
4621 # because there are redundant spellings (in local time) of
4622 # UTC time when DST begins: the clock jumps from 1:59:59
4623 # to 3:00:00, and a local time of 2:MM:SS doesn't really
4624 # make sense then. The classes above treat 2:MM:SS as
4625 # daylight time then (it's "after 2am"), really an alias
4626 # for 1:MM:SS standard time. The latter form is what
4627 # conversion back from UTC produces.
4628 if dt.date() == dston.date() and dt.hour == 2:
4629 # We're in the redundant hour, and coming back from
4630 # UTC gives the 1:MM:SS standard-time spelling.
4631 self.assertEqual(there_and_back + HOUR, dt)
4632 # Although during was considered to be in daylight
4633 # time, there_and_back is not.
4634 self.assertEqual(there_and_back.dst(), ZERO)
4635 # They're the same times in UTC.
4636 self.assertEqual(there_and_back.astimezone(utc),
4637 dt.astimezone(utc))
4638 else:
4639 # We're not in the redundant hour.
4640 self.assertEqual(dt, there_and_back)
4641
4642 # Because we have a redundant spelling when DST begins, there is
Ezio Melotti3b3499b2011-03-16 11:35:38 +02004643 # (unfortunately) an hour when DST ends that can't be spelled at all in
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004644 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
4645 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
4646 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
4647 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
4648 # expressed in local time. Nevertheless, we want conversion back
4649 # from UTC to mimic the local clock's "repeat an hour" behavior.
4650 nexthour_utc = asutc + HOUR
4651 nexthour_tz = nexthour_utc.astimezone(tz)
4652 if dt.date() == dstoff.date() and dt.hour == 0:
4653 # We're in the hour before the last DST hour. The last DST hour
4654 # is ineffable. We want the conversion back to repeat 1:MM.
4655 self.assertEqual(nexthour_tz, dt.replace(hour=1))
4656 nexthour_utc += HOUR
4657 nexthour_tz = nexthour_utc.astimezone(tz)
4658 self.assertEqual(nexthour_tz, dt.replace(hour=1))
4659 else:
4660 self.assertEqual(nexthour_tz - dt, HOUR)
4661
4662 # Check a time that's outside DST.
4663 def checkoutside(self, dt, tz, utc):
4664 self.assertEqual(dt.dst(), ZERO)
4665
4666 # Conversion to our own timezone is always an identity.
4667 self.assertEqual(dt.astimezone(tz), dt)
4668
4669 # Converting to UTC and back is an identity too.
4670 asutc = dt.astimezone(utc)
4671 there_and_back = asutc.astimezone(tz)
4672 self.assertEqual(dt, there_and_back)
4673
4674 def convert_between_tz_and_utc(self, tz, utc):
4675 dston = self.dston.replace(tzinfo=tz)
4676 # Because 1:MM on the day DST ends is taken as being standard time,
4677 # there is no spelling in tz for the last hour of daylight time.
4678 # For purposes of the test, the last hour of DST is 0:MM, which is
4679 # taken as being daylight time (and 1:MM is taken as being standard
4680 # time).
4681 dstoff = self.dstoff.replace(tzinfo=tz)
4682 for delta in (timedelta(weeks=13),
4683 DAY,
4684 HOUR,
4685 timedelta(minutes=1),
4686 timedelta(microseconds=1)):
4687
4688 self.checkinside(dston, tz, utc, dston, dstoff)
4689 for during in dston + delta, dstoff - delta:
4690 self.checkinside(during, tz, utc, dston, dstoff)
4691
4692 self.checkoutside(dstoff, tz, utc)
4693 for outside in dston - delta, dstoff + delta:
4694 self.checkoutside(outside, tz, utc)
4695
4696 def test_easy(self):
4697 # Despite the name of this test, the endcases are excruciating.
4698 self.convert_between_tz_and_utc(Eastern, utc_real)
4699 self.convert_between_tz_and_utc(Pacific, utc_real)
4700 self.convert_between_tz_and_utc(Eastern, utc_fake)
4701 self.convert_between_tz_and_utc(Pacific, utc_fake)
4702 # The next is really dancing near the edge. It works because
4703 # Pacific and Eastern are far enough apart that their "problem
4704 # hours" don't overlap.
4705 self.convert_between_tz_and_utc(Eastern, Pacific)
4706 self.convert_between_tz_and_utc(Pacific, Eastern)
4707 # OTOH, these fail! Don't enable them. The difficulty is that
4708 # the edge case tests assume that every hour is representable in
4709 # the "utc" class. This is always true for a fixed-offset tzinfo
4710 # class (lke utc_real and utc_fake), but not for Eastern or Central.
4711 # For these adjacent DST-aware time zones, the range of time offsets
4712 # tested ends up creating hours in the one that aren't representable
4713 # in the other. For the same reason, we would see failures in the
4714 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
4715 # offset deltas in convert_between_tz_and_utc().
4716 #
4717 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
4718 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
4719
4720 def test_tricky(self):
4721 # 22:00 on day before daylight starts.
4722 fourback = self.dston - timedelta(hours=4)
4723 ninewest = FixedOffset(-9*60, "-0900", 0)
4724 fourback = fourback.replace(tzinfo=ninewest)
4725 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
4726 # 2", we should get the 3 spelling.
4727 # If we plug 22:00 the day before into Eastern, it "looks like std
4728 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
4729 # to 22:00 lands on 2:00, which makes no sense in local time (the
4730 # local clock jumps from 1 to 3). The point here is to make sure we
4731 # get the 3 spelling.
4732 expected = self.dston.replace(hour=3)
4733 got = fourback.astimezone(Eastern).replace(tzinfo=None)
4734 self.assertEqual(expected, got)
4735
4736 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
4737 # case we want the 1:00 spelling.
4738 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
4739 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
4740 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
4741 # spelling.
4742 expected = self.dston.replace(hour=1)
4743 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
4744 self.assertEqual(expected, got)
4745
4746 # Now on the day DST ends, we want "repeat an hour" behavior.
4747 # UTC 4:MM 5:MM 6:MM 7:MM checking these
4748 # EST 23:MM 0:MM 1:MM 2:MM
4749 # EDT 0:MM 1:MM 2:MM 3:MM
4750 # wall 0:MM 1:MM 1:MM 2:MM against these
4751 for utc in utc_real, utc_fake:
4752 for tz in Eastern, Pacific:
4753 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
4754 # Convert that to UTC.
4755 first_std_hour -= tz.utcoffset(None)
4756 # Adjust for possibly fake UTC.
4757 asutc = first_std_hour + utc.utcoffset(None)
4758 # First UTC hour to convert; this is 4:00 when utc=utc_real &
4759 # tz=Eastern.
4760 asutcbase = asutc.replace(tzinfo=utc)
4761 for tzhour in (0, 1, 1, 2):
4762 expectedbase = self.dstoff.replace(hour=tzhour)
4763 for minute in 0, 30, 59:
4764 expected = expectedbase.replace(minute=minute)
4765 asutc = asutcbase.replace(minute=minute)
4766 astz = asutc.astimezone(tz)
4767 self.assertEqual(astz.replace(tzinfo=None), expected)
4768 asutcbase += HOUR
4769
4770
4771 def test_bogus_dst(self):
4772 class ok(tzinfo):
4773 def utcoffset(self, dt): return HOUR
4774 def dst(self, dt): return HOUR
4775
4776 now = self.theclass.now().replace(tzinfo=utc_real)
4777 # Doesn't blow up.
4778 now.astimezone(ok())
4779
4780 # Does blow up.
4781 class notok(ok):
4782 def dst(self, dt): return None
4783 self.assertRaises(ValueError, now.astimezone, notok())
4784
4785 # Sometimes blow up. In the following, tzinfo.dst()
4786 # implementation may return None or not None depending on
4787 # whether DST is assumed to be in effect. In this situation,
4788 # a ValueError should be raised by astimezone().
4789 class tricky_notok(ok):
4790 def dst(self, dt):
4791 if dt.year == 2000:
4792 return None
4793 else:
4794 return 10*HOUR
4795 dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
4796 self.assertRaises(ValueError, dt.astimezone, tricky_notok())
4797
4798 def test_fromutc(self):
4799 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
4800 now = datetime.utcnow().replace(tzinfo=utc_real)
4801 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
4802 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
4803 enow = Eastern.fromutc(now) # doesn't blow up
4804 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
4805 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
4806 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
4807
4808 # Always converts UTC to standard time.
4809 class FauxUSTimeZone(USTimeZone):
4810 def fromutc(self, dt):
4811 return dt + self.stdoffset
4812 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
4813
4814 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
4815 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
4816 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
4817
4818 # Check around DST start.
4819 start = self.dston.replace(hour=4, tzinfo=Eastern)
4820 fstart = start.replace(tzinfo=FEastern)
4821 for wall in 23, 0, 1, 3, 4, 5:
4822 expected = start.replace(hour=wall)
4823 if wall == 23:
4824 expected -= timedelta(days=1)
4825 got = Eastern.fromutc(start)
4826 self.assertEqual(expected, got)
4827
4828 expected = fstart + FEastern.stdoffset
4829 got = FEastern.fromutc(fstart)
4830 self.assertEqual(expected, got)
4831
4832 # Ensure astimezone() calls fromutc() too.
4833 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
4834 self.assertEqual(expected, got)
4835
4836 start += HOUR
4837 fstart += HOUR
4838
4839 # Check around DST end.
4840 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
4841 fstart = start.replace(tzinfo=FEastern)
4842 for wall in 0, 1, 1, 2, 3, 4:
4843 expected = start.replace(hour=wall)
4844 got = Eastern.fromutc(start)
4845 self.assertEqual(expected, got)
4846
4847 expected = fstart + FEastern.stdoffset
4848 got = FEastern.fromutc(fstart)
4849 self.assertEqual(expected, got)
4850
4851 # Ensure astimezone() calls fromutc() too.
4852 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
4853 self.assertEqual(expected, got)
4854
4855 start += HOUR
4856 fstart += HOUR
4857
4858
4859#############################################################################
4860# oddballs
4861
4862class Oddballs(unittest.TestCase):
4863
4864 def test_bug_1028306(self):
4865 # Trying to compare a date to a datetime should act like a mixed-
4866 # type comparison, despite that datetime is a subclass of date.
4867 as_date = date.today()
4868 as_datetime = datetime.combine(as_date, time())
4869 self.assertTrue(as_date != as_datetime)
4870 self.assertTrue(as_datetime != as_date)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004871 self.assertFalse(as_date == as_datetime)
4872 self.assertFalse(as_datetime == as_date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004873 self.assertRaises(TypeError, lambda: as_date < as_datetime)
4874 self.assertRaises(TypeError, lambda: as_datetime < as_date)
4875 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
4876 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
4877 self.assertRaises(TypeError, lambda: as_date > as_datetime)
4878 self.assertRaises(TypeError, lambda: as_datetime > as_date)
4879 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
4880 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
4881
Raymond Hettinger15f44ab2016-08-30 10:47:49 -07004882 # Nevertheless, comparison should work with the base-class (date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004883 # projection if use of a date method is forced.
4884 self.assertEqual(as_date.__eq__(as_datetime), True)
4885 different_day = (as_date.day + 1) % 20 + 1
4886 as_different = as_datetime.replace(day= different_day)
4887 self.assertEqual(as_date.__eq__(as_different), False)
4888
4889 # And date should compare with other subclasses of date. If a
4890 # subclass wants to stop this, it's up to the subclass to do so.
4891 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
4892 self.assertEqual(as_date, date_sc)
4893 self.assertEqual(date_sc, as_date)
4894
4895 # Ditto for datetimes.
4896 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
4897 as_date.day, 0, 0, 0)
4898 self.assertEqual(as_datetime, datetime_sc)
4899 self.assertEqual(datetime_sc, as_datetime)
4900
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004901 def test_extra_attributes(self):
4902 for x in [date.today(),
4903 time(),
4904 datetime.utcnow(),
4905 timedelta(),
4906 tzinfo(),
4907 timezone(timedelta())]:
4908 with self.assertRaises(AttributeError):
4909 x.abc = 1
4910
4911 def test_check_arg_types(self):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004912 class Number:
4913 def __init__(self, value):
4914 self.value = value
4915 def __int__(self):
4916 return self.value
4917
4918 for xx in [decimal.Decimal(10),
4919 decimal.Decimal('10.9'),
4920 Number(10)]:
4921 self.assertEqual(datetime(10, 10, 10, 10, 10, 10, 10),
4922 datetime(xx, xx, xx, xx, xx, xx, xx))
4923
4924 with self.assertRaisesRegex(TypeError, '^an integer is required '
R David Murray44b548d2016-09-08 13:59:53 -04004925 r'\(got type str\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004926 datetime(10, 10, '10')
4927
4928 f10 = Number(10.9)
4929 with self.assertRaisesRegex(TypeError, '^__int__ returned non-int '
R David Murray44b548d2016-09-08 13:59:53 -04004930 r'\(type float\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004931 datetime(10, 10, f10)
4932
4933 class Float(float):
4934 pass
4935 s10 = Float(10.9)
4936 with self.assertRaisesRegex(TypeError, '^integer argument expected, '
4937 'got float$'):
4938 datetime(10, 10, s10)
4939
4940 with self.assertRaises(TypeError):
4941 datetime(10., 10, 10)
4942 with self.assertRaises(TypeError):
4943 datetime(10, 10., 10)
4944 with self.assertRaises(TypeError):
4945 datetime(10, 10, 10.)
4946 with self.assertRaises(TypeError):
4947 datetime(10, 10, 10, 10.)
4948 with self.assertRaises(TypeError):
4949 datetime(10, 10, 10, 10, 10.)
4950 with self.assertRaises(TypeError):
4951 datetime(10, 10, 10, 10, 10, 10.)
4952 with self.assertRaises(TypeError):
4953 datetime(10, 10, 10, 10, 10, 10, 10.)
4954
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004955#############################################################################
4956# Local Time Disambiguation
4957
4958# An experimental reimplementation of fromutc that respects the "fold" flag.
4959
4960class tzinfo2(tzinfo):
4961
4962 def fromutc(self, dt):
4963 "datetime in UTC -> datetime in local time."
4964
4965 if not isinstance(dt, datetime):
4966 raise TypeError("fromutc() requires a datetime argument")
4967 if dt.tzinfo is not self:
4968 raise ValueError("dt.tzinfo is not self")
4969 # Returned value satisfies
4970 # dt + ldt.utcoffset() = ldt
4971 off0 = dt.replace(fold=0).utcoffset()
4972 off1 = dt.replace(fold=1).utcoffset()
4973 if off0 is None or off1 is None or dt.dst() is None:
4974 raise ValueError
4975 if off0 == off1:
4976 ldt = dt + off0
4977 off1 = ldt.utcoffset()
4978 if off0 == off1:
4979 return ldt
4980 # Now, we discovered both possible offsets, so
4981 # we can just try four possible solutions:
4982 for off in [off0, off1]:
4983 ldt = dt + off
4984 if ldt.utcoffset() == off:
4985 return ldt
4986 ldt = ldt.replace(fold=1)
4987 if ldt.utcoffset() == off:
4988 return ldt
4989
4990 raise ValueError("No suitable local time found")
4991
4992# Reimplementing simplified US timezones to respect the "fold" flag:
4993
4994class USTimeZone2(tzinfo2):
4995
4996 def __init__(self, hours, reprname, stdname, dstname):
4997 self.stdoffset = timedelta(hours=hours)
4998 self.reprname = reprname
4999 self.stdname = stdname
5000 self.dstname = dstname
5001
5002 def __repr__(self):
5003 return self.reprname
5004
5005 def tzname(self, dt):
5006 if self.dst(dt):
5007 return self.dstname
5008 else:
5009 return self.stdname
5010
5011 def utcoffset(self, dt):
5012 return self.stdoffset + self.dst(dt)
5013
5014 def dst(self, dt):
5015 if dt is None or dt.tzinfo is None:
5016 # An exception instead may be sensible here, in one or more of
5017 # the cases.
5018 return ZERO
5019 assert dt.tzinfo is self
5020
5021 # Find first Sunday in April.
5022 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
5023 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
5024
5025 # Find last Sunday in October.
5026 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
5027 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
5028
5029 # Can't compare naive to aware objects, so strip the timezone from
5030 # dt first.
5031 dt = dt.replace(tzinfo=None)
5032 if start + HOUR <= dt < end:
5033 # DST is in effect.
5034 return HOUR
5035 elif end <= dt < end + HOUR:
5036 # Fold (an ambiguous hour): use dt.fold to disambiguate.
5037 return ZERO if dt.fold else HOUR
5038 elif start <= dt < start + HOUR:
5039 # Gap (a non-existent hour): reverse the fold rule.
5040 return HOUR if dt.fold else ZERO
5041 else:
5042 # DST is off.
5043 return ZERO
5044
5045Eastern2 = USTimeZone2(-5, "Eastern2", "EST", "EDT")
5046Central2 = USTimeZone2(-6, "Central2", "CST", "CDT")
5047Mountain2 = USTimeZone2(-7, "Mountain2", "MST", "MDT")
5048Pacific2 = USTimeZone2(-8, "Pacific2", "PST", "PDT")
5049
5050# Europe_Vilnius_1941 tzinfo implementation reproduces the following
5051# 1941 transition from Olson's tzdist:
5052#
5053# Zone NAME GMTOFF RULES FORMAT [UNTIL]
5054# ZoneEurope/Vilnius 1:00 - CET 1940 Aug 3
5055# 3:00 - MSK 1941 Jun 24
5056# 1:00 C-Eur CE%sT 1944 Aug
5057#
5058# $ zdump -v Europe/Vilnius | grep 1941
5059# Europe/Vilnius Mon Jun 23 20:59:59 1941 UTC = Mon Jun 23 23:59:59 1941 MSK isdst=0 gmtoff=10800
5060# Europe/Vilnius Mon Jun 23 21:00:00 1941 UTC = Mon Jun 23 23:00:00 1941 CEST isdst=1 gmtoff=7200
5061
5062class Europe_Vilnius_1941(tzinfo):
5063 def _utc_fold(self):
5064 return [datetime(1941, 6, 23, 21, tzinfo=self), # Mon Jun 23 21:00:00 1941 UTC
5065 datetime(1941, 6, 23, 22, tzinfo=self)] # Mon Jun 23 22:00:00 1941 UTC
5066
5067 def _loc_fold(self):
5068 return [datetime(1941, 6, 23, 23, tzinfo=self), # Mon Jun 23 23:00:00 1941 MSK / CEST
5069 datetime(1941, 6, 24, 0, tzinfo=self)] # Mon Jun 24 00:00:00 1941 CEST
5070
5071 def utcoffset(self, dt):
5072 fold_start, fold_stop = self._loc_fold()
5073 if dt < fold_start:
5074 return 3 * HOUR
5075 if dt < fold_stop:
5076 return (2 if dt.fold else 3) * HOUR
5077 # if dt >= fold_stop
5078 return 2 * HOUR
5079
5080 def dst(self, dt):
5081 fold_start, fold_stop = self._loc_fold()
5082 if dt < fold_start:
5083 return 0 * HOUR
5084 if dt < fold_stop:
5085 return (1 if dt.fold else 0) * HOUR
5086 # if dt >= fold_stop
5087 return 1 * HOUR
5088
5089 def tzname(self, dt):
5090 fold_start, fold_stop = self._loc_fold()
5091 if dt < fold_start:
5092 return 'MSK'
5093 if dt < fold_stop:
5094 return ('MSK', 'CEST')[dt.fold]
5095 # if dt >= fold_stop
5096 return 'CEST'
5097
5098 def fromutc(self, dt):
5099 assert dt.fold == 0
5100 assert dt.tzinfo is self
5101 if dt.year != 1941:
5102 raise NotImplementedError
5103 fold_start, fold_stop = self._utc_fold()
5104 if dt < fold_start:
5105 return dt + 3 * HOUR
5106 if dt < fold_stop:
5107 return (dt + 2 * HOUR).replace(fold=1)
5108 # if dt >= fold_stop
5109 return dt + 2 * HOUR
5110
5111
5112class TestLocalTimeDisambiguation(unittest.TestCase):
5113
5114 def test_vilnius_1941_fromutc(self):
5115 Vilnius = Europe_Vilnius_1941()
5116
5117 gdt = datetime(1941, 6, 23, 20, 59, 59, tzinfo=timezone.utc)
5118 ldt = gdt.astimezone(Vilnius)
5119 self.assertEqual(ldt.strftime("%c %Z%z"),
5120 'Mon Jun 23 23:59:59 1941 MSK+0300')
5121 self.assertEqual(ldt.fold, 0)
5122 self.assertFalse(ldt.dst())
5123
5124 gdt = datetime(1941, 6, 23, 21, tzinfo=timezone.utc)
5125 ldt = gdt.astimezone(Vilnius)
5126 self.assertEqual(ldt.strftime("%c %Z%z"),
5127 'Mon Jun 23 23:00:00 1941 CEST+0200')
5128 self.assertEqual(ldt.fold, 1)
5129 self.assertTrue(ldt.dst())
5130
5131 gdt = datetime(1941, 6, 23, 22, tzinfo=timezone.utc)
5132 ldt = gdt.astimezone(Vilnius)
5133 self.assertEqual(ldt.strftime("%c %Z%z"),
5134 'Tue Jun 24 00:00:00 1941 CEST+0200')
5135 self.assertEqual(ldt.fold, 0)
5136 self.assertTrue(ldt.dst())
5137
5138 def test_vilnius_1941_toutc(self):
5139 Vilnius = Europe_Vilnius_1941()
5140
5141 ldt = datetime(1941, 6, 23, 22, 59, 59, tzinfo=Vilnius)
5142 gdt = ldt.astimezone(timezone.utc)
5143 self.assertEqual(gdt.strftime("%c %Z"),
5144 'Mon Jun 23 19:59:59 1941 UTC')
5145
5146 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius)
5147 gdt = ldt.astimezone(timezone.utc)
5148 self.assertEqual(gdt.strftime("%c %Z"),
5149 'Mon Jun 23 20:59:59 1941 UTC')
5150
5151 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius, fold=1)
5152 gdt = ldt.astimezone(timezone.utc)
5153 self.assertEqual(gdt.strftime("%c %Z"),
5154 'Mon Jun 23 21:59:59 1941 UTC')
5155
5156 ldt = datetime(1941, 6, 24, 0, tzinfo=Vilnius)
5157 gdt = ldt.astimezone(timezone.utc)
5158 self.assertEqual(gdt.strftime("%c %Z"),
5159 'Mon Jun 23 22:00:00 1941 UTC')
5160
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005161 def test_constructors(self):
5162 t = time(0, fold=1)
5163 dt = datetime(1, 1, 1, fold=1)
5164 self.assertEqual(t.fold, 1)
5165 self.assertEqual(dt.fold, 1)
5166 with self.assertRaises(TypeError):
5167 time(0, 0, 0, 0, None, 0)
5168
5169 def test_member(self):
5170 dt = datetime(1, 1, 1, fold=1)
5171 t = dt.time()
5172 self.assertEqual(t.fold, 1)
5173 t = dt.timetz()
5174 self.assertEqual(t.fold, 1)
5175
5176 def test_replace(self):
5177 t = time(0)
5178 dt = datetime(1, 1, 1)
5179 self.assertEqual(t.replace(fold=1).fold, 1)
5180 self.assertEqual(dt.replace(fold=1).fold, 1)
5181 self.assertEqual(t.replace(fold=0).fold, 0)
5182 self.assertEqual(dt.replace(fold=0).fold, 0)
5183 # Check that replacement of other fields does not change "fold".
5184 t = t.replace(fold=1, tzinfo=Eastern)
5185 dt = dt.replace(fold=1, tzinfo=Eastern)
5186 self.assertEqual(t.replace(tzinfo=None).fold, 1)
5187 self.assertEqual(dt.replace(tzinfo=None).fold, 1)
Serhiy Storchaka314d6fc2017-03-31 22:48:16 +03005188 # Out of bounds.
5189 with self.assertRaises(ValueError):
5190 t.replace(fold=2)
5191 with self.assertRaises(ValueError):
5192 dt.replace(fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005193 # Check that fold is a keyword-only argument
5194 with self.assertRaises(TypeError):
5195 t.replace(1, 1, 1, None, 1)
5196 with self.assertRaises(TypeError):
5197 dt.replace(1, 1, 1, 1, 1, 1, 1, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04005198
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005199 def test_comparison(self):
5200 t = time(0)
5201 dt = datetime(1, 1, 1)
5202 self.assertEqual(t, t.replace(fold=1))
5203 self.assertEqual(dt, dt.replace(fold=1))
5204
5205 def test_hash(self):
5206 t = time(0)
5207 dt = datetime(1, 1, 1)
5208 self.assertEqual(hash(t), hash(t.replace(fold=1)))
5209 self.assertEqual(hash(dt), hash(dt.replace(fold=1)))
5210
5211 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5212 def test_fromtimestamp(self):
5213 s = 1414906200
5214 dt0 = datetime.fromtimestamp(s)
5215 dt1 = datetime.fromtimestamp(s + 3600)
5216 self.assertEqual(dt0.fold, 0)
5217 self.assertEqual(dt1.fold, 1)
5218
5219 @support.run_with_tz('Australia/Lord_Howe')
5220 def test_fromtimestamp_lord_howe(self):
5221 tm = _time.localtime(1.4e9)
5222 if _time.strftime('%Z%z', tm) != 'LHST+1030':
5223 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
5224 # $ TZ=Australia/Lord_Howe date -r 1428158700
5225 # Sun Apr 5 01:45:00 LHDT 2015
5226 # $ TZ=Australia/Lord_Howe date -r 1428160500
5227 # Sun Apr 5 01:45:00 LHST 2015
5228 s = 1428158700
5229 t0 = datetime.fromtimestamp(s)
5230 t1 = datetime.fromtimestamp(s + 1800)
5231 self.assertEqual(t0, t1)
5232 self.assertEqual(t0.fold, 0)
5233 self.assertEqual(t1.fold, 1)
5234
Ammar Askar96d1e692018-07-25 09:54:58 -07005235 def test_fromtimestamp_low_fold_detection(self):
5236 # Ensure that fold detection doesn't cause an
5237 # OSError for really low values, see bpo-29097
5238 self.assertEqual(datetime.fromtimestamp(0).fold, 0)
5239
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005240 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5241 def test_timestamp(self):
5242 dt0 = datetime(2014, 11, 2, 1, 30)
5243 dt1 = dt0.replace(fold=1)
5244 self.assertEqual(dt0.timestamp() + 3600,
5245 dt1.timestamp())
5246
5247 @support.run_with_tz('Australia/Lord_Howe')
5248 def test_timestamp_lord_howe(self):
5249 tm = _time.localtime(1.4e9)
5250 if _time.strftime('%Z%z', tm) != 'LHST+1030':
5251 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
5252 t = datetime(2015, 4, 5, 1, 45)
5253 s0 = t.replace(fold=0).timestamp()
5254 s1 = t.replace(fold=1).timestamp()
5255 self.assertEqual(s0 + 1800, s1)
5256
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005257 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5258 def test_astimezone(self):
5259 dt0 = datetime(2014, 11, 2, 1, 30)
5260 dt1 = dt0.replace(fold=1)
5261 # Convert both naive instances to aware.
5262 adt0 = dt0.astimezone()
5263 adt1 = dt1.astimezone()
5264 # Check that the first instance in DST zone and the second in STD
5265 self.assertEqual(adt0.tzname(), 'EDT')
5266 self.assertEqual(adt1.tzname(), 'EST')
5267 self.assertEqual(adt0 + HOUR, adt1)
5268 # Aware instances with fixed offset tzinfo's always have fold=0
5269 self.assertEqual(adt0.fold, 0)
5270 self.assertEqual(adt1.fold, 0)
5271
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005272 def test_pickle_fold(self):
5273 t = time(fold=1)
5274 dt = datetime(1, 1, 1, fold=1)
5275 for pickler, unpickler, proto in pickle_choices:
5276 for x in [t, dt]:
5277 s = pickler.dumps(x, proto)
5278 y = unpickler.loads(s)
5279 self.assertEqual(x, y)
5280 self.assertEqual((0 if proto < 4 else x.fold), y.fold)
5281
5282 def test_repr(self):
5283 t = time(fold=1)
5284 dt = datetime(1, 1, 1, fold=1)
5285 self.assertEqual(repr(t), 'datetime.time(0, 0, fold=1)')
5286 self.assertEqual(repr(dt),
5287 'datetime.datetime(1, 1, 1, 0, 0, fold=1)')
5288
5289 def test_dst(self):
5290 # Let's first establish that things work in regular times.
5291 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
5292 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
5293 self.assertEqual(dt_summer.dst(), HOUR)
5294 self.assertEqual(dt_winter.dst(), ZERO)
5295 # The disambiguation flag is ignored
5296 self.assertEqual(dt_summer.replace(fold=1).dst(), HOUR)
5297 self.assertEqual(dt_winter.replace(fold=1).dst(), ZERO)
5298
5299 # Pick local time in the fold.
5300 for minute in [0, 30, 59]:
5301 dt = datetime(2002, 10, 27, 1, minute, tzinfo=Eastern2)
5302 # With fold=0 (the default) it is in DST.
5303 self.assertEqual(dt.dst(), HOUR)
5304 # With fold=1 it is in STD.
5305 self.assertEqual(dt.replace(fold=1).dst(), ZERO)
5306
5307 # Pick local time in the gap.
5308 for minute in [0, 30, 59]:
5309 dt = datetime(2002, 4, 7, 2, minute, tzinfo=Eastern2)
5310 # With fold=0 (the default) it is in STD.
5311 self.assertEqual(dt.dst(), ZERO)
5312 # With fold=1 it is in DST.
5313 self.assertEqual(dt.replace(fold=1).dst(), HOUR)
5314
5315
5316 def test_utcoffset(self):
5317 # Let's first establish that things work in regular times.
5318 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
5319 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
5320 self.assertEqual(dt_summer.utcoffset(), -4 * HOUR)
5321 self.assertEqual(dt_winter.utcoffset(), -5 * HOUR)
5322 # The disambiguation flag is ignored
5323 self.assertEqual(dt_summer.replace(fold=1).utcoffset(), -4 * HOUR)
5324 self.assertEqual(dt_winter.replace(fold=1).utcoffset(), -5 * HOUR)
5325
5326 def test_fromutc(self):
5327 # Let's first establish that things work in regular times.
5328 u_summer = datetime(2002, 10, 27, 6, tzinfo=Eastern2) - timedelta.resolution
5329 u_winter = datetime(2002, 10, 27, 7, tzinfo=Eastern2)
5330 t_summer = Eastern2.fromutc(u_summer)
5331 t_winter = Eastern2.fromutc(u_winter)
5332 self.assertEqual(t_summer, u_summer - 4 * HOUR)
5333 self.assertEqual(t_winter, u_winter - 5 * HOUR)
5334 self.assertEqual(t_summer.fold, 0)
5335 self.assertEqual(t_winter.fold, 0)
5336
5337 # What happens in the fall-back fold?
5338 u = datetime(2002, 10, 27, 5, 30, tzinfo=Eastern2)
5339 t0 = Eastern2.fromutc(u)
5340 u += HOUR
5341 t1 = Eastern2.fromutc(u)
5342 self.assertEqual(t0, t1)
5343 self.assertEqual(t0.fold, 0)
5344 self.assertEqual(t1.fold, 1)
5345 # The tricky part is when u is in the local fold:
5346 u = datetime(2002, 10, 27, 1, 30, tzinfo=Eastern2)
5347 t = Eastern2.fromutc(u)
5348 self.assertEqual((t.day, t.hour), (26, 21))
5349 # .. or gets into the local fold after a standard time adjustment
5350 u = datetime(2002, 10, 27, 6, 30, tzinfo=Eastern2)
5351 t = Eastern2.fromutc(u)
5352 self.assertEqual((t.day, t.hour), (27, 1))
5353
5354 # What happens in the spring-forward gap?
5355 u = datetime(2002, 4, 7, 2, 0, tzinfo=Eastern2)
5356 t = Eastern2.fromutc(u)
5357 self.assertEqual((t.day, t.hour), (6, 21))
5358
5359 def test_mixed_compare_regular(self):
5360 t = datetime(2000, 1, 1, tzinfo=Eastern2)
5361 self.assertEqual(t, t.astimezone(timezone.utc))
5362 t = datetime(2000, 6, 1, tzinfo=Eastern2)
5363 self.assertEqual(t, t.astimezone(timezone.utc))
5364
5365 def test_mixed_compare_fold(self):
5366 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
5367 t_fold_utc = t_fold.astimezone(timezone.utc)
5368 self.assertNotEqual(t_fold, t_fold_utc)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04005369 self.assertNotEqual(t_fold_utc, t_fold)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005370
5371 def test_mixed_compare_gap(self):
5372 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
5373 t_gap_utc = t_gap.astimezone(timezone.utc)
5374 self.assertNotEqual(t_gap, t_gap_utc)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04005375 self.assertNotEqual(t_gap_utc, t_gap)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005376
5377 def test_hash_aware(self):
5378 t = datetime(2000, 1, 1, tzinfo=Eastern2)
5379 self.assertEqual(hash(t), hash(t.replace(fold=1)))
5380 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
5381 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
5382 self.assertEqual(hash(t_fold), hash(t_fold.replace(fold=1)))
5383 self.assertEqual(hash(t_gap), hash(t_gap.replace(fold=1)))
5384
5385SEC = timedelta(0, 1)
5386
5387def pairs(iterable):
5388 a, b = itertools.tee(iterable)
5389 next(b, None)
5390 return zip(a, b)
5391
5392class ZoneInfo(tzinfo):
5393 zoneroot = '/usr/share/zoneinfo'
5394 def __init__(self, ut, ti):
5395 """
5396
5397 :param ut: array
5398 Array of transition point timestamps
5399 :param ti: list
5400 A list of (offset, isdst, abbr) tuples
5401 :return: None
5402 """
5403 self.ut = ut
5404 self.ti = ti
5405 self.lt = self.invert(ut, ti)
5406
5407 @staticmethod
5408 def invert(ut, ti):
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04005409 lt = (array('q', ut), array('q', ut))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005410 if ut:
5411 offset = ti[0][0] // SEC
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04005412 lt[0][0] += offset
5413 lt[1][0] += offset
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005414 for i in range(1, len(ut)):
5415 lt[0][i] += ti[i-1][0] // SEC
5416 lt[1][i] += ti[i][0] // SEC
5417 return lt
5418
5419 @classmethod
5420 def fromfile(cls, fileobj):
5421 if fileobj.read(4).decode() != "TZif":
5422 raise ValueError("not a zoneinfo file")
5423 fileobj.seek(32)
5424 counts = array('i')
5425 counts.fromfile(fileobj, 3)
5426 if sys.byteorder != 'big':
5427 counts.byteswap()
5428
5429 ut = array('i')
5430 ut.fromfile(fileobj, counts[0])
5431 if sys.byteorder != 'big':
5432 ut.byteswap()
5433
5434 type_indices = array('B')
5435 type_indices.fromfile(fileobj, counts[0])
5436
5437 ttis = []
5438 for i in range(counts[1]):
5439 ttis.append(struct.unpack(">lbb", fileobj.read(6)))
5440
5441 abbrs = fileobj.read(counts[2])
5442
5443 # Convert ttis
5444 for i, (gmtoff, isdst, abbrind) in enumerate(ttis):
5445 abbr = abbrs[abbrind:abbrs.find(0, abbrind)].decode()
5446 ttis[i] = (timedelta(0, gmtoff), isdst, abbr)
5447
5448 ti = [None] * len(ut)
5449 for i, idx in enumerate(type_indices):
5450 ti[i] = ttis[idx]
5451
5452 self = cls(ut, ti)
5453
5454 return self
5455
5456 @classmethod
5457 def fromname(cls, name):
5458 path = os.path.join(cls.zoneroot, name)
5459 with open(path, 'rb') as f:
5460 return cls.fromfile(f)
5461
5462 EPOCHORDINAL = date(1970, 1, 1).toordinal()
5463
5464 def fromutc(self, dt):
5465 """datetime in UTC -> datetime in local time."""
5466
5467 if not isinstance(dt, datetime):
5468 raise TypeError("fromutc() requires a datetime argument")
5469 if dt.tzinfo is not self:
5470 raise ValueError("dt.tzinfo is not self")
5471
5472 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
5473 + dt.hour * 3600
5474 + dt.minute * 60
5475 + dt.second)
5476
5477 if timestamp < self.ut[1]:
5478 tti = self.ti[0]
5479 fold = 0
5480 else:
5481 idx = bisect.bisect_right(self.ut, timestamp)
5482 assert self.ut[idx-1] <= timestamp
5483 assert idx == len(self.ut) or timestamp < self.ut[idx]
5484 tti_prev, tti = self.ti[idx-2:idx]
5485 # Detect fold
5486 shift = tti_prev[0] - tti[0]
5487 fold = (shift > timedelta(0, timestamp - self.ut[idx-1]))
5488 dt += tti[0]
5489 if fold:
5490 return dt.replace(fold=1)
5491 else:
5492 return dt
5493
5494 def _find_ti(self, dt, i):
5495 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
5496 + dt.hour * 3600
5497 + dt.minute * 60
5498 + dt.second)
5499 lt = self.lt[dt.fold]
5500 idx = bisect.bisect_right(lt, timestamp)
5501
5502 return self.ti[max(0, idx - 1)][i]
5503
5504 def utcoffset(self, dt):
5505 return self._find_ti(dt, 0)
5506
5507 def dst(self, dt):
5508 isdst = self._find_ti(dt, 1)
5509 # XXX: We cannot accurately determine the "save" value,
5510 # so let's return 1h whenever DST is in effect. Since
5511 # we don't use dst() in fromutc(), it is unlikely that
5512 # it will be needed for anything more than bool(dst()).
5513 return ZERO if isdst else HOUR
5514
5515 def tzname(self, dt):
5516 return self._find_ti(dt, 2)
5517
5518 @classmethod
5519 def zonenames(cls, zonedir=None):
5520 if zonedir is None:
5521 zonedir = cls.zoneroot
Alexander Belopolsky1b8f26c2016-08-11 11:01:45 -04005522 zone_tab = os.path.join(zonedir, 'zone.tab')
5523 try:
5524 f = open(zone_tab)
5525 except OSError:
5526 return
5527 with f:
5528 for line in f:
5529 line = line.strip()
5530 if line and not line.startswith('#'):
5531 yield line.split()[2]
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005532
5533 @classmethod
5534 def stats(cls, start_year=1):
5535 count = gap_count = fold_count = zeros_count = 0
5536 min_gap = min_fold = timedelta.max
5537 max_gap = max_fold = ZERO
5538 min_gap_datetime = max_gap_datetime = datetime.min
5539 min_gap_zone = max_gap_zone = None
5540 min_fold_datetime = max_fold_datetime = datetime.min
5541 min_fold_zone = max_fold_zone = None
5542 stats_since = datetime(start_year, 1, 1) # Starting from 1970 eliminates a lot of noise
5543 for zonename in cls.zonenames():
5544 count += 1
5545 tz = cls.fromname(zonename)
5546 for dt, shift in tz.transitions():
5547 if dt < stats_since:
5548 continue
5549 if shift > ZERO:
5550 gap_count += 1
5551 if (shift, dt) > (max_gap, max_gap_datetime):
5552 max_gap = shift
5553 max_gap_zone = zonename
5554 max_gap_datetime = dt
5555 if (shift, datetime.max - dt) < (min_gap, datetime.max - min_gap_datetime):
5556 min_gap = shift
5557 min_gap_zone = zonename
5558 min_gap_datetime = dt
5559 elif shift < ZERO:
5560 fold_count += 1
5561 shift = -shift
5562 if (shift, dt) > (max_fold, max_fold_datetime):
5563 max_fold = shift
5564 max_fold_zone = zonename
5565 max_fold_datetime = dt
5566 if (shift, datetime.max - dt) < (min_fold, datetime.max - min_fold_datetime):
5567 min_fold = shift
5568 min_fold_zone = zonename
5569 min_fold_datetime = dt
5570 else:
5571 zeros_count += 1
5572 trans_counts = (gap_count, fold_count, zeros_count)
5573 print("Number of zones: %5d" % count)
5574 print("Number of transitions: %5d = %d (gaps) + %d (folds) + %d (zeros)" %
5575 ((sum(trans_counts),) + trans_counts))
5576 print("Min gap: %16s at %s in %s" % (min_gap, min_gap_datetime, min_gap_zone))
5577 print("Max gap: %16s at %s in %s" % (max_gap, max_gap_datetime, max_gap_zone))
5578 print("Min fold: %16s at %s in %s" % (min_fold, min_fold_datetime, min_fold_zone))
5579 print("Max fold: %16s at %s in %s" % (max_fold, max_fold_datetime, max_fold_zone))
5580
5581
5582 def transitions(self):
5583 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
5584 shift = ti[0] - prev_ti[0]
5585 yield datetime.utcfromtimestamp(t), shift
5586
5587 def nondst_folds(self):
5588 """Find all folds with the same value of isdst on both sides of the transition."""
5589 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
5590 shift = ti[0] - prev_ti[0]
5591 if shift < ZERO and ti[1] == prev_ti[1]:
5592 yield datetime.utcfromtimestamp(t), -shift, prev_ti[2], ti[2]
5593
5594 @classmethod
5595 def print_all_nondst_folds(cls, same_abbr=False, start_year=1):
5596 count = 0
5597 for zonename in cls.zonenames():
5598 tz = cls.fromname(zonename)
5599 for dt, shift, prev_abbr, abbr in tz.nondst_folds():
5600 if dt.year < start_year or same_abbr and prev_abbr != abbr:
5601 continue
5602 count += 1
5603 print("%3d) %-30s %s %10s %5s -> %s" %
5604 (count, zonename, dt, shift, prev_abbr, abbr))
5605
5606 def folds(self):
5607 for t, shift in self.transitions():
5608 if shift < ZERO:
5609 yield t, -shift
5610
5611 def gaps(self):
5612 for t, shift in self.transitions():
5613 if shift > ZERO:
5614 yield t, shift
5615
5616 def zeros(self):
5617 for t, shift in self.transitions():
5618 if not shift:
5619 yield t
5620
5621
5622class ZoneInfoTest(unittest.TestCase):
5623 zonename = 'America/New_York'
5624
5625 def setUp(self):
5626 if sys.platform == "win32":
5627 self.skipTest("Skipping zoneinfo tests on Windows")
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04005628 try:
5629 self.tz = ZoneInfo.fromname(self.zonename)
5630 except FileNotFoundError as err:
5631 self.skipTest("Skipping %s: %s" % (self.zonename, err))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005632
5633 def assertEquivDatetimes(self, a, b):
5634 self.assertEqual((a.replace(tzinfo=None), a.fold, id(a.tzinfo)),
5635 (b.replace(tzinfo=None), b.fold, id(b.tzinfo)))
5636
5637 def test_folds(self):
5638 tz = self.tz
5639 for dt, shift in tz.folds():
5640 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
5641 udt = dt + x
5642 ldt = tz.fromutc(udt.replace(tzinfo=tz))
5643 self.assertEqual(ldt.fold, 1)
5644 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
5645 self.assertEquivDatetimes(adt, ldt)
5646 utcoffset = ldt.utcoffset()
5647 self.assertEqual(ldt.replace(tzinfo=None), udt + utcoffset)
5648 # Round trip
5649 self.assertEquivDatetimes(ldt.astimezone(timezone.utc),
5650 udt.replace(tzinfo=timezone.utc))
5651
5652
5653 for x in [-timedelta.resolution, shift]:
5654 udt = dt + x
5655 udt = udt.replace(tzinfo=tz)
5656 ldt = tz.fromutc(udt)
5657 self.assertEqual(ldt.fold, 0)
5658
5659 def test_gaps(self):
5660 tz = self.tz
5661 for dt, shift in tz.gaps():
5662 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
5663 udt = dt + x
5664 udt = udt.replace(tzinfo=tz)
5665 ldt = tz.fromutc(udt)
5666 self.assertEqual(ldt.fold, 0)
5667 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
5668 self.assertEquivDatetimes(adt, ldt)
5669 utcoffset = ldt.utcoffset()
5670 self.assertEqual(ldt.replace(tzinfo=None), udt.replace(tzinfo=None) + utcoffset)
5671 # Create a local time inside the gap
5672 ldt = tz.fromutc(dt.replace(tzinfo=tz)) - shift + x
5673 self.assertLess(ldt.replace(fold=1).utcoffset(),
5674 ldt.replace(fold=0).utcoffset(),
5675 "At %s." % ldt)
5676
5677 for x in [-timedelta.resolution, shift]:
5678 udt = dt + x
5679 ldt = tz.fromutc(udt.replace(tzinfo=tz))
5680 self.assertEqual(ldt.fold, 0)
5681
5682 def test_system_transitions(self):
5683 if ('Riyadh8' in self.zonename or
5684 # From tzdata NEWS file:
5685 # The files solar87, solar88, and solar89 are no longer distributed.
5686 # They were a negative experiment - that is, a demonstration that
5687 # tz data can represent solar time only with some difficulty and error.
5688 # Their presence in the distribution caused confusion, as Riyadh
5689 # civil time was generally not solar time in those years.
5690 self.zonename.startswith('right/')):
5691 self.skipTest("Skipping %s" % self.zonename)
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04005692 tz = self.tz
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005693 TZ = os.environ.get('TZ')
5694 os.environ['TZ'] = self.zonename
5695 try:
5696 _time.tzset()
5697 for udt, shift in tz.transitions():
Alexander Belopolsky10c2dd22016-08-12 19:08:15 -04005698 if udt.year >= 2037:
5699 # System support for times around the end of 32-bit time_t
5700 # and later is flaky on many systems.
5701 break
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005702 s0 = (udt - datetime(1970, 1, 1)) // SEC
5703 ss = shift // SEC # shift seconds
5704 for x in [-40 * 3600, -20*3600, -1, 0,
5705 ss - 1, ss + 20 * 3600, ss + 40 * 3600]:
5706 s = s0 + x
5707 sdt = datetime.fromtimestamp(s)
5708 tzdt = datetime.fromtimestamp(s, tz).replace(tzinfo=None)
5709 self.assertEquivDatetimes(sdt, tzdt)
5710 s1 = sdt.timestamp()
5711 self.assertEqual(s, s1)
5712 if ss > 0: # gap
5713 # Create local time inside the gap
5714 dt = datetime.fromtimestamp(s0) - shift / 2
5715 ts0 = dt.timestamp()
5716 ts1 = dt.replace(fold=1).timestamp()
5717 self.assertEqual(ts0, s0 + ss / 2)
5718 self.assertEqual(ts1, s0 - ss / 2)
5719 finally:
5720 if TZ is None:
5721 del os.environ['TZ']
5722 else:
5723 os.environ['TZ'] = TZ
5724 _time.tzset()
5725
5726
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04005727class ZoneInfoCompleteTest(unittest.TestSuite):
5728 def __init__(self):
5729 tests = []
5730 if is_resource_enabled('tzdata'):
5731 for name in ZoneInfo.zonenames():
5732 Test = type('ZoneInfoTest[%s]' % name, (ZoneInfoTest,), {})
5733 Test.zonename = name
5734 for method in dir(Test):
5735 if method.startswith('test_'):
5736 tests.append(Test(method))
5737 super().__init__(tests)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005738
5739# Iran had a sub-minute UTC offset before 1946.
5740class IranTest(ZoneInfoTest):
Alexander Belopolsky07e2a0a2016-07-30 11:41:02 -04005741 zonename = 'Asia/Tehran'
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005742
Paul Ganssle04af5b12018-01-24 17:29:30 -05005743
5744class CapiTest(unittest.TestCase):
5745 def setUp(self):
5746 # Since the C API is not present in the _Pure tests, skip all tests
5747 if self.__class__.__name__.endswith('Pure'):
5748 self.skipTest('Not relevant in pure Python')
5749
5750 # This *must* be called, and it must be called first, so until either
5751 # restriction is loosened, we'll call it as part of test setup
5752 _testcapi.test_datetime_capi()
5753
5754 def test_utc_capi(self):
5755 for use_macro in (True, False):
5756 capi_utc = _testcapi.get_timezone_utc_capi(use_macro)
5757
5758 with self.subTest(use_macro=use_macro):
5759 self.assertIs(capi_utc, timezone.utc)
5760
5761 def test_timezones_capi(self):
5762 est_capi, est_macro, est_macro_nn = _testcapi.make_timezones_capi()
5763
5764 exp_named = timezone(timedelta(hours=-5), "EST")
5765 exp_unnamed = timezone(timedelta(hours=-5))
5766
5767 cases = [
5768 ('est_capi', est_capi, exp_named),
5769 ('est_macro', est_macro, exp_named),
5770 ('est_macro_nn', est_macro_nn, exp_unnamed)
5771 ]
5772
5773 for name, tz_act, tz_exp in cases:
5774 with self.subTest(name=name):
5775 self.assertEqual(tz_act, tz_exp)
5776
5777 dt1 = datetime(2000, 2, 4, tzinfo=tz_act)
5778 dt2 = datetime(2000, 2, 4, tzinfo=tz_exp)
5779
5780 self.assertEqual(dt1, dt2)
5781 self.assertEqual(dt1.tzname(), dt2.tzname())
5782
5783 dt_utc = datetime(2000, 2, 4, 5, tzinfo=timezone.utc)
5784
5785 self.assertEqual(dt1.astimezone(timezone.utc), dt_utc)
5786
Paul Gansslea049f572018-02-22 15:15:32 -05005787 def test_timezones_offset_zero(self):
5788 utc0, utc1, non_utc = _testcapi.get_timezones_offset_zero()
5789
5790 with self.subTest(testname="utc0"):
5791 self.assertIs(utc0, timezone.utc)
5792
5793 with self.subTest(testname="utc1"):
5794 self.assertIs(utc1, timezone.utc)
5795
5796 with self.subTest(testname="non_utc"):
5797 self.assertIsNot(non_utc, timezone.utc)
5798
5799 non_utc_exp = timezone(timedelta(hours=0), "")
5800
5801 self.assertEqual(non_utc, non_utc_exp)
5802
5803 dt1 = datetime(2000, 2, 4, tzinfo=non_utc)
5804 dt2 = datetime(2000, 2, 4, tzinfo=non_utc_exp)
5805
5806 self.assertEqual(dt1, dt2)
5807 self.assertEqual(dt1.tzname(), dt2.tzname())
5808
Paul Ganssle04af5b12018-01-24 17:29:30 -05005809 def test_check_date(self):
5810 class DateSubclass(date):
5811 pass
5812
5813 d = date(2011, 1, 1)
5814 ds = DateSubclass(2011, 1, 1)
5815 dt = datetime(2011, 1, 1)
5816
5817 is_date = _testcapi.datetime_check_date
5818
5819 # Check the ones that should be valid
5820 self.assertTrue(is_date(d))
5821 self.assertTrue(is_date(dt))
5822 self.assertTrue(is_date(ds))
5823 self.assertTrue(is_date(d, True))
5824
5825 # Check that the subclasses do not match exactly
5826 self.assertFalse(is_date(dt, True))
5827 self.assertFalse(is_date(ds, True))
5828
5829 # Check that various other things are not dates at all
5830 args = [tuple(), list(), 1, '2011-01-01',
5831 timedelta(1), timezone.utc, time(12, 00)]
5832 for arg in args:
5833 for exact in (True, False):
5834 with self.subTest(arg=arg, exact=exact):
5835 self.assertFalse(is_date(arg, exact))
5836
5837 def test_check_time(self):
5838 class TimeSubclass(time):
5839 pass
5840
5841 t = time(12, 30)
5842 ts = TimeSubclass(12, 30)
5843
5844 is_time = _testcapi.datetime_check_time
5845
5846 # Check the ones that should be valid
5847 self.assertTrue(is_time(t))
5848 self.assertTrue(is_time(ts))
5849 self.assertTrue(is_time(t, True))
5850
5851 # Check that the subclass does not match exactly
5852 self.assertFalse(is_time(ts, True))
5853
5854 # Check that various other things are not times
5855 args = [tuple(), list(), 1, '2011-01-01',
5856 timedelta(1), timezone.utc, date(2011, 1, 1)]
5857
5858 for arg in args:
5859 for exact in (True, False):
5860 with self.subTest(arg=arg, exact=exact):
5861 self.assertFalse(is_time(arg, exact))
5862
5863 def test_check_datetime(self):
5864 class DateTimeSubclass(datetime):
5865 pass
5866
5867 dt = datetime(2011, 1, 1, 12, 30)
5868 dts = DateTimeSubclass(2011, 1, 1, 12, 30)
5869
5870 is_datetime = _testcapi.datetime_check_datetime
5871
5872 # Check the ones that should be valid
5873 self.assertTrue(is_datetime(dt))
5874 self.assertTrue(is_datetime(dts))
5875 self.assertTrue(is_datetime(dt, True))
5876
5877 # Check that the subclass does not match exactly
5878 self.assertFalse(is_datetime(dts, True))
5879
5880 # Check that various other things are not datetimes
5881 args = [tuple(), list(), 1, '2011-01-01',
5882 timedelta(1), timezone.utc, date(2011, 1, 1)]
5883
5884 for arg in args:
5885 for exact in (True, False):
5886 with self.subTest(arg=arg, exact=exact):
5887 self.assertFalse(is_datetime(arg, exact))
5888
5889 def test_check_delta(self):
5890 class TimeDeltaSubclass(timedelta):
5891 pass
5892
5893 td = timedelta(1)
5894 tds = TimeDeltaSubclass(1)
5895
5896 is_timedelta = _testcapi.datetime_check_delta
5897
5898 # Check the ones that should be valid
5899 self.assertTrue(is_timedelta(td))
5900 self.assertTrue(is_timedelta(tds))
5901 self.assertTrue(is_timedelta(td, True))
5902
5903 # Check that the subclass does not match exactly
5904 self.assertFalse(is_timedelta(tds, True))
5905
5906 # Check that various other things are not timedeltas
5907 args = [tuple(), list(), 1, '2011-01-01',
5908 timezone.utc, date(2011, 1, 1), datetime(2011, 1, 1)]
5909
5910 for arg in args:
5911 for exact in (True, False):
5912 with self.subTest(arg=arg, exact=exact):
5913 self.assertFalse(is_timedelta(arg, exact))
5914
5915 def test_check_tzinfo(self):
5916 class TZInfoSubclass(tzinfo):
5917 pass
5918
5919 tzi = tzinfo()
5920 tzis = TZInfoSubclass()
5921 tz = timezone(timedelta(hours=-5))
5922
5923 is_tzinfo = _testcapi.datetime_check_tzinfo
5924
5925 # Check the ones that should be valid
5926 self.assertTrue(is_tzinfo(tzi))
5927 self.assertTrue(is_tzinfo(tz))
5928 self.assertTrue(is_tzinfo(tzis))
5929 self.assertTrue(is_tzinfo(tzi, True))
5930
5931 # Check that the subclasses do not match exactly
5932 self.assertFalse(is_tzinfo(tz, True))
5933 self.assertFalse(is_tzinfo(tzis, True))
5934
5935 # Check that various other things are not tzinfos
5936 args = [tuple(), list(), 1, '2011-01-01',
5937 date(2011, 1, 1), datetime(2011, 1, 1)]
5938
5939 for arg in args:
5940 for exact in (True, False):
5941 with self.subTest(arg=arg, exact=exact):
5942 self.assertFalse(is_tzinfo(arg, exact))
5943
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04005944def load_tests(loader, standard_tests, pattern):
5945 standard_tests.addTest(ZoneInfoCompleteTest())
5946 return standard_tests
5947
5948
Alexander Belopolskycf86e362010-07-23 19:25:47 +00005949if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -05005950 unittest.main()