blob: 122f6b55ba33b17501e0ba54f553d2b4b8a4c04f [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
41
42pickle_choices = [(pickle, pickle, proto)
43 for proto in range(pickle.HIGHEST_PROTOCOL + 1)]
44assert len(pickle_choices) == pickle.HIGHEST_PROTOCOL + 1
45
46# An arbitrary collection of objects of non-datetime types, for testing
47# mixed-type comparisons.
48OTHERSTUFF = (10, 34.5, "abc", {}, [], ())
49
50
51# XXX Copied from test_float.
52INF = float("inf")
53NAN = float("nan")
54
Alexander Belopolskycf86e362010-07-23 19:25:47 +000055#############################################################################
56# module tests
57
58class TestModule(unittest.TestCase):
59
60 def test_constants(self):
61 datetime = datetime_module
62 self.assertEqual(datetime.MINYEAR, 1)
63 self.assertEqual(datetime.MAXYEAR, 9999)
64
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -040065 def test_name_cleanup(self):
Utkarsh Upadhyay287c5592017-07-21 02:14:54 +020066 if '_Pure' in self.__class__.__name__:
67 self.skipTest('Only run for Fast C implementation')
68
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -040069 datetime = datetime_module
70 names = set(name for name in dir(datetime)
71 if not name.startswith('__') and not name.endswith('__'))
72 allowed = set(['MAXYEAR', 'MINYEAR', 'date', 'datetime',
73 'datetime_CAPI', 'time', 'timedelta', 'timezone',
Ammar Askar96d1e692018-07-25 09:54:58 -070074 'tzinfo', 'sys'])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -040075 self.assertEqual(names - allowed, set([]))
76
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -050077 def test_divide_and_round(self):
Utkarsh Upadhyay287c5592017-07-21 02:14:54 +020078 if '_Fast' in self.__class__.__name__:
79 self.skipTest('Only run for Pure Python implementation')
80
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -050081 dar = datetime_module._divide_and_round
82
83 self.assertEqual(dar(-10, -3), 3)
84 self.assertEqual(dar(5, -2), -2)
85
86 # four cases: (2 signs of a) x (2 signs of b)
87 self.assertEqual(dar(7, 3), 2)
88 self.assertEqual(dar(-7, 3), -2)
89 self.assertEqual(dar(7, -3), -2)
90 self.assertEqual(dar(-7, -3), 2)
91
92 # ties to even - eight cases:
93 # (2 signs of a) x (2 signs of b) x (even / odd quotient)
94 self.assertEqual(dar(10, 4), 2)
95 self.assertEqual(dar(-10, 4), -2)
96 self.assertEqual(dar(10, -4), -2)
97 self.assertEqual(dar(-10, -4), 2)
98
99 self.assertEqual(dar(6, 4), 2)
100 self.assertEqual(dar(-6, 4), -2)
101 self.assertEqual(dar(6, -4), -2)
102 self.assertEqual(dar(-6, -4), 2)
103
104
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000105#############################################################################
106# tzinfo tests
107
108class FixedOffset(tzinfo):
109
110 def __init__(self, offset, name, dstoffset=42):
111 if isinstance(offset, int):
112 offset = timedelta(minutes=offset)
113 if isinstance(dstoffset, int):
114 dstoffset = timedelta(minutes=dstoffset)
115 self.__offset = offset
116 self.__name = name
117 self.__dstoffset = dstoffset
118 def __repr__(self):
119 return self.__name.lower()
120 def utcoffset(self, dt):
121 return self.__offset
122 def tzname(self, dt):
123 return self.__name
124 def dst(self, dt):
125 return self.__dstoffset
126
127class PicklableFixedOffset(FixedOffset):
128
129 def __init__(self, offset=None, name=None, dstoffset=None):
130 FixedOffset.__init__(self, offset, name, dstoffset)
131
Berker Peksage3385b42016-03-19 13:16:32 +0200132 def __getstate__(self):
133 return self.__dict__
134
Raymond Hettinger5a2146a2014-07-25 14:59:48 -0700135class _TZInfo(tzinfo):
136 def utcoffset(self, datetime_module):
137 return random.random()
138
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000139class TestTZInfo(unittest.TestCase):
140
Raymond Hettinger5a2146a2014-07-25 14:59:48 -0700141 def test_refcnt_crash_bug_22044(self):
142 tz1 = _TZInfo()
143 dt1 = datetime(2014, 7, 21, 11, 32, 3, 0, tz1)
144 with self.assertRaises(TypeError):
145 dt1.utcoffset()
146
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000147 def test_non_abstractness(self):
148 # In order to allow subclasses to get pickled, the C implementation
149 # wasn't able to get away with having __init__ raise
150 # NotImplementedError.
151 useless = tzinfo()
152 dt = datetime.max
153 self.assertRaises(NotImplementedError, useless.tzname, dt)
154 self.assertRaises(NotImplementedError, useless.utcoffset, dt)
155 self.assertRaises(NotImplementedError, useless.dst, dt)
156
157 def test_subclass_must_override(self):
158 class NotEnough(tzinfo):
159 def __init__(self, offset, name):
160 self.__offset = offset
161 self.__name = name
162 self.assertTrue(issubclass(NotEnough, tzinfo))
163 ne = NotEnough(3, "NotByALongShot")
164 self.assertIsInstance(ne, tzinfo)
165
166 dt = datetime.now()
167 self.assertRaises(NotImplementedError, ne.tzname, dt)
168 self.assertRaises(NotImplementedError, ne.utcoffset, dt)
169 self.assertRaises(NotImplementedError, ne.dst, dt)
170
171 def test_normal(self):
172 fo = FixedOffset(3, "Three")
173 self.assertIsInstance(fo, tzinfo)
174 for dt in datetime.now(), None:
175 self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3))
176 self.assertEqual(fo.tzname(dt), "Three")
177 self.assertEqual(fo.dst(dt), timedelta(minutes=42))
178
179 def test_pickling_base(self):
180 # There's no point to pickling tzinfo objects on their own (they
181 # carry no data), but they need to be picklable anyway else
182 # concrete subclasses can't be pickled.
183 orig = tzinfo.__new__(tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200184 self.assertIs(type(orig), tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000185 for pickler, unpickler, proto in pickle_choices:
186 green = pickler.dumps(orig, proto)
187 derived = unpickler.loads(green)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200188 self.assertIs(type(derived), tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000189
190 def test_pickling_subclass(self):
191 # Make sure we can pickle/unpickle an instance of a subclass.
192 offset = timedelta(minutes=-300)
193 for otype, args in [
194 (PicklableFixedOffset, (offset, 'cookie')),
195 (timezone, (offset,)),
196 (timezone, (offset, "EST"))]:
197 orig = otype(*args)
198 oname = orig.tzname(None)
199 self.assertIsInstance(orig, tzinfo)
200 self.assertIs(type(orig), otype)
201 self.assertEqual(orig.utcoffset(None), offset)
202 self.assertEqual(orig.tzname(None), oname)
203 for pickler, unpickler, proto in pickle_choices:
204 green = pickler.dumps(orig, proto)
205 derived = unpickler.loads(green)
206 self.assertIsInstance(derived, tzinfo)
207 self.assertIs(type(derived), otype)
208 self.assertEqual(derived.utcoffset(None), offset)
209 self.assertEqual(derived.tzname(None), oname)
210
Alexander Belopolskyc79447b2015-09-27 21:41:55 -0400211 def test_issue23600(self):
212 DSTDIFF = DSTOFFSET = timedelta(hours=1)
213
214 class UKSummerTime(tzinfo):
215 """Simple time zone which pretends to always be in summer time, since
216 that's what shows the failure.
217 """
218
219 def utcoffset(self, dt):
220 return DSTOFFSET
221
222 def dst(self, dt):
223 return DSTDIFF
224
225 def tzname(self, dt):
226 return 'UKSummerTime'
227
228 tz = UKSummerTime()
229 u = datetime(2014, 4, 26, 12, 1, tzinfo=tz)
230 t = tz.fromutc(u)
231 self.assertEqual(t - t.utcoffset(), u)
232
233
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000234class TestTimeZone(unittest.TestCase):
235
236 def setUp(self):
237 self.ACDT = timezone(timedelta(hours=9.5), 'ACDT')
238 self.EST = timezone(-timedelta(hours=5), 'EST')
239 self.DT = datetime(2010, 1, 1)
240
241 def test_str(self):
242 for tz in [self.ACDT, self.EST, timezone.utc,
243 timezone.min, timezone.max]:
244 self.assertEqual(str(tz), tz.tzname(None))
245
246 def test_repr(self):
247 datetime = datetime_module
248 for tz in [self.ACDT, self.EST, timezone.utc,
249 timezone.min, timezone.max]:
250 # test round-trip
251 tzrep = repr(tz)
252 self.assertEqual(tz, eval(tzrep))
253
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000254 def test_class_members(self):
255 limit = timedelta(hours=23, minutes=59)
256 self.assertEqual(timezone.utc.utcoffset(None), ZERO)
257 self.assertEqual(timezone.min.utcoffset(None), -limit)
258 self.assertEqual(timezone.max.utcoffset(None), limit)
259
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000260 def test_constructor(self):
Alexander Belopolsky1bcbaab2010-10-14 17:03:51 +0000261 self.assertIs(timezone.utc, timezone(timedelta(0)))
262 self.assertIsNot(timezone.utc, timezone(timedelta(0), 'UTC'))
263 self.assertEqual(timezone.utc, timezone(timedelta(0), 'UTC'))
Alexander Belopolsky018d3532017-07-31 10:26:50 -0400264 for subminute in [timedelta(microseconds=1), timedelta(seconds=1)]:
265 tz = timezone(subminute)
266 self.assertNotEqual(tz.utcoffset(None) % timedelta(minutes=1), 0)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000267 # invalid offsets
Alexander Belopolsky018d3532017-07-31 10:26:50 -0400268 for invalid in [timedelta(1, 1), timedelta(1)]:
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000269 self.assertRaises(ValueError, timezone, invalid)
270 self.assertRaises(ValueError, timezone, -invalid)
271
272 with self.assertRaises(TypeError): timezone(None)
273 with self.assertRaises(TypeError): timezone(42)
274 with self.assertRaises(TypeError): timezone(ZERO, None)
275 with self.assertRaises(TypeError): timezone(ZERO, 42)
276 with self.assertRaises(TypeError): timezone(ZERO, 'ABC', 'extra')
277
278 def test_inheritance(self):
279 self.assertIsInstance(timezone.utc, tzinfo)
280 self.assertIsInstance(self.EST, tzinfo)
281
282 def test_utcoffset(self):
283 dummy = self.DT
284 for h in [0, 1.5, 12]:
285 offset = h * HOUR
286 self.assertEqual(offset, timezone(offset).utcoffset(dummy))
287 self.assertEqual(-offset, timezone(-offset).utcoffset(dummy))
288
289 with self.assertRaises(TypeError): self.EST.utcoffset('')
290 with self.assertRaises(TypeError): self.EST.utcoffset(5)
291
292
293 def test_dst(self):
Raymond Hettinger7beae8a2011-01-06 05:34:17 +0000294 self.assertIsNone(timezone.utc.dst(self.DT))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000295
296 with self.assertRaises(TypeError): self.EST.dst('')
297 with self.assertRaises(TypeError): self.EST.dst(5)
298
299 def test_tzname(self):
Alexander Belopolsky7827a5b2015-09-06 13:07:21 -0400300 self.assertEqual('UTC', timezone.utc.tzname(None))
301 self.assertEqual('UTC', timezone(ZERO).tzname(None))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000302 self.assertEqual('UTC-05:00', timezone(-5 * HOUR).tzname(None))
303 self.assertEqual('UTC+09:30', timezone(9.5 * HOUR).tzname(None))
304 self.assertEqual('UTC-00:01', timezone(timedelta(minutes=-1)).tzname(None))
305 self.assertEqual('XYZ', timezone(-5 * HOUR, 'XYZ').tzname(None))
306
Alexander Belopolsky018d3532017-07-31 10:26:50 -0400307 # Sub-minute offsets:
308 self.assertEqual('UTC+01:06:40', timezone(timedelta(0, 4000)).tzname(None))
309 self.assertEqual('UTC-01:06:40',
310 timezone(-timedelta(0, 4000)).tzname(None))
311 self.assertEqual('UTC+01:06:40.000001',
312 timezone(timedelta(0, 4000, 1)).tzname(None))
313 self.assertEqual('UTC-01:06:40.000001',
314 timezone(-timedelta(0, 4000, 1)).tzname(None))
315
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000316 with self.assertRaises(TypeError): self.EST.tzname('')
317 with self.assertRaises(TypeError): self.EST.tzname(5)
318
319 def test_fromutc(self):
320 with self.assertRaises(ValueError):
321 timezone.utc.fromutc(self.DT)
322 with self.assertRaises(TypeError):
323 timezone.utc.fromutc('not datetime')
324 for tz in [self.EST, self.ACDT, Eastern]:
325 utctime = self.DT.replace(tzinfo=tz)
326 local = tz.fromutc(utctime)
327 self.assertEqual(local - utctime, tz.utcoffset(local))
328 self.assertEqual(local,
329 self.DT.replace(tzinfo=timezone.utc))
330
331 def test_comparison(self):
332 self.assertNotEqual(timezone(ZERO), timezone(HOUR))
333 self.assertEqual(timezone(HOUR), timezone(HOUR))
334 self.assertEqual(timezone(-5 * HOUR), timezone(-5 * HOUR, 'EST'))
335 with self.assertRaises(TypeError): timezone(ZERO) < timezone(ZERO)
336 self.assertIn(timezone(ZERO), {timezone(ZERO)})
Georg Brandl0085a242012-09-22 09:23:12 +0200337 self.assertTrue(timezone(ZERO) != None)
338 self.assertFalse(timezone(ZERO) == None)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000339
340 def test_aware_datetime(self):
341 # test that timezone instances can be used by datetime
342 t = datetime(1, 1, 1)
343 for tz in [timezone.min, timezone.max, timezone.utc]:
344 self.assertEqual(tz.tzname(t),
345 t.replace(tzinfo=tz).tzname())
346 self.assertEqual(tz.utcoffset(t),
347 t.replace(tzinfo=tz).utcoffset())
348 self.assertEqual(tz.dst(t),
349 t.replace(tzinfo=tz).dst())
350
Serhiy Storchakae28209f2015-11-16 11:12:58 +0200351 def test_pickle(self):
352 for tz in self.ACDT, self.EST, timezone.min, timezone.max:
353 for pickler, unpickler, proto in pickle_choices:
354 tz_copy = unpickler.loads(pickler.dumps(tz, proto))
355 self.assertEqual(tz_copy, tz)
356 tz = timezone.utc
357 for pickler, unpickler, proto in pickle_choices:
358 tz_copy = unpickler.loads(pickler.dumps(tz, proto))
359 self.assertIs(tz_copy, tz)
360
361 def test_copy(self):
362 for tz in self.ACDT, self.EST, timezone.min, timezone.max:
363 tz_copy = copy.copy(tz)
364 self.assertEqual(tz_copy, tz)
365 tz = timezone.utc
366 tz_copy = copy.copy(tz)
367 self.assertIs(tz_copy, tz)
368
369 def test_deepcopy(self):
370 for tz in self.ACDT, self.EST, timezone.min, timezone.max:
371 tz_copy = copy.deepcopy(tz)
372 self.assertEqual(tz_copy, tz)
373 tz = timezone.utc
374 tz_copy = copy.deepcopy(tz)
375 self.assertIs(tz_copy, tz)
376
377
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000378#############################################################################
Ezio Melotti85a86292013-08-17 16:57:41 +0300379# Base class for testing a particular aspect of timedelta, time, date and
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000380# datetime comparisons.
381
382class HarmlessMixedComparison:
383 # Test that __eq__ and __ne__ don't complain for mixed-type comparisons.
384
385 # Subclasses must define 'theclass', and theclass(1, 1, 1) must be a
386 # legit constructor.
387
388 def test_harmless_mixed_comparison(self):
389 me = self.theclass(1, 1, 1)
390
391 self.assertFalse(me == ())
392 self.assertTrue(me != ())
393 self.assertFalse(() == me)
394 self.assertTrue(() != me)
395
396 self.assertIn(me, [1, 20, [], me])
397 self.assertIn([], [me, 1, 20, []])
398
399 def test_harmful_mixed_comparison(self):
400 me = self.theclass(1, 1, 1)
401
402 self.assertRaises(TypeError, lambda: me < ())
403 self.assertRaises(TypeError, lambda: me <= ())
404 self.assertRaises(TypeError, lambda: me > ())
405 self.assertRaises(TypeError, lambda: me >= ())
406
407 self.assertRaises(TypeError, lambda: () < me)
408 self.assertRaises(TypeError, lambda: () <= me)
409 self.assertRaises(TypeError, lambda: () > me)
410 self.assertRaises(TypeError, lambda: () >= me)
411
412#############################################################################
413# timedelta tests
414
415class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
416
417 theclass = timedelta
418
419 def test_constructor(self):
420 eq = self.assertEqual
421 td = timedelta
422
423 # Check keyword args to constructor
424 eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0,
425 milliseconds=0, microseconds=0))
426 eq(td(1), td(days=1))
427 eq(td(0, 1), td(seconds=1))
428 eq(td(0, 0, 1), td(microseconds=1))
429 eq(td(weeks=1), td(days=7))
430 eq(td(days=1), td(hours=24))
431 eq(td(hours=1), td(minutes=60))
432 eq(td(minutes=1), td(seconds=60))
433 eq(td(seconds=1), td(milliseconds=1000))
434 eq(td(milliseconds=1), td(microseconds=1000))
435
436 # Check float args to constructor
437 eq(td(weeks=1.0/7), td(days=1))
438 eq(td(days=1.0/24), td(hours=1))
439 eq(td(hours=1.0/60), td(minutes=1))
440 eq(td(minutes=1.0/60), td(seconds=1))
441 eq(td(seconds=0.001), td(milliseconds=1))
442 eq(td(milliseconds=0.001), td(microseconds=1))
443
444 def test_computations(self):
445 eq = self.assertEqual
446 td = timedelta
447
448 a = td(7) # One week
449 b = td(0, 60) # One minute
450 c = td(0, 0, 1000) # One millisecond
451 eq(a+b+c, td(7, 60, 1000))
452 eq(a-b, td(6, 24*3600 - 60))
453 eq(b.__rsub__(a), td(6, 24*3600 - 60))
454 eq(-a, td(-7))
455 eq(+a, td(7))
456 eq(-b, td(-1, 24*3600 - 60))
457 eq(-c, td(-1, 24*3600 - 1, 999000))
458 eq(abs(a), a)
459 eq(abs(-a), a)
460 eq(td(6, 24*3600), a)
461 eq(td(0, 0, 60*1000000), b)
462 eq(a*10, td(70))
463 eq(a*10, 10*a)
464 eq(a*10, 10*a)
465 eq(b*10, td(0, 600))
466 eq(10*b, td(0, 600))
467 eq(b*10, td(0, 600))
468 eq(c*10, td(0, 0, 10000))
469 eq(10*c, td(0, 0, 10000))
470 eq(c*10, td(0, 0, 10000))
471 eq(a*-1, -a)
472 eq(b*-2, -b-b)
473 eq(c*-2, -c+-c)
474 eq(b*(60*24), (b*60)*24)
475 eq(b*(60*24), (60*b)*24)
476 eq(c*1000, td(0, 1))
477 eq(1000*c, td(0, 1))
478 eq(a//7, td(1))
479 eq(b//10, td(0, 6))
480 eq(c//1000, td(0, 0, 1))
481 eq(a//10, td(0, 7*24*360))
482 eq(a//3600000, td(0, 0, 7*24*1000))
483 eq(a/0.5, td(14))
484 eq(b/0.5, td(0, 120))
485 eq(a/7, td(1))
486 eq(b/10, td(0, 6))
487 eq(c/1000, td(0, 0, 1))
488 eq(a/10, td(0, 7*24*360))
489 eq(a/3600000, td(0, 0, 7*24*1000))
490
491 # Multiplication by float
492 us = td(microseconds=1)
493 eq((3*us) * 0.5, 2*us)
494 eq((5*us) * 0.5, 2*us)
495 eq(0.5 * (3*us), 2*us)
496 eq(0.5 * (5*us), 2*us)
497 eq((-3*us) * 0.5, -2*us)
498 eq((-5*us) * 0.5, -2*us)
499
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -0500500 # Issue #23521
501 eq(td(seconds=1) * 0.123456, td(microseconds=123456))
502 eq(td(seconds=1) * 0.6112295, td(microseconds=611229))
503
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000504 # Division by int and float
505 eq((3*us) / 2, 2*us)
506 eq((5*us) / 2, 2*us)
507 eq((-3*us) / 2.0, -2*us)
508 eq((-5*us) / 2.0, -2*us)
509 eq((3*us) / -2, -2*us)
510 eq((5*us) / -2, -2*us)
511 eq((3*us) / -2.0, -2*us)
512 eq((5*us) / -2.0, -2*us)
513 for i in range(-10, 10):
514 eq((i*us/3)//us, round(i/3))
515 for i in range(-10, 10):
516 eq((i*us/-3)//us, round(i/-3))
517
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -0500518 # Issue #23521
519 eq(td(seconds=1) / (1 / 0.6112295), td(microseconds=611229))
520
Alexander Belopolskyb6f5ec72011-04-05 20:07:38 -0400521 # Issue #11576
522 eq(td(999999999, 86399, 999999) - td(999999999, 86399, 999998),
523 td(0, 0, 1))
524 eq(td(999999999, 1, 1) - td(999999999, 1, 0),
525 td(0, 0, 1))
526
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000527 def test_disallowed_computations(self):
528 a = timedelta(42)
529
530 # Add/sub ints or floats should be illegal
531 for i in 1, 1.0:
532 self.assertRaises(TypeError, lambda: a+i)
533 self.assertRaises(TypeError, lambda: a-i)
534 self.assertRaises(TypeError, lambda: i+a)
535 self.assertRaises(TypeError, lambda: i-a)
536
537 # Division of int by timedelta doesn't make sense.
538 # Division by zero doesn't make sense.
539 zero = 0
540 self.assertRaises(TypeError, lambda: zero // a)
541 self.assertRaises(ZeroDivisionError, lambda: a // zero)
542 self.assertRaises(ZeroDivisionError, lambda: a / zero)
543 self.assertRaises(ZeroDivisionError, lambda: a / 0.0)
544 self.assertRaises(TypeError, lambda: a / '')
545
Eric Smith3ab08ca2010-12-04 15:17:38 +0000546 @support.requires_IEEE_754
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000547 def test_disallowed_special(self):
548 a = timedelta(42)
549 self.assertRaises(ValueError, a.__mul__, NAN)
550 self.assertRaises(ValueError, a.__truediv__, NAN)
551
552 def test_basic_attributes(self):
553 days, seconds, us = 1, 7, 31
554 td = timedelta(days, seconds, us)
555 self.assertEqual(td.days, days)
556 self.assertEqual(td.seconds, seconds)
557 self.assertEqual(td.microseconds, us)
558
559 def test_total_seconds(self):
560 td = timedelta(days=365)
561 self.assertEqual(td.total_seconds(), 31536000.0)
562 for total_seconds in [123456.789012, -123456.789012, 0.123456, 0, 1e6]:
563 td = timedelta(seconds=total_seconds)
564 self.assertEqual(td.total_seconds(), total_seconds)
565 # Issue8644: Test that td.total_seconds() has the same
566 # accuracy as td / timedelta(seconds=1).
567 for ms in [-1, -2, -123]:
568 td = timedelta(microseconds=ms)
569 self.assertEqual(td.total_seconds(), td / timedelta(seconds=1))
570
571 def test_carries(self):
572 t1 = timedelta(days=100,
573 weeks=-7,
574 hours=-24*(100-49),
575 minutes=-3,
576 seconds=12,
577 microseconds=(3*60 - 12) * 1e6 + 1)
578 t2 = timedelta(microseconds=1)
579 self.assertEqual(t1, t2)
580
581 def test_hash_equality(self):
582 t1 = timedelta(days=100,
583 weeks=-7,
584 hours=-24*(100-49),
585 minutes=-3,
586 seconds=12,
587 microseconds=(3*60 - 12) * 1000000)
588 t2 = timedelta()
589 self.assertEqual(hash(t1), hash(t2))
590
591 t1 += timedelta(weeks=7)
592 t2 += timedelta(days=7*7)
593 self.assertEqual(t1, t2)
594 self.assertEqual(hash(t1), hash(t2))
595
596 d = {t1: 1}
597 d[t2] = 2
598 self.assertEqual(len(d), 1)
599 self.assertEqual(d[t1], 2)
600
601 def test_pickling(self):
602 args = 12, 34, 56
603 orig = timedelta(*args)
604 for pickler, unpickler, proto in pickle_choices:
605 green = pickler.dumps(orig, proto)
606 derived = unpickler.loads(green)
607 self.assertEqual(orig, derived)
608
609 def test_compare(self):
610 t1 = timedelta(2, 3, 4)
611 t2 = timedelta(2, 3, 4)
612 self.assertEqual(t1, t2)
613 self.assertTrue(t1 <= t2)
614 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200615 self.assertFalse(t1 != t2)
616 self.assertFalse(t1 < t2)
617 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000618
619 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
620 t2 = timedelta(*args) # this is larger than t1
621 self.assertTrue(t1 < t2)
622 self.assertTrue(t2 > t1)
623 self.assertTrue(t1 <= t2)
624 self.assertTrue(t2 >= t1)
625 self.assertTrue(t1 != t2)
626 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200627 self.assertFalse(t1 == t2)
628 self.assertFalse(t2 == t1)
629 self.assertFalse(t1 > t2)
630 self.assertFalse(t2 < t1)
631 self.assertFalse(t1 >= t2)
632 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000633
634 for badarg in OTHERSTUFF:
635 self.assertEqual(t1 == badarg, False)
636 self.assertEqual(t1 != badarg, True)
637 self.assertEqual(badarg == t1, False)
638 self.assertEqual(badarg != t1, True)
639
640 self.assertRaises(TypeError, lambda: t1 <= badarg)
641 self.assertRaises(TypeError, lambda: t1 < badarg)
642 self.assertRaises(TypeError, lambda: t1 > badarg)
643 self.assertRaises(TypeError, lambda: t1 >= badarg)
644 self.assertRaises(TypeError, lambda: badarg <= t1)
645 self.assertRaises(TypeError, lambda: badarg < t1)
646 self.assertRaises(TypeError, lambda: badarg > t1)
647 self.assertRaises(TypeError, lambda: badarg >= t1)
648
649 def test_str(self):
650 td = timedelta
651 eq = self.assertEqual
652
653 eq(str(td(1)), "1 day, 0:00:00")
654 eq(str(td(-1)), "-1 day, 0:00:00")
655 eq(str(td(2)), "2 days, 0:00:00")
656 eq(str(td(-2)), "-2 days, 0:00:00")
657
658 eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
659 eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
660 eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
661 "-210 days, 23:12:34")
662
663 eq(str(td(milliseconds=1)), "0:00:00.001000")
664 eq(str(td(microseconds=3)), "0:00:00.000003")
665
666 eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
667 microseconds=999999)),
668 "999999999 days, 23:59:59.999999")
669
670 def test_repr(self):
671 name = 'datetime.' + self.theclass.__name__
672 self.assertEqual(repr(self.theclass(1)),
Utkarsh Upadhyaycc5a65c2017-07-25 23:51:33 +0200673 "%s(days=1)" % name)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000674 self.assertEqual(repr(self.theclass(10, 2)),
Utkarsh Upadhyaycc5a65c2017-07-25 23:51:33 +0200675 "%s(days=10, seconds=2)" % name)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000676 self.assertEqual(repr(self.theclass(-10, 2, 400000)),
Utkarsh Upadhyaycc5a65c2017-07-25 23:51:33 +0200677 "%s(days=-10, seconds=2, microseconds=400000)" % name)
678 self.assertEqual(repr(self.theclass(seconds=60)),
679 "%s(seconds=60)" % name)
680 self.assertEqual(repr(self.theclass()),
681 "%s(0)" % name)
682 self.assertEqual(repr(self.theclass(microseconds=100)),
683 "%s(microseconds=100)" % name)
684 self.assertEqual(repr(self.theclass(days=1, microseconds=100)),
685 "%s(days=1, microseconds=100)" % name)
686 self.assertEqual(repr(self.theclass(seconds=1, microseconds=100)),
687 "%s(seconds=1, microseconds=100)" % name)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000688
689 def test_roundtrip(self):
690 for td in (timedelta(days=999999999, hours=23, minutes=59,
691 seconds=59, microseconds=999999),
692 timedelta(days=-999999999),
693 timedelta(days=-999999999, seconds=1),
694 timedelta(days=1, seconds=2, microseconds=3)):
695
696 # Verify td -> string -> td identity.
697 s = repr(td)
698 self.assertTrue(s.startswith('datetime.'))
699 s = s[9:]
700 td2 = eval(s)
701 self.assertEqual(td, td2)
702
703 # Verify identity via reconstructing from pieces.
704 td2 = timedelta(td.days, td.seconds, td.microseconds)
705 self.assertEqual(td, td2)
706
707 def test_resolution_info(self):
708 self.assertIsInstance(timedelta.min, timedelta)
709 self.assertIsInstance(timedelta.max, timedelta)
710 self.assertIsInstance(timedelta.resolution, timedelta)
711 self.assertTrue(timedelta.max > timedelta.min)
712 self.assertEqual(timedelta.min, timedelta(-999999999))
713 self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
714 self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
715
716 def test_overflow(self):
717 tiny = timedelta.resolution
718
719 td = timedelta.min + tiny
720 td -= tiny # no problem
721 self.assertRaises(OverflowError, td.__sub__, tiny)
722 self.assertRaises(OverflowError, td.__add__, -tiny)
723
724 td = timedelta.max - tiny
725 td += tiny # no problem
726 self.assertRaises(OverflowError, td.__add__, tiny)
727 self.assertRaises(OverflowError, td.__sub__, -tiny)
728
729 self.assertRaises(OverflowError, lambda: -timedelta.max)
730
731 day = timedelta(1)
732 self.assertRaises(OverflowError, day.__mul__, 10**9)
733 self.assertRaises(OverflowError, day.__mul__, 1e9)
734 self.assertRaises(OverflowError, day.__truediv__, 1e-20)
735 self.assertRaises(OverflowError, day.__truediv__, 1e-10)
736 self.assertRaises(OverflowError, day.__truediv__, 9e-10)
737
Eric Smith3ab08ca2010-12-04 15:17:38 +0000738 @support.requires_IEEE_754
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000739 def _test_overflow_special(self):
740 day = timedelta(1)
741 self.assertRaises(OverflowError, day.__mul__, INF)
742 self.assertRaises(OverflowError, day.__mul__, -INF)
743
744 def test_microsecond_rounding(self):
745 td = timedelta
746 eq = self.assertEqual
747
748 # Single-field rounding.
749 eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
750 eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
Victor Stinner69cc4872015-09-08 23:58:54 +0200751 eq(td(milliseconds=0.5/1000), td(microseconds=0))
752 eq(td(milliseconds=-0.5/1000), td(microseconds=-0))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000753 eq(td(milliseconds=0.6/1000), td(microseconds=1))
754 eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
Victor Stinner69cc4872015-09-08 23:58:54 +0200755 eq(td(milliseconds=1.5/1000), td(microseconds=2))
756 eq(td(milliseconds=-1.5/1000), td(microseconds=-2))
757 eq(td(seconds=0.5/10**6), td(microseconds=0))
758 eq(td(seconds=-0.5/10**6), td(microseconds=-0))
759 eq(td(seconds=1/2**7), td(microseconds=7812))
760 eq(td(seconds=-1/2**7), td(microseconds=-7812))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000761
762 # Rounding due to contributions from more than one field.
763 us_per_hour = 3600e6
764 us_per_day = us_per_hour * 24
765 eq(td(days=.4/us_per_day), td(0))
766 eq(td(hours=.2/us_per_hour), td(0))
Victor Stinnercd5d7652015-09-09 01:09:21 +0200767 eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000768
769 eq(td(days=-.4/us_per_day), td(0))
770 eq(td(hours=-.2/us_per_hour), td(0))
771 eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
772
Victor Stinner69cc4872015-09-08 23:58:54 +0200773 # Test for a patch in Issue 8860
774 eq(td(microseconds=0.5), 0.5*td(microseconds=1.0))
775 eq(td(microseconds=0.5)//td.resolution, 0.5*td.resolution//td.resolution)
776
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000777 def test_massive_normalization(self):
778 td = timedelta(microseconds=-1)
779 self.assertEqual((td.days, td.seconds, td.microseconds),
780 (-1, 24*3600-1, 999999))
781
782 def test_bool(self):
783 self.assertTrue(timedelta(1))
784 self.assertTrue(timedelta(0, 1))
785 self.assertTrue(timedelta(0, 0, 1))
786 self.assertTrue(timedelta(microseconds=1))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200787 self.assertFalse(timedelta(0))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000788
789 def test_subclass_timedelta(self):
790
791 class T(timedelta):
792 @staticmethod
793 def from_td(td):
794 return T(td.days, td.seconds, td.microseconds)
795
796 def as_hours(self):
797 sum = (self.days * 24 +
798 self.seconds / 3600.0 +
799 self.microseconds / 3600e6)
800 return round(sum)
801
802 t1 = T(days=1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200803 self.assertIs(type(t1), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000804 self.assertEqual(t1.as_hours(), 24)
805
806 t2 = T(days=-1, seconds=-3600)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200807 self.assertIs(type(t2), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000808 self.assertEqual(t2.as_hours(), -25)
809
810 t3 = t1 + t2
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200811 self.assertIs(type(t3), timedelta)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000812 t4 = T.from_td(t3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200813 self.assertIs(type(t4), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000814 self.assertEqual(t3.days, t4.days)
815 self.assertEqual(t3.seconds, t4.seconds)
816 self.assertEqual(t3.microseconds, t4.microseconds)
817 self.assertEqual(str(t3), str(t4))
818 self.assertEqual(t4.as_hours(), -1)
819
820 def test_division(self):
821 t = timedelta(hours=1, minutes=24, seconds=19)
822 second = timedelta(seconds=1)
823 self.assertEqual(t / second, 5059.0)
824 self.assertEqual(t // second, 5059)
825
826 t = timedelta(minutes=2, seconds=30)
827 minute = timedelta(minutes=1)
828 self.assertEqual(t / minute, 2.5)
829 self.assertEqual(t // minute, 2)
830
831 zerotd = timedelta(0)
832 self.assertRaises(ZeroDivisionError, truediv, t, zerotd)
833 self.assertRaises(ZeroDivisionError, floordiv, t, zerotd)
834
835 # self.assertRaises(TypeError, truediv, t, 2)
836 # note: floor division of a timedelta by an integer *is*
837 # currently permitted.
838
839 def test_remainder(self):
840 t = timedelta(minutes=2, seconds=30)
841 minute = timedelta(minutes=1)
842 r = t % minute
843 self.assertEqual(r, timedelta(seconds=30))
844
845 t = timedelta(minutes=-2, seconds=30)
846 r = t % minute
847 self.assertEqual(r, timedelta(seconds=30))
848
849 zerotd = timedelta(0)
850 self.assertRaises(ZeroDivisionError, mod, t, zerotd)
851
852 self.assertRaises(TypeError, mod, t, 10)
853
854 def test_divmod(self):
855 t = timedelta(minutes=2, seconds=30)
856 minute = timedelta(minutes=1)
857 q, r = divmod(t, minute)
858 self.assertEqual(q, 2)
859 self.assertEqual(r, timedelta(seconds=30))
860
861 t = timedelta(minutes=-2, seconds=30)
862 q, r = divmod(t, minute)
863 self.assertEqual(q, -2)
864 self.assertEqual(r, timedelta(seconds=30))
865
866 zerotd = timedelta(0)
867 self.assertRaises(ZeroDivisionError, divmod, t, zerotd)
868
869 self.assertRaises(TypeError, divmod, t, 10)
870
Oren Milman865e4b42017-09-19 15:58:11 +0300871 def test_issue31293(self):
872 # The interpreter shouldn't crash in case a timedelta is divided or
873 # multiplied by a float with a bad as_integer_ratio() method.
874 def get_bad_float(bad_ratio):
875 class BadFloat(float):
876 def as_integer_ratio(self):
877 return bad_ratio
878 return BadFloat()
879
880 with self.assertRaises(TypeError):
881 timedelta() / get_bad_float(1 << 1000)
882 with self.assertRaises(TypeError):
883 timedelta() * get_bad_float(1 << 1000)
884
885 for bad_ratio in [(), (42, ), (1, 2, 3)]:
886 with self.assertRaises(ValueError):
887 timedelta() / get_bad_float(bad_ratio)
888 with self.assertRaises(ValueError):
889 timedelta() * get_bad_float(bad_ratio)
890
Serhiy Storchaka4ffd4652017-10-23 17:12:28 +0300891 def test_issue31752(self):
892 # The interpreter shouldn't crash because divmod() returns negative
893 # remainder.
894 class BadInt(int):
895 def __mul__(self, other):
896 return Prod()
897
898 class Prod:
899 def __radd__(self, other):
900 return Sum()
901
902 class Sum(int):
903 def __divmod__(self, other):
904 # negative remainder
905 return (0, -1)
906
907 timedelta(microseconds=BadInt(1))
908 timedelta(hours=BadInt(1))
909 timedelta(weeks=BadInt(1))
910
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000911
912#############################################################################
913# date tests
914
915class TestDateOnly(unittest.TestCase):
916 # Tests here won't pass if also run on datetime objects, so don't
917 # subclass this to test datetimes too.
918
919 def test_delta_non_days_ignored(self):
920 dt = date(2000, 1, 2)
921 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
922 microseconds=5)
923 days = timedelta(delta.days)
924 self.assertEqual(days, timedelta(1))
925
926 dt2 = dt + delta
927 self.assertEqual(dt2, dt + days)
928
929 dt2 = delta + dt
930 self.assertEqual(dt2, dt + days)
931
932 dt2 = dt - delta
933 self.assertEqual(dt2, dt - days)
934
935 delta = -delta
936 days = timedelta(delta.days)
937 self.assertEqual(days, timedelta(-2))
938
939 dt2 = dt + delta
940 self.assertEqual(dt2, dt + days)
941
942 dt2 = delta + dt
943 self.assertEqual(dt2, dt + days)
944
945 dt2 = dt - delta
946 self.assertEqual(dt2, dt - days)
947
948class SubclassDate(date):
949 sub_var = 1
950
951class TestDate(HarmlessMixedComparison, unittest.TestCase):
952 # Tests here should pass for both dates and datetimes, except for a
953 # few tests that TestDateTime overrides.
954
955 theclass = date
956
957 def test_basic_attributes(self):
958 dt = self.theclass(2002, 3, 1)
959 self.assertEqual(dt.year, 2002)
960 self.assertEqual(dt.month, 3)
961 self.assertEqual(dt.day, 1)
962
963 def test_roundtrip(self):
964 for dt in (self.theclass(1, 2, 3),
965 self.theclass.today()):
966 # Verify dt -> string -> date identity.
967 s = repr(dt)
968 self.assertTrue(s.startswith('datetime.'))
969 s = s[9:]
970 dt2 = eval(s)
971 self.assertEqual(dt, dt2)
972
973 # Verify identity via reconstructing from pieces.
974 dt2 = self.theclass(dt.year, dt.month, dt.day)
975 self.assertEqual(dt, dt2)
976
977 def test_ordinal_conversions(self):
978 # Check some fixed values.
979 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
980 (1, 12, 31, 365),
981 (2, 1, 1, 366),
982 # first example from "Calendrical Calculations"
983 (1945, 11, 12, 710347)]:
984 d = self.theclass(y, m, d)
985 self.assertEqual(n, d.toordinal())
986 fromord = self.theclass.fromordinal(n)
987 self.assertEqual(d, fromord)
988 if hasattr(fromord, "hour"):
989 # if we're checking something fancier than a date, verify
990 # the extra fields have been zeroed out
991 self.assertEqual(fromord.hour, 0)
992 self.assertEqual(fromord.minute, 0)
993 self.assertEqual(fromord.second, 0)
994 self.assertEqual(fromord.microsecond, 0)
995
996 # Check first and last days of year spottily across the whole
997 # range of years supported.
998 for year in range(MINYEAR, MAXYEAR+1, 7):
999 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
1000 d = self.theclass(year, 1, 1)
1001 n = d.toordinal()
1002 d2 = self.theclass.fromordinal(n)
1003 self.assertEqual(d, d2)
1004 # Verify that moving back a day gets to the end of year-1.
1005 if year > 1:
1006 d = self.theclass.fromordinal(n-1)
1007 d2 = self.theclass(year-1, 12, 31)
1008 self.assertEqual(d, d2)
1009 self.assertEqual(d2.toordinal(), n-1)
1010
1011 # Test every day in a leap-year and a non-leap year.
1012 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
1013 for year, isleap in (2000, True), (2002, False):
1014 n = self.theclass(year, 1, 1).toordinal()
1015 for month, maxday in zip(range(1, 13), dim):
1016 if month == 2 and isleap:
1017 maxday += 1
1018 for day in range(1, maxday+1):
1019 d = self.theclass(year, month, day)
1020 self.assertEqual(d.toordinal(), n)
1021 self.assertEqual(d, self.theclass.fromordinal(n))
1022 n += 1
1023
1024 def test_extreme_ordinals(self):
1025 a = self.theclass.min
1026 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
1027 aord = a.toordinal()
1028 b = a.fromordinal(aord)
1029 self.assertEqual(a, b)
1030
1031 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
1032
1033 b = a + timedelta(days=1)
1034 self.assertEqual(b.toordinal(), aord + 1)
1035 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
1036
1037 a = self.theclass.max
1038 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
1039 aord = a.toordinal()
1040 b = a.fromordinal(aord)
1041 self.assertEqual(a, b)
1042
1043 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
1044
1045 b = a - timedelta(days=1)
1046 self.assertEqual(b.toordinal(), aord - 1)
1047 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
1048
1049 def test_bad_constructor_arguments(self):
1050 # bad years
1051 self.theclass(MINYEAR, 1, 1) # no exception
1052 self.theclass(MAXYEAR, 1, 1) # no exception
1053 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1054 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1055 # bad months
1056 self.theclass(2000, 1, 1) # no exception
1057 self.theclass(2000, 12, 1) # no exception
1058 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1059 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1060 # bad days
1061 self.theclass(2000, 2, 29) # no exception
1062 self.theclass(2004, 2, 29) # no exception
1063 self.theclass(2400, 2, 29) # no exception
1064 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1065 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1066 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1067 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1068 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1069 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1070
1071 def test_hash_equality(self):
1072 d = self.theclass(2000, 12, 31)
1073 # same thing
1074 e = self.theclass(2000, 12, 31)
1075 self.assertEqual(d, e)
1076 self.assertEqual(hash(d), hash(e))
1077
1078 dic = {d: 1}
1079 dic[e] = 2
1080 self.assertEqual(len(dic), 1)
1081 self.assertEqual(dic[d], 2)
1082 self.assertEqual(dic[e], 2)
1083
1084 d = self.theclass(2001, 1, 1)
1085 # same thing
1086 e = self.theclass(2001, 1, 1)
1087 self.assertEqual(d, e)
1088 self.assertEqual(hash(d), hash(e))
1089
1090 dic = {d: 1}
1091 dic[e] = 2
1092 self.assertEqual(len(dic), 1)
1093 self.assertEqual(dic[d], 2)
1094 self.assertEqual(dic[e], 2)
1095
1096 def test_computations(self):
1097 a = self.theclass(2002, 1, 31)
1098 b = self.theclass(1956, 1, 31)
1099 c = self.theclass(2001,2,1)
1100
1101 diff = a-b
1102 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1103 self.assertEqual(diff.seconds, 0)
1104 self.assertEqual(diff.microseconds, 0)
1105
1106 day = timedelta(1)
1107 week = timedelta(7)
1108 a = self.theclass(2002, 3, 2)
1109 self.assertEqual(a + day, self.theclass(2002, 3, 3))
1110 self.assertEqual(day + a, self.theclass(2002, 3, 3))
1111 self.assertEqual(a - day, self.theclass(2002, 3, 1))
1112 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
1113 self.assertEqual(a + week, self.theclass(2002, 3, 9))
1114 self.assertEqual(a - week, self.theclass(2002, 2, 23))
1115 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
1116 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
1117 self.assertEqual((a + week) - a, week)
1118 self.assertEqual((a + day) - a, day)
1119 self.assertEqual((a - week) - a, -week)
1120 self.assertEqual((a - day) - a, -day)
1121 self.assertEqual(a - (a + week), -week)
1122 self.assertEqual(a - (a + day), -day)
1123 self.assertEqual(a - (a - week), week)
1124 self.assertEqual(a - (a - day), day)
1125 self.assertEqual(c - (c - day), day)
1126
1127 # Add/sub ints or floats should be illegal
1128 for i in 1, 1.0:
1129 self.assertRaises(TypeError, lambda: a+i)
1130 self.assertRaises(TypeError, lambda: a-i)
1131 self.assertRaises(TypeError, lambda: i+a)
1132 self.assertRaises(TypeError, lambda: i-a)
1133
1134 # delta - date is senseless.
1135 self.assertRaises(TypeError, lambda: day - a)
1136 # mixing date and (delta or date) via * or // is senseless
1137 self.assertRaises(TypeError, lambda: day * a)
1138 self.assertRaises(TypeError, lambda: a * day)
1139 self.assertRaises(TypeError, lambda: day // a)
1140 self.assertRaises(TypeError, lambda: a // day)
1141 self.assertRaises(TypeError, lambda: a * a)
1142 self.assertRaises(TypeError, lambda: a // a)
1143 # date + date is senseless
1144 self.assertRaises(TypeError, lambda: a + a)
1145
1146 def test_overflow(self):
1147 tiny = self.theclass.resolution
1148
1149 for delta in [tiny, timedelta(1), timedelta(2)]:
1150 dt = self.theclass.min + delta
1151 dt -= delta # no problem
1152 self.assertRaises(OverflowError, dt.__sub__, delta)
1153 self.assertRaises(OverflowError, dt.__add__, -delta)
1154
1155 dt = self.theclass.max - delta
1156 dt += delta # no problem
1157 self.assertRaises(OverflowError, dt.__add__, delta)
1158 self.assertRaises(OverflowError, dt.__sub__, -delta)
1159
1160 def test_fromtimestamp(self):
1161 import time
1162
1163 # Try an arbitrary fixed value.
1164 year, month, day = 1999, 9, 19
1165 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
1166 d = self.theclass.fromtimestamp(ts)
1167 self.assertEqual(d.year, year)
1168 self.assertEqual(d.month, month)
1169 self.assertEqual(d.day, day)
1170
1171 def test_insane_fromtimestamp(self):
1172 # It's possible that some platform maps time_t to double,
1173 # and that this test will fail there. This test should
1174 # exempt such platforms (provided they return reasonable
1175 # results!).
1176 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001177 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001178 insane)
1179
1180 def test_today(self):
1181 import time
1182
1183 # We claim that today() is like fromtimestamp(time.time()), so
1184 # prove it.
1185 for dummy in range(3):
1186 today = self.theclass.today()
1187 ts = time.time()
1188 todayagain = self.theclass.fromtimestamp(ts)
1189 if today == todayagain:
1190 break
1191 # There are several legit reasons that could fail:
1192 # 1. It recently became midnight, between the today() and the
1193 # time() calls.
1194 # 2. The platform time() has such fine resolution that we'll
1195 # never get the same value twice.
1196 # 3. The platform time() has poor resolution, and we just
1197 # happened to call today() right before a resolution quantum
1198 # boundary.
1199 # 4. The system clock got fiddled between calls.
1200 # In any case, wait a little while and try again.
1201 time.sleep(0.1)
1202
1203 # It worked or it didn't. If it didn't, assume it's reason #2, and
1204 # let the test pass if they're within half a second of each other.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001205 if today != todayagain:
1206 self.assertAlmostEqual(todayagain, today,
1207 delta=timedelta(seconds=0.5))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001208
1209 def test_weekday(self):
1210 for i in range(7):
1211 # March 4, 2002 is a Monday
1212 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
1213 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
1214 # January 2, 1956 is a Monday
1215 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
1216 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
1217
1218 def test_isocalendar(self):
1219 # Check examples from
1220 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1221 for i in range(7):
1222 d = self.theclass(2003, 12, 22+i)
1223 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
1224 d = self.theclass(2003, 12, 29) + timedelta(i)
1225 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
1226 d = self.theclass(2004, 1, 5+i)
1227 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
1228 d = self.theclass(2009, 12, 21+i)
1229 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
1230 d = self.theclass(2009, 12, 28) + timedelta(i)
1231 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
1232 d = self.theclass(2010, 1, 4+i)
1233 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
1234
1235 def test_iso_long_years(self):
1236 # Calculate long ISO years and compare to table from
1237 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1238 ISO_LONG_YEARS_TABLE = """
1239 4 32 60 88
1240 9 37 65 93
1241 15 43 71 99
1242 20 48 76
1243 26 54 82
1244
1245 105 133 161 189
1246 111 139 167 195
1247 116 144 172
1248 122 150 178
1249 128 156 184
1250
1251 201 229 257 285
1252 207 235 263 291
1253 212 240 268 296
1254 218 246 274
1255 224 252 280
1256
1257 303 331 359 387
1258 308 336 364 392
1259 314 342 370 398
1260 320 348 376
1261 325 353 381
1262 """
1263 iso_long_years = sorted(map(int, ISO_LONG_YEARS_TABLE.split()))
1264 L = []
1265 for i in range(400):
1266 d = self.theclass(2000+i, 12, 31)
1267 d1 = self.theclass(1600+i, 12, 31)
1268 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
1269 if d.isocalendar()[1] == 53:
1270 L.append(i)
1271 self.assertEqual(L, iso_long_years)
1272
1273 def test_isoformat(self):
1274 t = self.theclass(2, 3, 2)
1275 self.assertEqual(t.isoformat(), "0002-03-02")
1276
1277 def test_ctime(self):
1278 t = self.theclass(2002, 3, 2)
1279 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
1280
1281 def test_strftime(self):
1282 t = self.theclass(2005, 3, 2)
1283 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
1284 self.assertEqual(t.strftime(""), "") # SF bug #761337
1285 self.assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784
1286
1287 self.assertRaises(TypeError, t.strftime) # needs an arg
1288 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
1289 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
1290
1291 # test that unicode input is allowed (issue 2782)
1292 self.assertEqual(t.strftime("%m"), "03")
1293
1294 # A naive object replaces %z and %Z w/ empty strings.
1295 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1296
1297 #make sure that invalid format specifiers are handled correctly
1298 #self.assertRaises(ValueError, t.strftime, "%e")
1299 #self.assertRaises(ValueError, t.strftime, "%")
1300 #self.assertRaises(ValueError, t.strftime, "%#")
1301
1302 #oh well, some systems just ignore those invalid ones.
Martin Panter46f50722016-05-26 05:35:26 +00001303 #at least, exercise them to make sure that no crashes
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001304 #are generated
1305 for f in ["%e", "%", "%#"]:
1306 try:
1307 t.strftime(f)
1308 except ValueError:
1309 pass
1310
1311 #check that this standard extension works
1312 t.strftime("%f")
1313
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001314 def test_format(self):
1315 dt = self.theclass(2007, 9, 10)
1316 self.assertEqual(dt.__format__(''), str(dt))
1317
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02001318 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001319 dt.__format__(123)
1320
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001321 # check that a derived class's __str__() gets called
1322 class A(self.theclass):
1323 def __str__(self):
1324 return 'A'
1325 a = A(2007, 9, 10)
1326 self.assertEqual(a.__format__(''), 'A')
1327
1328 # check that a derived class's strftime gets called
1329 class B(self.theclass):
1330 def strftime(self, format_spec):
1331 return 'B'
1332 b = B(2007, 9, 10)
1333 self.assertEqual(b.__format__(''), str(dt))
1334
1335 for fmt in ["m:%m d:%d y:%y",
1336 "m:%m d:%d y:%y H:%H M:%M S:%S",
1337 "%z %Z",
1338 ]:
1339 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1340 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1341 self.assertEqual(b.__format__(fmt), 'B')
1342
1343 def test_resolution_info(self):
1344 # XXX: Should min and max respect subclassing?
1345 if issubclass(self.theclass, datetime):
1346 expected_class = datetime
1347 else:
1348 expected_class = date
1349 self.assertIsInstance(self.theclass.min, expected_class)
1350 self.assertIsInstance(self.theclass.max, expected_class)
1351 self.assertIsInstance(self.theclass.resolution, timedelta)
1352 self.assertTrue(self.theclass.max > self.theclass.min)
1353
1354 def test_extreme_timedelta(self):
1355 big = self.theclass.max - self.theclass.min
1356 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
1357 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
1358 # n == 315537897599999999 ~= 2**58.13
1359 justasbig = timedelta(0, 0, n)
1360 self.assertEqual(big, justasbig)
1361 self.assertEqual(self.theclass.min + big, self.theclass.max)
1362 self.assertEqual(self.theclass.max - big, self.theclass.min)
1363
1364 def test_timetuple(self):
1365 for i in range(7):
1366 # January 2, 1956 is a Monday (0)
1367 d = self.theclass(1956, 1, 2+i)
1368 t = d.timetuple()
1369 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
1370 # February 1, 1956 is a Wednesday (2)
1371 d = self.theclass(1956, 2, 1+i)
1372 t = d.timetuple()
1373 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
1374 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
1375 # of the year.
1376 d = self.theclass(1956, 3, 1+i)
1377 t = d.timetuple()
1378 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
1379 self.assertEqual(t.tm_year, 1956)
1380 self.assertEqual(t.tm_mon, 3)
1381 self.assertEqual(t.tm_mday, 1+i)
1382 self.assertEqual(t.tm_hour, 0)
1383 self.assertEqual(t.tm_min, 0)
1384 self.assertEqual(t.tm_sec, 0)
1385 self.assertEqual(t.tm_wday, (3+i)%7)
1386 self.assertEqual(t.tm_yday, 61+i)
1387 self.assertEqual(t.tm_isdst, -1)
1388
1389 def test_pickling(self):
1390 args = 6, 7, 23
1391 orig = self.theclass(*args)
1392 for pickler, unpickler, proto in pickle_choices:
1393 green = pickler.dumps(orig, proto)
1394 derived = unpickler.loads(green)
1395 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02001396 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001397
1398 def test_compare(self):
1399 t1 = self.theclass(2, 3, 4)
1400 t2 = self.theclass(2, 3, 4)
1401 self.assertEqual(t1, t2)
1402 self.assertTrue(t1 <= t2)
1403 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001404 self.assertFalse(t1 != t2)
1405 self.assertFalse(t1 < t2)
1406 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001407
1408 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
1409 t2 = self.theclass(*args) # this is larger than t1
1410 self.assertTrue(t1 < t2)
1411 self.assertTrue(t2 > t1)
1412 self.assertTrue(t1 <= t2)
1413 self.assertTrue(t2 >= t1)
1414 self.assertTrue(t1 != t2)
1415 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001416 self.assertFalse(t1 == t2)
1417 self.assertFalse(t2 == t1)
1418 self.assertFalse(t1 > t2)
1419 self.assertFalse(t2 < t1)
1420 self.assertFalse(t1 >= t2)
1421 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001422
1423 for badarg in OTHERSTUFF:
1424 self.assertEqual(t1 == badarg, False)
1425 self.assertEqual(t1 != badarg, True)
1426 self.assertEqual(badarg == t1, False)
1427 self.assertEqual(badarg != t1, True)
1428
1429 self.assertRaises(TypeError, lambda: t1 < badarg)
1430 self.assertRaises(TypeError, lambda: t1 > badarg)
1431 self.assertRaises(TypeError, lambda: t1 >= badarg)
1432 self.assertRaises(TypeError, lambda: badarg <= t1)
1433 self.assertRaises(TypeError, lambda: badarg < t1)
1434 self.assertRaises(TypeError, lambda: badarg > t1)
1435 self.assertRaises(TypeError, lambda: badarg >= t1)
1436
1437 def test_mixed_compare(self):
1438 our = self.theclass(2000, 4, 5)
1439
1440 # Our class can be compared for equality to other classes
1441 self.assertEqual(our == 1, False)
1442 self.assertEqual(1 == our, False)
1443 self.assertEqual(our != 1, True)
1444 self.assertEqual(1 != our, True)
1445
1446 # But the ordering is undefined
1447 self.assertRaises(TypeError, lambda: our < 1)
1448 self.assertRaises(TypeError, lambda: 1 < our)
1449
1450 # Repeat those tests with a different class
1451
1452 class SomeClass:
1453 pass
1454
1455 their = SomeClass()
1456 self.assertEqual(our == their, False)
1457 self.assertEqual(their == our, False)
1458 self.assertEqual(our != their, True)
1459 self.assertEqual(their != our, True)
1460 self.assertRaises(TypeError, lambda: our < their)
1461 self.assertRaises(TypeError, lambda: their < our)
1462
1463 # However, if the other class explicitly defines ordering
1464 # relative to our class, it is allowed to do so
1465
1466 class LargerThanAnything:
1467 def __lt__(self, other):
1468 return False
1469 def __le__(self, other):
1470 return isinstance(other, LargerThanAnything)
1471 def __eq__(self, other):
1472 return isinstance(other, LargerThanAnything)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001473 def __gt__(self, other):
1474 return not isinstance(other, LargerThanAnything)
1475 def __ge__(self, other):
1476 return True
1477
1478 their = LargerThanAnything()
1479 self.assertEqual(our == their, False)
1480 self.assertEqual(their == our, False)
1481 self.assertEqual(our != their, True)
1482 self.assertEqual(their != our, True)
1483 self.assertEqual(our < their, True)
1484 self.assertEqual(their < our, False)
1485
1486 def test_bool(self):
1487 # All dates are considered true.
1488 self.assertTrue(self.theclass.min)
1489 self.assertTrue(self.theclass.max)
1490
Alexander Belopolsky89da3492011-05-02 13:14:24 -04001491 def test_strftime_y2k(self):
1492 for y in (1, 49, 70, 99, 100, 999, 1000, 1970):
Florent Xicluna49ce0682011-11-01 12:56:14 +01001493 d = self.theclass(y, 1, 1)
1494 # Issue 13305: For years < 1000, the value is not always
1495 # padded to 4 digits across platforms. The C standard
1496 # assumes year >= 1900, so it does not specify the number
1497 # of digits.
1498 if d.strftime("%Y") != '%04d' % y:
1499 # Year 42 returns '42', not padded
1500 self.assertEqual(d.strftime("%Y"), '%d' % y)
Victor Stinner15a83e82016-03-12 08:16:48 +01001501 # '0042' is obtained anyway
1502 self.assertEqual(d.strftime("%4Y"), '%04d' % y)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001503
1504 def test_replace(self):
1505 cls = self.theclass
1506 args = [1, 2, 3]
1507 base = cls(*args)
1508 self.assertEqual(base, base.replace())
1509
1510 i = 0
1511 for name, newval in (("year", 2),
1512 ("month", 3),
1513 ("day", 4)):
1514 newargs = args[:]
1515 newargs[i] = newval
1516 expected = cls(*newargs)
1517 got = base.replace(**{name: newval})
1518 self.assertEqual(expected, got)
1519 i += 1
1520
1521 # Out of bounds.
1522 base = cls(2000, 2, 29)
1523 self.assertRaises(ValueError, base.replace, year=2001)
1524
Paul Ganssle191e9932017-11-09 16:34:29 -05001525 def test_subclass_replace(self):
1526 class DateSubclass(self.theclass):
1527 pass
1528
1529 dt = DateSubclass(2012, 1, 1)
1530 self.assertIs(type(dt.replace(year=2013)), DateSubclass)
1531
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001532 def test_subclass_date(self):
1533
1534 class C(self.theclass):
1535 theAnswer = 42
1536
1537 def __new__(cls, *args, **kws):
1538 temp = kws.copy()
1539 extra = temp.pop('extra')
1540 result = self.theclass.__new__(cls, *args, **temp)
1541 result.extra = extra
1542 return result
1543
1544 def newmeth(self, start):
1545 return start + self.year + self.month
1546
1547 args = 2003, 4, 14
1548
1549 dt1 = self.theclass(*args)
1550 dt2 = C(*args, **{'extra': 7})
1551
1552 self.assertEqual(dt2.__class__, C)
1553 self.assertEqual(dt2.theAnswer, 42)
1554 self.assertEqual(dt2.extra, 7)
1555 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1556 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1557
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05001558 def test_subclass_alternate_constructors(self):
1559 # Test that alternate constructors call the constructor
1560 class DateSubclass(self.theclass):
1561 def __new__(cls, *args, **kwargs):
1562 result = self.theclass.__new__(cls, *args, **kwargs)
1563 result.extra = 7
1564
1565 return result
1566
1567 args = (2003, 4, 14)
1568 d_ord = 731319 # Equivalent ordinal date
1569 d_isoformat = '2003-04-14' # Equivalent isoformat()
1570
1571 base_d = DateSubclass(*args)
1572 self.assertIsInstance(base_d, DateSubclass)
1573 self.assertEqual(base_d.extra, 7)
1574
1575 # Timestamp depends on time zone, so we'll calculate the equivalent here
1576 ts = datetime.combine(base_d, time(0)).timestamp()
1577
1578 test_cases = [
1579 ('fromordinal', (d_ord,)),
1580 ('fromtimestamp', (ts,)),
1581 ('fromisoformat', (d_isoformat,)),
1582 ]
1583
1584 for constr_name, constr_args in test_cases:
1585 for base_obj in (DateSubclass, base_d):
1586 # Test both the classmethod and method
1587 with self.subTest(base_obj_type=type(base_obj),
1588 constr_name=constr_name):
1589 constr = getattr(base_obj, constr_name)
1590
1591 dt = constr(*constr_args)
1592
1593 # Test that it creates the right subclass
1594 self.assertIsInstance(dt, DateSubclass)
1595
1596 # Test that it's equal to the base object
1597 self.assertEqual(dt, base_d)
1598
1599 # Test that it called the constructor
1600 self.assertEqual(dt.extra, 7)
1601
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001602 def test_pickling_subclass_date(self):
1603
1604 args = 6, 7, 23
1605 orig = SubclassDate(*args)
1606 for pickler, unpickler, proto in pickle_choices:
1607 green = pickler.dumps(orig, proto)
1608 derived = unpickler.loads(green)
1609 self.assertEqual(orig, derived)
1610
1611 def test_backdoor_resistance(self):
1612 # For fast unpickling, the constructor accepts a pickle byte string.
1613 # This is a low-overhead backdoor. A user can (by intent or
1614 # mistake) pass a string directly, which (if it's the right length)
1615 # will get treated like a pickle, and bypass the normal sanity
1616 # checks in the constructor. This can create insane objects.
1617 # The constructor doesn't want to burn the time to validate all
1618 # fields, but does check the month field. This stops, e.g.,
1619 # datetime.datetime('1995-03-25') from yielding an insane object.
1620 base = b'1995-03-25'
1621 if not issubclass(self.theclass, datetime):
1622 base = base[:4]
1623 for month_byte in b'9', b'\0', b'\r', b'\xff':
1624 self.assertRaises(TypeError, self.theclass,
1625 base[:2] + month_byte + base[3:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001626 if issubclass(self.theclass, datetime):
1627 # Good bytes, but bad tzinfo:
1628 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
1629 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001630
1631 for ord_byte in range(1, 13):
1632 # This shouldn't blow up because of the month byte alone. If
1633 # the implementation changes to do more-careful checking, it may
1634 # blow up because other fields are insane.
1635 self.theclass(base[:2] + bytes([ord_byte]) + base[3:])
1636
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001637 def test_fromisoformat(self):
1638 # Test that isoformat() is reversible
1639 base_dates = [
1640 (1, 1, 1),
1641 (1000, 2, 14),
1642 (1900, 1, 1),
1643 (2000, 2, 29),
1644 (2004, 11, 12),
1645 (2004, 4, 3),
1646 (2017, 5, 30)
1647 ]
1648
1649 for dt_tuple in base_dates:
1650 dt = self.theclass(*dt_tuple)
1651 dt_str = dt.isoformat()
1652 with self.subTest(dt_str=dt_str):
1653 dt_rt = self.theclass.fromisoformat(dt.isoformat())
1654
1655 self.assertEqual(dt, dt_rt)
1656
1657 def test_fromisoformat_subclass(self):
1658 class DateSubclass(self.theclass):
1659 pass
1660
1661 dt = DateSubclass(2014, 12, 14)
1662
1663 dt_rt = DateSubclass.fromisoformat(dt.isoformat())
1664
1665 self.assertIsInstance(dt_rt, DateSubclass)
1666
1667 def test_fromisoformat_fails(self):
1668 # Test that fromisoformat() fails on invalid values
1669 bad_strs = [
1670 '', # Empty string
Paul Ganssle096329f2018-08-23 11:06:20 -04001671 '\ud800', # bpo-34454: Surrogate code point
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001672 '009-03-04', # Not 10 characters
1673 '123456789', # Not a date
1674 '200a-12-04', # Invalid character in year
1675 '2009-1a-04', # Invalid character in month
1676 '2009-12-0a', # Invalid character in day
1677 '2009-01-32', # Invalid day
1678 '2009-02-29', # Invalid leap day
1679 '20090228', # Valid ISO8601 output not from isoformat()
Paul Ganssle096329f2018-08-23 11:06:20 -04001680 '2009\ud80002\ud80028', # Separators are surrogate codepoints
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001681 ]
1682
1683 for bad_str in bad_strs:
1684 with self.assertRaises(ValueError):
1685 self.theclass.fromisoformat(bad_str)
1686
1687 def test_fromisoformat_fails_typeerror(self):
1688 # Test that fromisoformat fails when passed the wrong type
1689 import io
1690
1691 bad_types = [b'2009-03-01', None, io.StringIO('2009-03-01')]
1692 for bad_type in bad_types:
1693 with self.assertRaises(TypeError):
1694 self.theclass.fromisoformat(bad_type)
1695
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001696#############################################################################
1697# datetime tests
1698
1699class SubclassDatetime(datetime):
1700 sub_var = 1
1701
1702class TestDateTime(TestDate):
1703
1704 theclass = datetime
1705
1706 def test_basic_attributes(self):
1707 dt = self.theclass(2002, 3, 1, 12, 0)
1708 self.assertEqual(dt.year, 2002)
1709 self.assertEqual(dt.month, 3)
1710 self.assertEqual(dt.day, 1)
1711 self.assertEqual(dt.hour, 12)
1712 self.assertEqual(dt.minute, 0)
1713 self.assertEqual(dt.second, 0)
1714 self.assertEqual(dt.microsecond, 0)
1715
1716 def test_basic_attributes_nonzero(self):
1717 # Make sure all attributes are non-zero so bugs in
1718 # bit-shifting access show up.
1719 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1720 self.assertEqual(dt.year, 2002)
1721 self.assertEqual(dt.month, 3)
1722 self.assertEqual(dt.day, 1)
1723 self.assertEqual(dt.hour, 12)
1724 self.assertEqual(dt.minute, 59)
1725 self.assertEqual(dt.second, 59)
1726 self.assertEqual(dt.microsecond, 8000)
1727
1728 def test_roundtrip(self):
1729 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1730 self.theclass.now()):
1731 # Verify dt -> string -> datetime identity.
1732 s = repr(dt)
1733 self.assertTrue(s.startswith('datetime.'))
1734 s = s[9:]
1735 dt2 = eval(s)
1736 self.assertEqual(dt, dt2)
1737
1738 # Verify identity via reconstructing from pieces.
1739 dt2 = self.theclass(dt.year, dt.month, dt.day,
1740 dt.hour, dt.minute, dt.second,
1741 dt.microsecond)
1742 self.assertEqual(dt, dt2)
1743
1744 def test_isoformat(self):
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001745 t = self.theclass(1, 2, 3, 4, 5, 1, 123)
1746 self.assertEqual(t.isoformat(), "0001-02-03T04:05:01.000123")
1747 self.assertEqual(t.isoformat('T'), "0001-02-03T04:05:01.000123")
1748 self.assertEqual(t.isoformat(' '), "0001-02-03 04:05:01.000123")
1749 self.assertEqual(t.isoformat('\x00'), "0001-02-03\x0004:05:01.000123")
1750 self.assertEqual(t.isoformat(timespec='hours'), "0001-02-03T04")
1751 self.assertEqual(t.isoformat(timespec='minutes'), "0001-02-03T04:05")
1752 self.assertEqual(t.isoformat(timespec='seconds'), "0001-02-03T04:05:01")
1753 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1754 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000123")
1755 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01.000123")
1756 self.assertEqual(t.isoformat(sep=' ', timespec='minutes'), "0001-02-03 04:05")
1757 self.assertRaises(ValueError, t.isoformat, timespec='foo')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001758 # str is ISO format with the separator forced to a blank.
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001759 self.assertEqual(str(t), "0001-02-03 04:05:01.000123")
1760
1761 t = self.theclass(1, 2, 3, 4, 5, 1, 999500, tzinfo=timezone.utc)
1762 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999+00:00")
1763
1764 t = self.theclass(1, 2, 3, 4, 5, 1, 999500)
1765 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999")
1766
1767 t = self.theclass(1, 2, 3, 4, 5, 1)
1768 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01")
1769 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1770 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000000")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001771
1772 t = self.theclass(2, 3, 2)
1773 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1774 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1775 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1776 # str is ISO format with the separator forced to a blank.
1777 self.assertEqual(str(t), "0002-03-02 00:00:00")
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001778 # ISO format with timezone
1779 tz = FixedOffset(timedelta(seconds=16), 'XXX')
1780 t = self.theclass(2, 3, 2, tzinfo=tz)
1781 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00+00:00:16")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001782
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001783 def test_isoformat_timezone(self):
1784 tzoffsets = [
1785 ('05:00', timedelta(hours=5)),
1786 ('02:00', timedelta(hours=2)),
1787 ('06:27', timedelta(hours=6, minutes=27)),
1788 ('12:32:30', timedelta(hours=12, minutes=32, seconds=30)),
1789 ('02:04:09.123456', timedelta(hours=2, minutes=4, seconds=9, microseconds=123456))
1790 ]
1791
1792 tzinfos = [
1793 ('', None),
1794 ('+00:00', timezone.utc),
1795 ('+00:00', timezone(timedelta(0))),
1796 ]
1797
1798 tzinfos += [
1799 (prefix + expected, timezone(sign * td))
1800 for expected, td in tzoffsets
1801 for prefix, sign in [('-', -1), ('+', 1)]
1802 ]
1803
1804 dt_base = self.theclass(2016, 4, 1, 12, 37, 9)
1805 exp_base = '2016-04-01T12:37:09'
1806
1807 for exp_tz, tzi in tzinfos:
1808 dt = dt_base.replace(tzinfo=tzi)
1809 exp = exp_base + exp_tz
1810 with self.subTest(tzi=tzi):
1811 assert dt.isoformat() == exp
1812
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001813 def test_format(self):
1814 dt = self.theclass(2007, 9, 10, 4, 5, 1, 123)
1815 self.assertEqual(dt.__format__(''), str(dt))
1816
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02001817 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001818 dt.__format__(123)
1819
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001820 # check that a derived class's __str__() gets called
1821 class A(self.theclass):
1822 def __str__(self):
1823 return 'A'
1824 a = A(2007, 9, 10, 4, 5, 1, 123)
1825 self.assertEqual(a.__format__(''), 'A')
1826
1827 # check that a derived class's strftime gets called
1828 class B(self.theclass):
1829 def strftime(self, format_spec):
1830 return 'B'
1831 b = B(2007, 9, 10, 4, 5, 1, 123)
1832 self.assertEqual(b.__format__(''), str(dt))
1833
1834 for fmt in ["m:%m d:%d y:%y",
1835 "m:%m d:%d y:%y H:%H M:%M S:%S",
1836 "%z %Z",
1837 ]:
1838 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1839 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1840 self.assertEqual(b.__format__(fmt), 'B')
1841
1842 def test_more_ctime(self):
1843 # Test fields that TestDate doesn't touch.
1844 import time
1845
1846 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1847 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1848 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1849 # out. The difference is that t.ctime() produces " 2" for the day,
1850 # but platform ctime() produces "02" for the day. According to
1851 # C99, t.ctime() is correct here.
1852 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1853
1854 # So test a case where that difference doesn't matter.
1855 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1856 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1857
1858 def test_tz_independent_comparing(self):
1859 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1860 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1861 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1862 self.assertEqual(dt1, dt3)
1863 self.assertTrue(dt2 > dt3)
1864
1865 # Make sure comparison doesn't forget microseconds, and isn't done
1866 # via comparing a float timestamp (an IEEE double doesn't have enough
Leo Ariasc3d95082018-02-03 18:36:10 -06001867 # precision to span microsecond resolution across years 1 through 9999,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001868 # so comparing via timestamp necessarily calls some distinct values
1869 # equal).
1870 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1871 us = timedelta(microseconds=1)
1872 dt2 = dt1 + us
1873 self.assertEqual(dt2 - dt1, us)
1874 self.assertTrue(dt1 < dt2)
1875
1876 def test_strftime_with_bad_tzname_replace(self):
1877 # verify ok if tzinfo.tzname().replace() returns a non-string
1878 class MyTzInfo(FixedOffset):
1879 def tzname(self, dt):
1880 class MyStr(str):
1881 def replace(self, *args):
1882 return None
1883 return MyStr('name')
1884 t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
1885 self.assertRaises(TypeError, t.strftime, '%Z')
1886
1887 def test_bad_constructor_arguments(self):
1888 # bad years
1889 self.theclass(MINYEAR, 1, 1) # no exception
1890 self.theclass(MAXYEAR, 1, 1) # no exception
1891 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1892 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1893 # bad months
1894 self.theclass(2000, 1, 1) # no exception
1895 self.theclass(2000, 12, 1) # no exception
1896 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1897 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1898 # bad days
1899 self.theclass(2000, 2, 29) # no exception
1900 self.theclass(2004, 2, 29) # no exception
1901 self.theclass(2400, 2, 29) # no exception
1902 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1903 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1904 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1905 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1906 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1907 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1908 # bad hours
1909 self.theclass(2000, 1, 31, 0) # no exception
1910 self.theclass(2000, 1, 31, 23) # no exception
1911 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1912 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1913 # bad minutes
1914 self.theclass(2000, 1, 31, 23, 0) # no exception
1915 self.theclass(2000, 1, 31, 23, 59) # no exception
1916 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1917 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1918 # bad seconds
1919 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1920 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1921 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1922 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1923 # bad microseconds
1924 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1925 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1926 self.assertRaises(ValueError, self.theclass,
1927 2000, 1, 31, 23, 59, 59, -1)
1928 self.assertRaises(ValueError, self.theclass,
1929 2000, 1, 31, 23, 59, 59,
1930 1000000)
Alexander Belopolsky47649ab2016-08-08 17:05:40 -04001931 # bad fold
1932 self.assertRaises(ValueError, self.theclass,
1933 2000, 1, 31, fold=-1)
1934 self.assertRaises(ValueError, self.theclass,
1935 2000, 1, 31, fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001936 # Positional fold:
1937 self.assertRaises(TypeError, self.theclass,
1938 2000, 1, 31, 23, 59, 59, 0, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04001939
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001940 def test_hash_equality(self):
1941 d = self.theclass(2000, 12, 31, 23, 30, 17)
1942 e = self.theclass(2000, 12, 31, 23, 30, 17)
1943 self.assertEqual(d, e)
1944 self.assertEqual(hash(d), hash(e))
1945
1946 dic = {d: 1}
1947 dic[e] = 2
1948 self.assertEqual(len(dic), 1)
1949 self.assertEqual(dic[d], 2)
1950 self.assertEqual(dic[e], 2)
1951
1952 d = self.theclass(2001, 1, 1, 0, 5, 17)
1953 e = self.theclass(2001, 1, 1, 0, 5, 17)
1954 self.assertEqual(d, e)
1955 self.assertEqual(hash(d), hash(e))
1956
1957 dic = {d: 1}
1958 dic[e] = 2
1959 self.assertEqual(len(dic), 1)
1960 self.assertEqual(dic[d], 2)
1961 self.assertEqual(dic[e], 2)
1962
1963 def test_computations(self):
1964 a = self.theclass(2002, 1, 31)
1965 b = self.theclass(1956, 1, 31)
1966 diff = a-b
1967 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1968 self.assertEqual(diff.seconds, 0)
1969 self.assertEqual(diff.microseconds, 0)
1970 a = self.theclass(2002, 3, 2, 17, 6)
1971 millisec = timedelta(0, 0, 1000)
1972 hour = timedelta(0, 3600)
1973 day = timedelta(1)
1974 week = timedelta(7)
1975 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1976 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1977 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1978 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1979 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1980 self.assertEqual(a - hour, a + -hour)
1981 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1982 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1983 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1984 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1985 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1986 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1987 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1988 self.assertEqual((a + week) - a, week)
1989 self.assertEqual((a + day) - a, day)
1990 self.assertEqual((a + hour) - a, hour)
1991 self.assertEqual((a + millisec) - a, millisec)
1992 self.assertEqual((a - week) - a, -week)
1993 self.assertEqual((a - day) - a, -day)
1994 self.assertEqual((a - hour) - a, -hour)
1995 self.assertEqual((a - millisec) - a, -millisec)
1996 self.assertEqual(a - (a + week), -week)
1997 self.assertEqual(a - (a + day), -day)
1998 self.assertEqual(a - (a + hour), -hour)
1999 self.assertEqual(a - (a + millisec), -millisec)
2000 self.assertEqual(a - (a - week), week)
2001 self.assertEqual(a - (a - day), day)
2002 self.assertEqual(a - (a - hour), hour)
2003 self.assertEqual(a - (a - millisec), millisec)
2004 self.assertEqual(a + (week + day + hour + millisec),
2005 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
2006 self.assertEqual(a + (week + day + hour + millisec),
2007 (((a + week) + day) + hour) + millisec)
2008 self.assertEqual(a - (week + day + hour + millisec),
2009 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
2010 self.assertEqual(a - (week + day + hour + millisec),
2011 (((a - week) - day) - hour) - millisec)
2012 # Add/sub ints or floats should be illegal
2013 for i in 1, 1.0:
2014 self.assertRaises(TypeError, lambda: a+i)
2015 self.assertRaises(TypeError, lambda: a-i)
2016 self.assertRaises(TypeError, lambda: i+a)
2017 self.assertRaises(TypeError, lambda: i-a)
2018
2019 # delta - datetime is senseless.
2020 self.assertRaises(TypeError, lambda: day - a)
2021 # mixing datetime and (delta or datetime) via * or // is senseless
2022 self.assertRaises(TypeError, lambda: day * a)
2023 self.assertRaises(TypeError, lambda: a * day)
2024 self.assertRaises(TypeError, lambda: day // a)
2025 self.assertRaises(TypeError, lambda: a // day)
2026 self.assertRaises(TypeError, lambda: a * a)
2027 self.assertRaises(TypeError, lambda: a // a)
2028 # datetime + datetime is senseless
2029 self.assertRaises(TypeError, lambda: a + a)
2030
2031 def test_pickling(self):
2032 args = 6, 7, 23, 20, 59, 1, 64**2
2033 orig = self.theclass(*args)
2034 for pickler, unpickler, proto in pickle_choices:
2035 green = pickler.dumps(orig, proto)
2036 derived = unpickler.loads(green)
2037 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02002038 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002039
2040 def test_more_pickling(self):
2041 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
Serhiy Storchakabad12572014-12-15 14:03:42 +02002042 for proto in range(pickle.HIGHEST_PROTOCOL + 1):
2043 s = pickle.dumps(a, proto)
2044 b = pickle.loads(s)
2045 self.assertEqual(b.year, 2003)
2046 self.assertEqual(b.month, 2)
2047 self.assertEqual(b.day, 7)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002048
2049 def test_pickling_subclass_datetime(self):
2050 args = 6, 7, 23, 20, 59, 1, 64**2
2051 orig = SubclassDatetime(*args)
2052 for pickler, unpickler, proto in pickle_choices:
2053 green = pickler.dumps(orig, proto)
2054 derived = unpickler.loads(green)
2055 self.assertEqual(orig, derived)
2056
2057 def test_more_compare(self):
2058 # The test_compare() inherited from TestDate covers the error cases.
2059 # We just want to test lexicographic ordering on the members datetime
2060 # has that date lacks.
2061 args = [2000, 11, 29, 20, 58, 16, 999998]
2062 t1 = self.theclass(*args)
2063 t2 = self.theclass(*args)
2064 self.assertEqual(t1, t2)
2065 self.assertTrue(t1 <= t2)
2066 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002067 self.assertFalse(t1 != t2)
2068 self.assertFalse(t1 < t2)
2069 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002070
2071 for i in range(len(args)):
2072 newargs = args[:]
2073 newargs[i] = args[i] + 1
2074 t2 = self.theclass(*newargs) # this is larger than t1
2075 self.assertTrue(t1 < t2)
2076 self.assertTrue(t2 > t1)
2077 self.assertTrue(t1 <= t2)
2078 self.assertTrue(t2 >= t1)
2079 self.assertTrue(t1 != t2)
2080 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002081 self.assertFalse(t1 == t2)
2082 self.assertFalse(t2 == t1)
2083 self.assertFalse(t1 > t2)
2084 self.assertFalse(t2 < t1)
2085 self.assertFalse(t1 >= t2)
2086 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002087
2088
2089 # A helper for timestamp constructor tests.
2090 def verify_field_equality(self, expected, got):
2091 self.assertEqual(expected.tm_year, got.year)
2092 self.assertEqual(expected.tm_mon, got.month)
2093 self.assertEqual(expected.tm_mday, got.day)
2094 self.assertEqual(expected.tm_hour, got.hour)
2095 self.assertEqual(expected.tm_min, got.minute)
2096 self.assertEqual(expected.tm_sec, got.second)
2097
2098 def test_fromtimestamp(self):
2099 import time
2100
2101 ts = time.time()
2102 expected = time.localtime(ts)
2103 got = self.theclass.fromtimestamp(ts)
2104 self.verify_field_equality(expected, got)
2105
2106 def test_utcfromtimestamp(self):
2107 import time
2108
2109 ts = time.time()
2110 expected = time.gmtime(ts)
2111 got = self.theclass.utcfromtimestamp(ts)
2112 self.verify_field_equality(expected, got)
2113
Alexander Belopolskya4415142012-06-08 12:33:09 -04002114 # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in
2115 # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0).
2116 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
2117 def test_timestamp_naive(self):
2118 t = self.theclass(1970, 1, 1)
2119 self.assertEqual(t.timestamp(), 18000.0)
2120 t = self.theclass(1970, 1, 1, 1, 2, 3, 4)
2121 self.assertEqual(t.timestamp(),
2122 18000.0 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002123 # Missing hour
2124 t0 = self.theclass(2012, 3, 11, 2, 30)
2125 t1 = t0.replace(fold=1)
2126 self.assertEqual(self.theclass.fromtimestamp(t1.timestamp()),
2127 t0 - timedelta(hours=1))
2128 self.assertEqual(self.theclass.fromtimestamp(t0.timestamp()),
2129 t1 + timedelta(hours=1))
Alexander Belopolskya4415142012-06-08 12:33:09 -04002130 # Ambiguous hour defaults to DST
2131 t = self.theclass(2012, 11, 4, 1, 30)
2132 self.assertEqual(self.theclass.fromtimestamp(t.timestamp()), t)
2133
2134 # Timestamp may raise an overflow error on some platforms
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002135 # XXX: Do we care to support the first and last year?
2136 for t in [self.theclass(2,1,1), self.theclass(9998,12,12)]:
Alexander Belopolskya4415142012-06-08 12:33:09 -04002137 try:
2138 s = t.timestamp()
2139 except OverflowError:
2140 pass
2141 else:
2142 self.assertEqual(self.theclass.fromtimestamp(s), t)
2143
2144 def test_timestamp_aware(self):
2145 t = self.theclass(1970, 1, 1, tzinfo=timezone.utc)
2146 self.assertEqual(t.timestamp(), 0.0)
2147 t = self.theclass(1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone.utc)
2148 self.assertEqual(t.timestamp(),
2149 3600 + 2*60 + 3 + 4*1e-6)
2150 t = self.theclass(1970, 1, 1, 1, 2, 3, 4,
2151 tzinfo=timezone(timedelta(hours=-5), 'EST'))
2152 self.assertEqual(t.timestamp(),
2153 18000 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002154
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002155 @support.run_with_tz('MSK-03') # Something east of Greenwich
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002156 def test_microsecond_rounding(self):
Victor Stinner5ebfe422015-09-18 14:52:15 +02002157 for fts in [self.theclass.fromtimestamp,
2158 self.theclass.utcfromtimestamp]:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002159 zero = fts(0)
2160 self.assertEqual(zero.second, 0)
2161 self.assertEqual(zero.microsecond, 0)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002162 one = fts(1e-6)
Victor Stinner8050ca92012-03-14 00:17:05 +01002163 try:
2164 minus_one = fts(-1e-6)
2165 except OSError:
2166 # localtime(-1) and gmtime(-1) is not supported on Windows
2167 pass
2168 else:
2169 self.assertEqual(minus_one.second, 59)
2170 self.assertEqual(minus_one.microsecond, 999999)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002171
Victor Stinner8050ca92012-03-14 00:17:05 +01002172 t = fts(-1e-8)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002173 self.assertEqual(t, zero)
Victor Stinner8050ca92012-03-14 00:17:05 +01002174 t = fts(-9e-7)
2175 self.assertEqual(t, minus_one)
2176 t = fts(-1e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002177 self.assertEqual(t, zero)
Victor Stinner8820a352015-09-05 10:50:20 +02002178 t = fts(-1/2**7)
2179 self.assertEqual(t.second, 59)
Victor Stinner7667f582015-09-09 01:02:23 +02002180 self.assertEqual(t.microsecond, 992188)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002181
2182 t = fts(1e-7)
2183 self.assertEqual(t, zero)
2184 t = fts(9e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002185 self.assertEqual(t, one)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002186 t = fts(0.99999949)
2187 self.assertEqual(t.second, 0)
2188 self.assertEqual(t.microsecond, 999999)
2189 t = fts(0.9999999)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002190 self.assertEqual(t.second, 1)
2191 self.assertEqual(t.microsecond, 0)
Victor Stinneradfefa52015-09-04 23:57:25 +02002192 t = fts(1/2**7)
2193 self.assertEqual(t.second, 0)
Victor Stinner7667f582015-09-09 01:02:23 +02002194 self.assertEqual(t.microsecond, 7812)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002195
Victor Stinnerb67f0962017-02-10 10:34:02 +01002196 def test_timestamp_limits(self):
2197 # minimum timestamp
2198 min_dt = self.theclass.min.replace(tzinfo=timezone.utc)
2199 min_ts = min_dt.timestamp()
Victor Stinner6f37e362017-02-10 11:45:14 +01002200 try:
2201 # date 0001-01-01 00:00:00+00:00: timestamp=-62135596800
2202 self.assertEqual(self.theclass.fromtimestamp(min_ts, tz=timezone.utc),
2203 min_dt)
Victor Stinner2a35c932017-02-10 12:37:21 +01002204 except (OverflowError, OSError) as exc:
2205 # the date 0001-01-01 doesn't fit into 32-bit time_t,
2206 # or platform doesn't support such very old date
Victor Stinner6f37e362017-02-10 11:45:14 +01002207 self.skipTest(str(exc))
Victor Stinnerb67f0962017-02-10 10:34:02 +01002208
2209 # maximum timestamp: set seconds to zero to avoid rounding issues
2210 max_dt = self.theclass.max.replace(tzinfo=timezone.utc,
2211 second=0, microsecond=0)
2212 max_ts = max_dt.timestamp()
2213 # date 9999-12-31 23:59:00+00:00: timestamp 253402300740
2214 self.assertEqual(self.theclass.fromtimestamp(max_ts, tz=timezone.utc),
2215 max_dt)
2216
2217 # number of seconds greater than 1 year: make sure that the new date
2218 # is not valid in datetime.datetime limits
2219 delta = 3600 * 24 * 400
2220
2221 # too small
2222 ts = min_ts - delta
2223 # converting a Python int to C time_t can raise a OverflowError,
2224 # especially on 32-bit platforms.
2225 with self.assertRaises((ValueError, OverflowError)):
2226 self.theclass.fromtimestamp(ts)
2227 with self.assertRaises((ValueError, OverflowError)):
2228 self.theclass.utcfromtimestamp(ts)
2229
2230 # too big
2231 ts = max_dt.timestamp() + delta
2232 with self.assertRaises((ValueError, OverflowError)):
2233 self.theclass.fromtimestamp(ts)
2234 with self.assertRaises((ValueError, OverflowError)):
2235 self.theclass.utcfromtimestamp(ts)
2236
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002237 def test_insane_fromtimestamp(self):
2238 # It's possible that some platform maps time_t to double,
2239 # and that this test will fail there. This test should
2240 # exempt such platforms (provided they return reasonable
2241 # results!).
2242 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002243 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002244 insane)
2245
2246 def test_insane_utcfromtimestamp(self):
2247 # It's possible that some platform maps time_t to double,
2248 # and that this test will fail there. This test should
2249 # exempt such platforms (provided they return reasonable
2250 # results!).
2251 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002252 self.assertRaises(OverflowError, self.theclass.utcfromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002253 insane)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002254
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002255 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2256 def test_negative_float_fromtimestamp(self):
2257 # The result is tz-dependent; at least test that this doesn't
2258 # fail (like it did before bug 1646728 was fixed).
2259 self.theclass.fromtimestamp(-1.05)
2260
2261 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2262 def test_negative_float_utcfromtimestamp(self):
2263 d = self.theclass.utcfromtimestamp(-1.05)
2264 self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
2265
2266 def test_utcnow(self):
2267 import time
2268
2269 # Call it a success if utcnow() and utcfromtimestamp() are within
2270 # a second of each other.
2271 tolerance = timedelta(seconds=1)
2272 for dummy in range(3):
2273 from_now = self.theclass.utcnow()
2274 from_timestamp = self.theclass.utcfromtimestamp(time.time())
2275 if abs(from_timestamp - from_now) <= tolerance:
2276 break
2277 # Else try again a few times.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002278 self.assertLessEqual(abs(from_timestamp - from_now), tolerance)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002279
2280 def test_strptime(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002281 string = '2004-12-01 13:02:47.197'
2282 format = '%Y-%m-%d %H:%M:%S.%f'
2283 expected = _strptime._strptime_datetime(self.theclass, string, format)
2284 got = self.theclass.strptime(string, format)
2285 self.assertEqual(expected, got)
2286 self.assertIs(type(expected), self.theclass)
2287 self.assertIs(type(got), self.theclass)
2288
2289 strptime = self.theclass.strptime
2290 self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
2291 self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
Mario Corchero32318932017-10-26 01:35:41 +01002292 self.assertEqual(
2293 strptime("-00:02:01.000003", "%z").utcoffset(),
2294 -timedelta(minutes=2, seconds=1, microseconds=3)
2295 )
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002296 # Only local timezone and UTC are supported
2297 for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
2298 (-_time.timezone, _time.tzname[0])):
2299 if tzseconds < 0:
2300 sign = '-'
2301 seconds = -tzseconds
2302 else:
2303 sign ='+'
2304 seconds = tzseconds
2305 hours, minutes = divmod(seconds//60, 60)
2306 dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
Martin Panterfca22322015-11-16 09:22:19 +00002307 dt = strptime(dtstr, "%z %Z")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002308 self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
2309 self.assertEqual(dt.tzname(), tzname)
2310 # Can produce inconsistent datetime
2311 dtstr, fmt = "+1234 UTC", "%z %Z"
2312 dt = strptime(dtstr, fmt)
2313 self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE)
2314 self.assertEqual(dt.tzname(), 'UTC')
2315 # yet will roundtrip
2316 self.assertEqual(dt.strftime(fmt), dtstr)
2317
2318 # Produce naive datetime if no %z is provided
2319 self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
2320
2321 with self.assertRaises(ValueError): strptime("-2400", "%z")
2322 with self.assertRaises(ValueError): strptime("-000", "%z")
2323
2324 def test_more_timetuple(self):
2325 # This tests fields beyond those tested by the TestDate.test_timetuple.
2326 t = self.theclass(2004, 12, 31, 6, 22, 33)
2327 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
2328 self.assertEqual(t.timetuple(),
2329 (t.year, t.month, t.day,
2330 t.hour, t.minute, t.second,
2331 t.weekday(),
2332 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
2333 -1))
2334 tt = t.timetuple()
2335 self.assertEqual(tt.tm_year, t.year)
2336 self.assertEqual(tt.tm_mon, t.month)
2337 self.assertEqual(tt.tm_mday, t.day)
2338 self.assertEqual(tt.tm_hour, t.hour)
2339 self.assertEqual(tt.tm_min, t.minute)
2340 self.assertEqual(tt.tm_sec, t.second)
2341 self.assertEqual(tt.tm_wday, t.weekday())
2342 self.assertEqual(tt.tm_yday, t.toordinal() -
2343 date(t.year, 1, 1).toordinal() + 1)
2344 self.assertEqual(tt.tm_isdst, -1)
2345
2346 def test_more_strftime(self):
2347 # This tests fields beyond those tested by the TestDate.test_strftime.
2348 t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
2349 self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
2350 "12 31 04 000047 33 22 06 366")
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04002351 for (s, us), z in [((33, 123), "33.000123"), ((33, 0), "33"),]:
2352 tz = timezone(-timedelta(hours=2, seconds=s, microseconds=us))
2353 t = t.replace(tzinfo=tz)
2354 self.assertEqual(t.strftime("%z"), "-0200" + z)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002355
2356 def test_extract(self):
2357 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2358 self.assertEqual(dt.date(), date(2002, 3, 4))
2359 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2360
2361 def test_combine(self):
2362 d = date(2002, 3, 4)
2363 t = time(18, 45, 3, 1234)
2364 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2365 combine = self.theclass.combine
2366 dt = combine(d, t)
2367 self.assertEqual(dt, expected)
2368
2369 dt = combine(time=t, date=d)
2370 self.assertEqual(dt, expected)
2371
2372 self.assertEqual(d, dt.date())
2373 self.assertEqual(t, dt.time())
2374 self.assertEqual(dt, combine(dt.date(), dt.time()))
2375
2376 self.assertRaises(TypeError, combine) # need an arg
2377 self.assertRaises(TypeError, combine, d) # need two args
2378 self.assertRaises(TypeError, combine, t, d) # args reversed
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002379 self.assertRaises(TypeError, combine, d, t, 1) # wrong tzinfo type
2380 self.assertRaises(TypeError, combine, d, t, 1, 2) # too many args
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002381 self.assertRaises(TypeError, combine, "date", "time") # wrong types
2382 self.assertRaises(TypeError, combine, d, "time") # wrong type
2383 self.assertRaises(TypeError, combine, "date", t) # wrong type
2384
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002385 # tzinfo= argument
2386 dt = combine(d, t, timezone.utc)
2387 self.assertIs(dt.tzinfo, timezone.utc)
2388 dt = combine(d, t, tzinfo=timezone.utc)
2389 self.assertIs(dt.tzinfo, timezone.utc)
2390 t = time()
2391 dt = combine(dt, t)
2392 self.assertEqual(dt.date(), d)
2393 self.assertEqual(dt.time(), t)
2394
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002395 def test_replace(self):
2396 cls = self.theclass
2397 args = [1, 2, 3, 4, 5, 6, 7]
2398 base = cls(*args)
2399 self.assertEqual(base, base.replace())
2400
2401 i = 0
2402 for name, newval in (("year", 2),
2403 ("month", 3),
2404 ("day", 4),
2405 ("hour", 5),
2406 ("minute", 6),
2407 ("second", 7),
2408 ("microsecond", 8)):
2409 newargs = args[:]
2410 newargs[i] = newval
2411 expected = cls(*newargs)
2412 got = base.replace(**{name: newval})
2413 self.assertEqual(expected, got)
2414 i += 1
2415
2416 # Out of bounds.
2417 base = cls(2000, 2, 29)
2418 self.assertRaises(ValueError, base.replace, year=2001)
2419
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002420 @support.run_with_tz('EDT4')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002421 def test_astimezone(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002422 dt = self.theclass.now()
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002423 f = FixedOffset(44, "0044")
2424 dt_utc = dt.replace(tzinfo=timezone(timedelta(hours=-4), 'EDT'))
2425 self.assertEqual(dt.astimezone(), dt_utc) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002426 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
2427 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002428 dt_f = dt.replace(tzinfo=f) + timedelta(hours=4, minutes=44)
2429 self.assertEqual(dt.astimezone(f), dt_f) # naive
2430 self.assertEqual(dt.astimezone(tz=f), dt_f) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002431
2432 class Bogus(tzinfo):
2433 def utcoffset(self, dt): return None
2434 def dst(self, dt): return timedelta(0)
2435 bog = Bogus()
2436 self.assertRaises(ValueError, dt.astimezone, bog) # naive
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002437 self.assertEqual(dt.replace(tzinfo=bog).astimezone(f), dt_f)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002438
2439 class AlsoBogus(tzinfo):
2440 def utcoffset(self, dt): return timedelta(0)
2441 def dst(self, dt): return None
2442 alsobog = AlsoBogus()
2443 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
2444
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002445 class Broken(tzinfo):
2446 def utcoffset(self, dt): return 1
2447 def dst(self, dt): return 1
2448 broken = Broken()
2449 dt_broken = dt.replace(tzinfo=broken)
2450 with self.assertRaises(TypeError):
2451 dt_broken.astimezone()
2452
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002453 def test_subclass_datetime(self):
2454
2455 class C(self.theclass):
2456 theAnswer = 42
2457
2458 def __new__(cls, *args, **kws):
2459 temp = kws.copy()
2460 extra = temp.pop('extra')
2461 result = self.theclass.__new__(cls, *args, **temp)
2462 result.extra = extra
2463 return result
2464
2465 def newmeth(self, start):
2466 return start + self.year + self.month + self.second
2467
2468 args = 2003, 4, 14, 12, 13, 41
2469
2470 dt1 = self.theclass(*args)
2471 dt2 = C(*args, **{'extra': 7})
2472
2473 self.assertEqual(dt2.__class__, C)
2474 self.assertEqual(dt2.theAnswer, 42)
2475 self.assertEqual(dt2.extra, 7)
2476 self.assertEqual(dt1.toordinal(), dt2.toordinal())
2477 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
2478 dt1.second - 7)
2479
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002480 def test_subclass_alternate_constructors_datetime(self):
2481 # Test that alternate constructors call the constructor
2482 class DateTimeSubclass(self.theclass):
2483 def __new__(cls, *args, **kwargs):
2484 result = self.theclass.__new__(cls, *args, **kwargs)
2485 result.extra = 7
2486
2487 return result
2488
2489 args = (2003, 4, 14, 12, 30, 15, 123456)
2490 d_isoformat = '2003-04-14T12:30:15.123456' # Equivalent isoformat()
2491 utc_ts = 1050323415.123456 # UTC timestamp
2492
2493 base_d = DateTimeSubclass(*args)
2494 self.assertIsInstance(base_d, DateTimeSubclass)
2495 self.assertEqual(base_d.extra, 7)
2496
2497 # Timestamp depends on time zone, so we'll calculate the equivalent here
2498 ts = base_d.timestamp()
2499
2500 test_cases = [
2501 ('fromtimestamp', (ts,)),
2502 # See https://bugs.python.org/issue32417
2503 # ('fromtimestamp', (ts, timezone.utc)),
2504 ('utcfromtimestamp', (utc_ts,)),
2505 ('fromisoformat', (d_isoformat,)),
2506 ('strptime', (d_isoformat, '%Y-%m-%dT%H:%M:%S.%f')),
2507 ('combine', (date(*args[0:3]), time(*args[3:]))),
2508 ]
2509
2510 for constr_name, constr_args in test_cases:
2511 for base_obj in (DateTimeSubclass, base_d):
2512 # Test both the classmethod and method
2513 with self.subTest(base_obj_type=type(base_obj),
2514 constr_name=constr_name):
2515 constr = getattr(base_obj, constr_name)
2516
2517 dt = constr(*constr_args)
2518
2519 # Test that it creates the right subclass
2520 self.assertIsInstance(dt, DateTimeSubclass)
2521
2522 # Test that it's equal to the base object
2523 self.assertEqual(dt, base_d.replace(tzinfo=None))
2524
2525 # Test that it called the constructor
2526 self.assertEqual(dt.extra, 7)
2527
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002528 def test_fromisoformat_datetime(self):
2529 # Test that isoformat() is reversible
2530 base_dates = [
2531 (1, 1, 1),
2532 (1900, 1, 1),
2533 (2004, 11, 12),
2534 (2017, 5, 30)
2535 ]
2536
2537 base_times = [
2538 (0, 0, 0, 0),
2539 (0, 0, 0, 241000),
2540 (0, 0, 0, 234567),
2541 (12, 30, 45, 234567)
2542 ]
2543
2544 separators = [' ', 'T']
2545
2546 tzinfos = [None, timezone.utc,
2547 timezone(timedelta(hours=-5)),
2548 timezone(timedelta(hours=2))]
2549
2550 dts = [self.theclass(*date_tuple, *time_tuple, tzinfo=tzi)
2551 for date_tuple in base_dates
2552 for time_tuple in base_times
2553 for tzi in tzinfos]
2554
2555 for dt in dts:
2556 for sep in separators:
2557 dtstr = dt.isoformat(sep=sep)
2558
2559 with self.subTest(dtstr=dtstr):
2560 dt_rt = self.theclass.fromisoformat(dtstr)
2561 self.assertEqual(dt, dt_rt)
2562
2563 def test_fromisoformat_timezone(self):
2564 base_dt = self.theclass(2014, 12, 30, 12, 30, 45, 217456)
2565
2566 tzoffsets = [
2567 timedelta(hours=5), timedelta(hours=2),
2568 timedelta(hours=6, minutes=27),
2569 timedelta(hours=12, minutes=32, seconds=30),
2570 timedelta(hours=2, minutes=4, seconds=9, microseconds=123456)
2571 ]
2572
2573 tzoffsets += [-1 * td for td in tzoffsets]
2574
2575 tzinfos = [None, timezone.utc,
2576 timezone(timedelta(hours=0))]
2577
2578 tzinfos += [timezone(td) for td in tzoffsets]
2579
2580 for tzi in tzinfos:
2581 dt = base_dt.replace(tzinfo=tzi)
2582 dtstr = dt.isoformat()
2583
2584 with self.subTest(tstr=dtstr):
2585 dt_rt = self.theclass.fromisoformat(dtstr)
2586 assert dt == dt_rt, dt_rt
2587
2588 def test_fromisoformat_separators(self):
2589 separators = [
2590 ' ', 'T', '\u007f', # 1-bit widths
2591 '\u0080', 'ʁ', # 2-bit widths
2592 'ᛇ', '時', # 3-bit widths
Paul Ganssle096329f2018-08-23 11:06:20 -04002593 '🐍', # 4-bit widths
2594 '\ud800', # bpo-34454: Surrogate code point
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002595 ]
2596
2597 for sep in separators:
2598 dt = self.theclass(2018, 1, 31, 23, 59, 47, 124789)
2599 dtstr = dt.isoformat(sep=sep)
2600
2601 with self.subTest(dtstr=dtstr):
2602 dt_rt = self.theclass.fromisoformat(dtstr)
2603 self.assertEqual(dt, dt_rt)
2604
2605 def test_fromisoformat_ambiguous(self):
2606 # Test strings like 2018-01-31+12:15 (where +12:15 is not a time zone)
2607 separators = ['+', '-']
2608 for sep in separators:
2609 dt = self.theclass(2018, 1, 31, 12, 15)
2610 dtstr = dt.isoformat(sep=sep)
2611
2612 with self.subTest(dtstr=dtstr):
2613 dt_rt = self.theclass.fromisoformat(dtstr)
2614 self.assertEqual(dt, dt_rt)
2615
2616 def test_fromisoformat_timespecs(self):
2617 datetime_bases = [
2618 (2009, 12, 4, 8, 17, 45, 123456),
2619 (2009, 12, 4, 8, 17, 45, 0)]
2620
2621 tzinfos = [None, timezone.utc,
2622 timezone(timedelta(hours=-5)),
2623 timezone(timedelta(hours=2)),
2624 timezone(timedelta(hours=6, minutes=27))]
2625
2626 timespecs = ['hours', 'minutes', 'seconds',
2627 'milliseconds', 'microseconds']
2628
2629 for ip, ts in enumerate(timespecs):
2630 for tzi in tzinfos:
2631 for dt_tuple in datetime_bases:
2632 if ts == 'milliseconds':
2633 new_microseconds = 1000 * (dt_tuple[6] // 1000)
2634 dt_tuple = dt_tuple[0:6] + (new_microseconds,)
2635
2636 dt = self.theclass(*(dt_tuple[0:(4 + ip)]), tzinfo=tzi)
2637 dtstr = dt.isoformat(timespec=ts)
2638 with self.subTest(dtstr=dtstr):
2639 dt_rt = self.theclass.fromisoformat(dtstr)
2640 self.assertEqual(dt, dt_rt)
2641
2642 def test_fromisoformat_fails_datetime(self):
2643 # Test that fromisoformat() fails on invalid values
2644 bad_strs = [
2645 '', # Empty string
Paul Ganssle096329f2018-08-23 11:06:20 -04002646 '\ud800', # bpo-34454: Surrogate code point
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002647 '2009.04-19T03', # Wrong first separator
2648 '2009-04.19T03', # Wrong second separator
2649 '2009-04-19T0a', # Invalid hours
2650 '2009-04-19T03:1a:45', # Invalid minutes
2651 '2009-04-19T03:15:4a', # Invalid seconds
2652 '2009-04-19T03;15:45', # Bad first time separator
2653 '2009-04-19T03:15;45', # Bad second time separator
2654 '2009-04-19T03:15:4500:00', # Bad time zone separator
2655 '2009-04-19T03:15:45.2345', # Too many digits for milliseconds
2656 '2009-04-19T03:15:45.1234567', # Too many digits for microseconds
2657 '2009-04-19T03:15:45.123456+24:30', # Invalid time zone offset
2658 '2009-04-19T03:15:45.123456-24:30', # Invalid negative offset
2659 '2009-04-10ᛇᛇᛇᛇᛇ12:15', # Too many unicode separators
Paul Ganssle096329f2018-08-23 11:06:20 -04002660 '2009-04\ud80010T12:15', # Surrogate char in date
2661 '2009-04-10T12\ud80015', # Surrogate char in time
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002662 '2009-04-19T1', # Incomplete hours
2663 '2009-04-19T12:3', # Incomplete minutes
2664 '2009-04-19T12:30:4', # Incomplete seconds
2665 '2009-04-19T12:', # Ends with time separator
2666 '2009-04-19T12:30:', # Ends with time separator
2667 '2009-04-19T12:30:45.', # Ends with time separator
2668 '2009-04-19T12:30:45.123456+', # Ends with timzone separator
2669 '2009-04-19T12:30:45.123456-', # Ends with timzone separator
2670 '2009-04-19T12:30:45.123456-05:00a', # Extra text
2671 '2009-04-19T12:30:45.123-05:00a', # Extra text
2672 '2009-04-19T12:30:45-05:00a', # Extra text
2673 ]
2674
2675 for bad_str in bad_strs:
2676 with self.subTest(bad_str=bad_str):
2677 with self.assertRaises(ValueError):
2678 self.theclass.fromisoformat(bad_str)
2679
Paul Ganssle3df85402018-10-22 12:32:52 -04002680 def test_fromisoformat_fails_surrogate(self):
2681 # Test that when fromisoformat() fails with a surrogate character as
2682 # the separator, the error message contains the original string
2683 dtstr = "2018-01-03\ud80001:0113"
2684
2685 with self.assertRaisesRegex(ValueError, re.escape(repr(dtstr))):
2686 self.theclass.fromisoformat(dtstr)
2687
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002688 def test_fromisoformat_utc(self):
2689 dt_str = '2014-04-19T13:21:13+00:00'
2690 dt = self.theclass.fromisoformat(dt_str)
2691
2692 self.assertIs(dt.tzinfo, timezone.utc)
2693
2694 def test_fromisoformat_subclass(self):
2695 class DateTimeSubclass(self.theclass):
2696 pass
2697
2698 dt = DateTimeSubclass(2014, 12, 14, 9, 30, 45, 457390,
2699 tzinfo=timezone(timedelta(hours=10, minutes=45)))
2700
2701 dt_rt = DateTimeSubclass.fromisoformat(dt.isoformat())
2702
2703 self.assertEqual(dt, dt_rt)
2704 self.assertIsInstance(dt_rt, DateTimeSubclass)
2705
2706
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002707class TestSubclassDateTime(TestDateTime):
2708 theclass = SubclassDatetime
2709 # Override tests not designed for subclass
Zachary Ware9fe6d862013-12-08 00:20:35 -06002710 @unittest.skip('not appropriate for subclasses')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002711 def test_roundtrip(self):
2712 pass
2713
2714class SubclassTime(time):
2715 sub_var = 1
2716
2717class TestTime(HarmlessMixedComparison, unittest.TestCase):
2718
2719 theclass = time
2720
2721 def test_basic_attributes(self):
2722 t = self.theclass(12, 0)
2723 self.assertEqual(t.hour, 12)
2724 self.assertEqual(t.minute, 0)
2725 self.assertEqual(t.second, 0)
2726 self.assertEqual(t.microsecond, 0)
2727
2728 def test_basic_attributes_nonzero(self):
2729 # Make sure all attributes are non-zero so bugs in
2730 # bit-shifting access show up.
2731 t = self.theclass(12, 59, 59, 8000)
2732 self.assertEqual(t.hour, 12)
2733 self.assertEqual(t.minute, 59)
2734 self.assertEqual(t.second, 59)
2735 self.assertEqual(t.microsecond, 8000)
2736
2737 def test_roundtrip(self):
2738 t = self.theclass(1, 2, 3, 4)
2739
2740 # Verify t -> string -> time identity.
2741 s = repr(t)
2742 self.assertTrue(s.startswith('datetime.'))
2743 s = s[9:]
2744 t2 = eval(s)
2745 self.assertEqual(t, t2)
2746
2747 # Verify identity via reconstructing from pieces.
2748 t2 = self.theclass(t.hour, t.minute, t.second,
2749 t.microsecond)
2750 self.assertEqual(t, t2)
2751
2752 def test_comparing(self):
2753 args = [1, 2, 3, 4]
2754 t1 = self.theclass(*args)
2755 t2 = self.theclass(*args)
2756 self.assertEqual(t1, t2)
2757 self.assertTrue(t1 <= t2)
2758 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002759 self.assertFalse(t1 != t2)
2760 self.assertFalse(t1 < t2)
2761 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002762
2763 for i in range(len(args)):
2764 newargs = args[:]
2765 newargs[i] = args[i] + 1
2766 t2 = self.theclass(*newargs) # this is larger than t1
2767 self.assertTrue(t1 < t2)
2768 self.assertTrue(t2 > t1)
2769 self.assertTrue(t1 <= t2)
2770 self.assertTrue(t2 >= t1)
2771 self.assertTrue(t1 != t2)
2772 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002773 self.assertFalse(t1 == t2)
2774 self.assertFalse(t2 == t1)
2775 self.assertFalse(t1 > t2)
2776 self.assertFalse(t2 < t1)
2777 self.assertFalse(t1 >= t2)
2778 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002779
2780 for badarg in OTHERSTUFF:
2781 self.assertEqual(t1 == badarg, False)
2782 self.assertEqual(t1 != badarg, True)
2783 self.assertEqual(badarg == t1, False)
2784 self.assertEqual(badarg != t1, True)
2785
2786 self.assertRaises(TypeError, lambda: t1 <= badarg)
2787 self.assertRaises(TypeError, lambda: t1 < badarg)
2788 self.assertRaises(TypeError, lambda: t1 > badarg)
2789 self.assertRaises(TypeError, lambda: t1 >= badarg)
2790 self.assertRaises(TypeError, lambda: badarg <= t1)
2791 self.assertRaises(TypeError, lambda: badarg < t1)
2792 self.assertRaises(TypeError, lambda: badarg > t1)
2793 self.assertRaises(TypeError, lambda: badarg >= t1)
2794
2795 def test_bad_constructor_arguments(self):
2796 # bad hours
2797 self.theclass(0, 0) # no exception
2798 self.theclass(23, 0) # no exception
2799 self.assertRaises(ValueError, self.theclass, -1, 0)
2800 self.assertRaises(ValueError, self.theclass, 24, 0)
2801 # bad minutes
2802 self.theclass(23, 0) # no exception
2803 self.theclass(23, 59) # no exception
2804 self.assertRaises(ValueError, self.theclass, 23, -1)
2805 self.assertRaises(ValueError, self.theclass, 23, 60)
2806 # bad seconds
2807 self.theclass(23, 59, 0) # no exception
2808 self.theclass(23, 59, 59) # no exception
2809 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
2810 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
2811 # bad microseconds
2812 self.theclass(23, 59, 59, 0) # no exception
2813 self.theclass(23, 59, 59, 999999) # no exception
2814 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
2815 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
2816
2817 def test_hash_equality(self):
2818 d = self.theclass(23, 30, 17)
2819 e = self.theclass(23, 30, 17)
2820 self.assertEqual(d, e)
2821 self.assertEqual(hash(d), hash(e))
2822
2823 dic = {d: 1}
2824 dic[e] = 2
2825 self.assertEqual(len(dic), 1)
2826 self.assertEqual(dic[d], 2)
2827 self.assertEqual(dic[e], 2)
2828
2829 d = self.theclass(0, 5, 17)
2830 e = self.theclass(0, 5, 17)
2831 self.assertEqual(d, e)
2832 self.assertEqual(hash(d), hash(e))
2833
2834 dic = {d: 1}
2835 dic[e] = 2
2836 self.assertEqual(len(dic), 1)
2837 self.assertEqual(dic[d], 2)
2838 self.assertEqual(dic[e], 2)
2839
2840 def test_isoformat(self):
2841 t = self.theclass(4, 5, 1, 123)
2842 self.assertEqual(t.isoformat(), "04:05:01.000123")
2843 self.assertEqual(t.isoformat(), str(t))
2844
2845 t = self.theclass()
2846 self.assertEqual(t.isoformat(), "00:00:00")
2847 self.assertEqual(t.isoformat(), str(t))
2848
2849 t = self.theclass(microsecond=1)
2850 self.assertEqual(t.isoformat(), "00:00:00.000001")
2851 self.assertEqual(t.isoformat(), str(t))
2852
2853 t = self.theclass(microsecond=10)
2854 self.assertEqual(t.isoformat(), "00:00:00.000010")
2855 self.assertEqual(t.isoformat(), str(t))
2856
2857 t = self.theclass(microsecond=100)
2858 self.assertEqual(t.isoformat(), "00:00:00.000100")
2859 self.assertEqual(t.isoformat(), str(t))
2860
2861 t = self.theclass(microsecond=1000)
2862 self.assertEqual(t.isoformat(), "00:00:00.001000")
2863 self.assertEqual(t.isoformat(), str(t))
2864
2865 t = self.theclass(microsecond=10000)
2866 self.assertEqual(t.isoformat(), "00:00:00.010000")
2867 self.assertEqual(t.isoformat(), str(t))
2868
2869 t = self.theclass(microsecond=100000)
2870 self.assertEqual(t.isoformat(), "00:00:00.100000")
2871 self.assertEqual(t.isoformat(), str(t))
2872
Alexander Belopolskya2998a62016-03-06 14:58:43 -05002873 t = self.theclass(hour=12, minute=34, second=56, microsecond=123456)
2874 self.assertEqual(t.isoformat(timespec='hours'), "12")
2875 self.assertEqual(t.isoformat(timespec='minutes'), "12:34")
2876 self.assertEqual(t.isoformat(timespec='seconds'), "12:34:56")
2877 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.123")
2878 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.123456")
2879 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56.123456")
2880 self.assertRaises(ValueError, t.isoformat, timespec='monkey')
2881
2882 t = self.theclass(hour=12, minute=34, second=56, microsecond=999500)
2883 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.999")
2884
2885 t = self.theclass(hour=12, minute=34, second=56, microsecond=0)
2886 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.000")
2887 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.000000")
2888 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56")
2889
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002890 def test_isoformat_timezone(self):
2891 tzoffsets = [
2892 ('05:00', timedelta(hours=5)),
2893 ('02:00', timedelta(hours=2)),
2894 ('06:27', timedelta(hours=6, minutes=27)),
2895 ('12:32:30', timedelta(hours=12, minutes=32, seconds=30)),
2896 ('02:04:09.123456', timedelta(hours=2, minutes=4, seconds=9, microseconds=123456))
2897 ]
2898
2899 tzinfos = [
2900 ('', None),
2901 ('+00:00', timezone.utc),
2902 ('+00:00', timezone(timedelta(0))),
2903 ]
2904
2905 tzinfos += [
2906 (prefix + expected, timezone(sign * td))
2907 for expected, td in tzoffsets
2908 for prefix, sign in [('-', -1), ('+', 1)]
2909 ]
2910
2911 t_base = self.theclass(12, 37, 9)
2912 exp_base = '12:37:09'
2913
2914 for exp_tz, tzi in tzinfos:
2915 t = t_base.replace(tzinfo=tzi)
2916 exp = exp_base + exp_tz
2917 with self.subTest(tzi=tzi):
2918 assert t.isoformat() == exp
2919
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002920 def test_1653736(self):
2921 # verify it doesn't accept extra keyword arguments
2922 t = self.theclass(second=1)
2923 self.assertRaises(TypeError, t.isoformat, foo=3)
2924
2925 def test_strftime(self):
2926 t = self.theclass(1, 2, 3, 4)
2927 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
2928 # A naive object replaces %z and %Z with empty strings.
2929 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
2930
2931 def test_format(self):
2932 t = self.theclass(1, 2, 3, 4)
2933 self.assertEqual(t.__format__(''), str(t))
2934
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02002935 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002936 t.__format__(123)
2937
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002938 # check that a derived class's __str__() gets called
2939 class A(self.theclass):
2940 def __str__(self):
2941 return 'A'
2942 a = A(1, 2, 3, 4)
2943 self.assertEqual(a.__format__(''), 'A')
2944
2945 # check that a derived class's strftime gets called
2946 class B(self.theclass):
2947 def strftime(self, format_spec):
2948 return 'B'
2949 b = B(1, 2, 3, 4)
2950 self.assertEqual(b.__format__(''), str(t))
2951
2952 for fmt in ['%H %M %S',
2953 ]:
2954 self.assertEqual(t.__format__(fmt), t.strftime(fmt))
2955 self.assertEqual(a.__format__(fmt), t.strftime(fmt))
2956 self.assertEqual(b.__format__(fmt), 'B')
2957
2958 def test_str(self):
2959 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
2960 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
2961 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
2962 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
2963 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
2964
2965 def test_repr(self):
2966 name = 'datetime.' + self.theclass.__name__
2967 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
2968 "%s(1, 2, 3, 4)" % name)
2969 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
2970 "%s(10, 2, 3, 4000)" % name)
2971 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
2972 "%s(0, 2, 3, 400000)" % name)
2973 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
2974 "%s(12, 2, 3)" % name)
2975 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
2976 "%s(23, 15)" % name)
2977
2978 def test_resolution_info(self):
2979 self.assertIsInstance(self.theclass.min, self.theclass)
2980 self.assertIsInstance(self.theclass.max, self.theclass)
2981 self.assertIsInstance(self.theclass.resolution, timedelta)
2982 self.assertTrue(self.theclass.max > self.theclass.min)
2983
2984 def test_pickling(self):
2985 args = 20, 59, 16, 64**2
2986 orig = self.theclass(*args)
2987 for pickler, unpickler, proto in pickle_choices:
2988 green = pickler.dumps(orig, proto)
2989 derived = unpickler.loads(green)
2990 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02002991 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002992
2993 def test_pickling_subclass_time(self):
2994 args = 20, 59, 16, 64**2
2995 orig = SubclassTime(*args)
2996 for pickler, unpickler, proto in pickle_choices:
2997 green = pickler.dumps(orig, proto)
2998 derived = unpickler.loads(green)
2999 self.assertEqual(orig, derived)
3000
3001 def test_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003002 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003003 cls = self.theclass
3004 self.assertTrue(cls(1))
3005 self.assertTrue(cls(0, 1))
3006 self.assertTrue(cls(0, 0, 1))
3007 self.assertTrue(cls(0, 0, 0, 1))
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003008 self.assertTrue(cls(0))
3009 self.assertTrue(cls())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003010
3011 def test_replace(self):
3012 cls = self.theclass
3013 args = [1, 2, 3, 4]
3014 base = cls(*args)
3015 self.assertEqual(base, base.replace())
3016
3017 i = 0
3018 for name, newval in (("hour", 5),
3019 ("minute", 6),
3020 ("second", 7),
3021 ("microsecond", 8)):
3022 newargs = args[:]
3023 newargs[i] = newval
3024 expected = cls(*newargs)
3025 got = base.replace(**{name: newval})
3026 self.assertEqual(expected, got)
3027 i += 1
3028
3029 # Out of bounds.
3030 base = cls(1)
3031 self.assertRaises(ValueError, base.replace, hour=24)
3032 self.assertRaises(ValueError, base.replace, minute=-1)
3033 self.assertRaises(ValueError, base.replace, second=100)
3034 self.assertRaises(ValueError, base.replace, microsecond=1000000)
3035
Paul Ganssle191e9932017-11-09 16:34:29 -05003036 def test_subclass_replace(self):
3037 class TimeSubclass(self.theclass):
3038 pass
3039
3040 ctime = TimeSubclass(12, 30)
3041 self.assertIs(type(ctime.replace(hour=10)), TimeSubclass)
3042
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003043 def test_subclass_time(self):
3044
3045 class C(self.theclass):
3046 theAnswer = 42
3047
3048 def __new__(cls, *args, **kws):
3049 temp = kws.copy()
3050 extra = temp.pop('extra')
3051 result = self.theclass.__new__(cls, *args, **temp)
3052 result.extra = extra
3053 return result
3054
3055 def newmeth(self, start):
3056 return start + self.hour + self.second
3057
3058 args = 4, 5, 6
3059
3060 dt1 = self.theclass(*args)
3061 dt2 = C(*args, **{'extra': 7})
3062
3063 self.assertEqual(dt2.__class__, C)
3064 self.assertEqual(dt2.theAnswer, 42)
3065 self.assertEqual(dt2.extra, 7)
3066 self.assertEqual(dt1.isoformat(), dt2.isoformat())
3067 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
3068
3069 def test_backdoor_resistance(self):
3070 # see TestDate.test_backdoor_resistance().
3071 base = '2:59.0'
3072 for hour_byte in ' ', '9', chr(24), '\xff':
3073 self.assertRaises(TypeError, self.theclass,
3074 hour_byte + base[1:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04003075 # Good bytes, but bad tzinfo:
3076 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
3077 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003078
3079# A mixin for classes with a tzinfo= argument. Subclasses must define
Martin Panter46f50722016-05-26 05:35:26 +00003080# theclass as a class attribute, and theclass(1, 1, 1, tzinfo=whatever)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003081# must be legit (which is true for time and datetime).
3082class TZInfoBase:
3083
3084 def test_argument_passing(self):
3085 cls = self.theclass
3086 # A datetime passes itself on, a time passes None.
3087 class introspective(tzinfo):
3088 def tzname(self, dt): return dt and "real" or "none"
3089 def utcoffset(self, dt):
3090 return timedelta(minutes = dt and 42 or -42)
3091 dst = utcoffset
3092
3093 obj = cls(1, 2, 3, tzinfo=introspective())
3094
3095 expected = cls is time and "none" or "real"
3096 self.assertEqual(obj.tzname(), expected)
3097
3098 expected = timedelta(minutes=(cls is time and -42 or 42))
3099 self.assertEqual(obj.utcoffset(), expected)
3100 self.assertEqual(obj.dst(), expected)
3101
3102 def test_bad_tzinfo_classes(self):
3103 cls = self.theclass
3104 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
3105
3106 class NiceTry(object):
3107 def __init__(self): pass
3108 def utcoffset(self, dt): pass
3109 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
3110
3111 class BetterTry(tzinfo):
3112 def __init__(self): pass
3113 def utcoffset(self, dt): pass
3114 b = BetterTry()
3115 t = cls(1, 1, 1, tzinfo=b)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003116 self.assertIs(t.tzinfo, b)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003117
3118 def test_utc_offset_out_of_bounds(self):
3119 class Edgy(tzinfo):
3120 def __init__(self, offset):
3121 self.offset = timedelta(minutes=offset)
3122 def utcoffset(self, dt):
3123 return self.offset
3124
3125 cls = self.theclass
3126 for offset, legit in ((-1440, False),
3127 (-1439, True),
3128 (1439, True),
3129 (1440, False)):
3130 if cls is time:
3131 t = cls(1, 2, 3, tzinfo=Edgy(offset))
3132 elif cls is datetime:
3133 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
3134 else:
3135 assert 0, "impossible"
3136 if legit:
3137 aofs = abs(offset)
3138 h, m = divmod(aofs, 60)
3139 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
3140 if isinstance(t, datetime):
3141 t = t.timetz()
3142 self.assertEqual(str(t), "01:02:03" + tag)
3143 else:
3144 self.assertRaises(ValueError, str, t)
3145
3146 def test_tzinfo_classes(self):
3147 cls = self.theclass
3148 class C1(tzinfo):
3149 def utcoffset(self, dt): return None
3150 def dst(self, dt): return None
3151 def tzname(self, dt): return None
3152 for t in (cls(1, 1, 1),
3153 cls(1, 1, 1, tzinfo=None),
3154 cls(1, 1, 1, tzinfo=C1())):
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003155 self.assertIsNone(t.utcoffset())
3156 self.assertIsNone(t.dst())
3157 self.assertIsNone(t.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003158
3159 class C3(tzinfo):
3160 def utcoffset(self, dt): return timedelta(minutes=-1439)
3161 def dst(self, dt): return timedelta(minutes=1439)
3162 def tzname(self, dt): return "aname"
3163 t = cls(1, 1, 1, tzinfo=C3())
3164 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
3165 self.assertEqual(t.dst(), timedelta(minutes=1439))
3166 self.assertEqual(t.tzname(), "aname")
3167
3168 # Wrong types.
3169 class C4(tzinfo):
3170 def utcoffset(self, dt): return "aname"
3171 def dst(self, dt): return 7
3172 def tzname(self, dt): return 0
3173 t = cls(1, 1, 1, tzinfo=C4())
3174 self.assertRaises(TypeError, t.utcoffset)
3175 self.assertRaises(TypeError, t.dst)
3176 self.assertRaises(TypeError, t.tzname)
3177
3178 # Offset out of range.
3179 class C6(tzinfo):
3180 def utcoffset(self, dt): return timedelta(hours=-24)
3181 def dst(self, dt): return timedelta(hours=24)
3182 t = cls(1, 1, 1, tzinfo=C6())
3183 self.assertRaises(ValueError, t.utcoffset)
3184 self.assertRaises(ValueError, t.dst)
3185
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04003186 # Not a whole number of seconds.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003187 class C7(tzinfo):
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04003188 def utcoffset(self, dt): return timedelta(microseconds=61)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003189 def dst(self, dt): return timedelta(microseconds=-81)
3190 t = cls(1, 1, 1, tzinfo=C7())
Alexander Belopolsky018d3532017-07-31 10:26:50 -04003191 self.assertEqual(t.utcoffset(), timedelta(microseconds=61))
3192 self.assertEqual(t.dst(), timedelta(microseconds=-81))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003193
3194 def test_aware_compare(self):
3195 cls = self.theclass
3196
3197 # Ensure that utcoffset() gets ignored if the comparands have
3198 # the same tzinfo member.
3199 class OperandDependentOffset(tzinfo):
3200 def utcoffset(self, t):
3201 if t.minute < 10:
3202 # d0 and d1 equal after adjustment
3203 return timedelta(minutes=t.minute)
3204 else:
3205 # d2 off in the weeds
3206 return timedelta(minutes=59)
3207
3208 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
3209 d0 = base.replace(minute=3)
3210 d1 = base.replace(minute=9)
3211 d2 = base.replace(minute=11)
3212 for x in d0, d1, d2:
3213 for y in d0, d1, d2:
3214 for op in lt, le, gt, ge, eq, ne:
3215 got = op(x, y)
3216 expected = op(x.minute, y.minute)
3217 self.assertEqual(got, expected)
3218
3219 # However, if they're different members, uctoffset is not ignored.
3220 # Note that a time can't actually have an operand-depedent offset,
3221 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
3222 # so skip this test for time.
3223 if cls is not time:
3224 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
3225 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
3226 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
3227 for x in d0, d1, d2:
3228 for y in d0, d1, d2:
3229 got = (x > y) - (x < y)
3230 if (x is d0 or x is d1) and (y is d0 or y is d1):
3231 expected = 0
3232 elif x is y is d2:
3233 expected = 0
3234 elif x is d2:
3235 expected = -1
3236 else:
3237 assert y is d2
3238 expected = 1
3239 self.assertEqual(got, expected)
3240
3241
3242# Testing time objects with a non-None tzinfo.
3243class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
3244 theclass = time
3245
3246 def test_empty(self):
3247 t = self.theclass()
3248 self.assertEqual(t.hour, 0)
3249 self.assertEqual(t.minute, 0)
3250 self.assertEqual(t.second, 0)
3251 self.assertEqual(t.microsecond, 0)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003252 self.assertIsNone(t.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003253
3254 def test_zones(self):
3255 est = FixedOffset(-300, "EST", 1)
3256 utc = FixedOffset(0, "UTC", -2)
3257 met = FixedOffset(60, "MET", 3)
3258 t1 = time( 7, 47, tzinfo=est)
3259 t2 = time(12, 47, tzinfo=utc)
3260 t3 = time(13, 47, tzinfo=met)
3261 t4 = time(microsecond=40)
3262 t5 = time(microsecond=40, tzinfo=utc)
3263
3264 self.assertEqual(t1.tzinfo, est)
3265 self.assertEqual(t2.tzinfo, utc)
3266 self.assertEqual(t3.tzinfo, met)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003267 self.assertIsNone(t4.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003268 self.assertEqual(t5.tzinfo, utc)
3269
3270 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
3271 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
3272 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003273 self.assertIsNone(t4.utcoffset())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003274 self.assertRaises(TypeError, t1.utcoffset, "no args")
3275
3276 self.assertEqual(t1.tzname(), "EST")
3277 self.assertEqual(t2.tzname(), "UTC")
3278 self.assertEqual(t3.tzname(), "MET")
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003279 self.assertIsNone(t4.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003280 self.assertRaises(TypeError, t1.tzname, "no args")
3281
3282 self.assertEqual(t1.dst(), timedelta(minutes=1))
3283 self.assertEqual(t2.dst(), timedelta(minutes=-2))
3284 self.assertEqual(t3.dst(), timedelta(minutes=3))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003285 self.assertIsNone(t4.dst())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003286 self.assertRaises(TypeError, t1.dst, "no args")
3287
3288 self.assertEqual(hash(t1), hash(t2))
3289 self.assertEqual(hash(t1), hash(t3))
3290 self.assertEqual(hash(t2), hash(t3))
3291
3292 self.assertEqual(t1, t2)
3293 self.assertEqual(t1, t3)
3294 self.assertEqual(t2, t3)
Alexander Belopolsky08313822012-06-15 20:19:47 -04003295 self.assertNotEqual(t4, t5) # mixed tz-aware & naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003296 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
3297 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
3298
3299 self.assertEqual(str(t1), "07:47:00-05:00")
3300 self.assertEqual(str(t2), "12:47:00+00:00")
3301 self.assertEqual(str(t3), "13:47:00+01:00")
3302 self.assertEqual(str(t4), "00:00:00.000040")
3303 self.assertEqual(str(t5), "00:00:00.000040+00:00")
3304
3305 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
3306 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
3307 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
3308 self.assertEqual(t4.isoformat(), "00:00:00.000040")
3309 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
3310
3311 d = 'datetime.time'
3312 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
3313 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
3314 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
3315 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
3316 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
3317
3318 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
3319 "07:47:00 %Z=EST %z=-0500")
3320 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
3321 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
3322
3323 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
3324 t1 = time(23, 59, tzinfo=yuck)
3325 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
3326 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
3327
3328 # Check that an invalid tzname result raises an exception.
3329 class Badtzname(tzinfo):
Alexander Belopolskye239d232010-12-08 23:31:48 +00003330 tz = 42
3331 def tzname(self, dt): return self.tz
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003332 t = time(2, 3, 4, tzinfo=Badtzname())
3333 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
3334 self.assertRaises(TypeError, t.strftime, "%Z")
3335
Alexander Belopolskye239d232010-12-08 23:31:48 +00003336 # Issue #6697:
Utkarsh Upadhyay287c5592017-07-21 02:14:54 +02003337 if '_Fast' in self.__class__.__name__:
Alexander Belopolskye239d232010-12-08 23:31:48 +00003338 Badtzname.tz = '\ud800'
3339 self.assertRaises(ValueError, t.strftime, "%Z")
3340
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003341 def test_hash_edge_cases(self):
3342 # Offsets that overflow a basic time.
3343 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
3344 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
3345 self.assertEqual(hash(t1), hash(t2))
3346
3347 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
3348 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
3349 self.assertEqual(hash(t1), hash(t2))
3350
3351 def test_pickling(self):
3352 # Try one without a tzinfo.
3353 args = 20, 59, 16, 64**2
3354 orig = self.theclass(*args)
3355 for pickler, unpickler, proto in pickle_choices:
3356 green = pickler.dumps(orig, proto)
3357 derived = unpickler.loads(green)
3358 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003359 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003360
3361 # Try one with a tzinfo.
3362 tinfo = PicklableFixedOffset(-300, 'cookie')
3363 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
3364 for pickler, unpickler, proto in pickle_choices:
3365 green = pickler.dumps(orig, proto)
3366 derived = unpickler.loads(green)
3367 self.assertEqual(orig, derived)
3368 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3369 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3370 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003371 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003372
3373 def test_more_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003374 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003375 cls = self.theclass
3376
3377 t = cls(0, tzinfo=FixedOffset(-300, ""))
3378 self.assertTrue(t)
3379
3380 t = cls(5, tzinfo=FixedOffset(-300, ""))
3381 self.assertTrue(t)
3382
3383 t = cls(5, tzinfo=FixedOffset(300, ""))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003384 self.assertTrue(t)
3385
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003386 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
3387 self.assertTrue(t)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003388
3389 def test_replace(self):
3390 cls = self.theclass
3391 z100 = FixedOffset(100, "+100")
3392 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3393 args = [1, 2, 3, 4, z100]
3394 base = cls(*args)
3395 self.assertEqual(base, base.replace())
3396
3397 i = 0
3398 for name, newval in (("hour", 5),
3399 ("minute", 6),
3400 ("second", 7),
3401 ("microsecond", 8),
3402 ("tzinfo", zm200)):
3403 newargs = args[:]
3404 newargs[i] = newval
3405 expected = cls(*newargs)
3406 got = base.replace(**{name: newval})
3407 self.assertEqual(expected, got)
3408 i += 1
3409
3410 # Ensure we can get rid of a tzinfo.
3411 self.assertEqual(base.tzname(), "+100")
3412 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003413 self.assertIsNone(base2.tzinfo)
3414 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003415
3416 # Ensure we can add one.
3417 base3 = base2.replace(tzinfo=z100)
3418 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003419 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003420
3421 # Out of bounds.
3422 base = cls(1)
3423 self.assertRaises(ValueError, base.replace, hour=24)
3424 self.assertRaises(ValueError, base.replace, minute=-1)
3425 self.assertRaises(ValueError, base.replace, second=100)
3426 self.assertRaises(ValueError, base.replace, microsecond=1000000)
3427
3428 def test_mixed_compare(self):
3429 t1 = time(1, 2, 3)
3430 t2 = time(1, 2, 3)
3431 self.assertEqual(t1, t2)
3432 t2 = t2.replace(tzinfo=None)
3433 self.assertEqual(t1, t2)
3434 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3435 self.assertEqual(t1, t2)
3436 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04003437 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003438
3439 # In time w/ identical tzinfo objects, utcoffset is ignored.
3440 class Varies(tzinfo):
3441 def __init__(self):
3442 self.offset = timedelta(minutes=22)
3443 def utcoffset(self, t):
3444 self.offset += timedelta(minutes=1)
3445 return self.offset
3446
3447 v = Varies()
3448 t1 = t2.replace(tzinfo=v)
3449 t2 = t2.replace(tzinfo=v)
3450 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3451 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3452 self.assertEqual(t1, t2)
3453
3454 # But if they're not identical, it isn't ignored.
3455 t2 = t2.replace(tzinfo=Varies())
3456 self.assertTrue(t1 < t2) # t1's offset counter still going up
3457
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003458 def test_fromisoformat(self):
3459 time_examples = [
3460 (0, 0, 0, 0),
3461 (23, 59, 59, 999999),
3462 ]
3463
3464 hh = (9, 12, 20)
3465 mm = (5, 30)
3466 ss = (4, 45)
3467 usec = (0, 245000, 678901)
3468
3469 time_examples += list(itertools.product(hh, mm, ss, usec))
3470
3471 tzinfos = [None, timezone.utc,
3472 timezone(timedelta(hours=2)),
3473 timezone(timedelta(hours=6, minutes=27))]
3474
3475 for ttup in time_examples:
3476 for tzi in tzinfos:
3477 t = self.theclass(*ttup, tzinfo=tzi)
3478 tstr = t.isoformat()
3479
3480 with self.subTest(tstr=tstr):
3481 t_rt = self.theclass.fromisoformat(tstr)
3482 self.assertEqual(t, t_rt)
3483
3484 def test_fromisoformat_timezone(self):
3485 base_time = self.theclass(12, 30, 45, 217456)
3486
3487 tzoffsets = [
3488 timedelta(hours=5), timedelta(hours=2),
3489 timedelta(hours=6, minutes=27),
3490 timedelta(hours=12, minutes=32, seconds=30),
3491 timedelta(hours=2, minutes=4, seconds=9, microseconds=123456)
3492 ]
3493
3494 tzoffsets += [-1 * td for td in tzoffsets]
3495
3496 tzinfos = [None, timezone.utc,
3497 timezone(timedelta(hours=0))]
3498
3499 tzinfos += [timezone(td) for td in tzoffsets]
3500
3501 for tzi in tzinfos:
3502 t = base_time.replace(tzinfo=tzi)
3503 tstr = t.isoformat()
3504
3505 with self.subTest(tstr=tstr):
3506 t_rt = self.theclass.fromisoformat(tstr)
3507 assert t == t_rt, t_rt
3508
3509 def test_fromisoformat_timespecs(self):
3510 time_bases = [
3511 (8, 17, 45, 123456),
3512 (8, 17, 45, 0)
3513 ]
3514
3515 tzinfos = [None, timezone.utc,
3516 timezone(timedelta(hours=-5)),
3517 timezone(timedelta(hours=2)),
3518 timezone(timedelta(hours=6, minutes=27))]
3519
3520 timespecs = ['hours', 'minutes', 'seconds',
3521 'milliseconds', 'microseconds']
3522
3523 for ip, ts in enumerate(timespecs):
3524 for tzi in tzinfos:
3525 for t_tuple in time_bases:
3526 if ts == 'milliseconds':
3527 new_microseconds = 1000 * (t_tuple[-1] // 1000)
3528 t_tuple = t_tuple[0:-1] + (new_microseconds,)
3529
3530 t = self.theclass(*(t_tuple[0:(1 + ip)]), tzinfo=tzi)
3531 tstr = t.isoformat(timespec=ts)
3532 with self.subTest(tstr=tstr):
3533 t_rt = self.theclass.fromisoformat(tstr)
3534 self.assertEqual(t, t_rt)
3535
3536 def test_fromisoformat_fails(self):
3537 bad_strs = [
3538 '', # Empty string
Paul Ganssle096329f2018-08-23 11:06:20 -04003539 '12\ud80000', # Invalid separator - surrogate char
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003540 '12:', # Ends on a separator
3541 '12:30:', # Ends on a separator
3542 '12:30:15.', # Ends on a separator
3543 '1', # Incomplete hours
3544 '12:3', # Incomplete minutes
3545 '12:30:1', # Incomplete seconds
3546 '1a:30:45.334034', # Invalid character in hours
3547 '12:a0:45.334034', # Invalid character in minutes
3548 '12:30:a5.334034', # Invalid character in seconds
3549 '12:30:45.1234', # Too many digits for milliseconds
3550 '12:30:45.1234567', # Too many digits for microseconds
3551 '12:30:45.123456+24:30', # Invalid time zone offset
3552 '12:30:45.123456-24:30', # Invalid negative offset
3553 '12:30:45', # Uses full-width unicode colons
3554 '12:30:45․123456', # Uses \u2024 in place of decimal point
3555 '12:30:45a', # Extra at tend of basic time
3556 '12:30:45.123a', # Extra at end of millisecond time
3557 '12:30:45.123456a', # Extra at end of microsecond time
3558 '12:30:45.123456+12:00:30a', # Extra at end of full time
3559 ]
3560
3561 for bad_str in bad_strs:
3562 with self.subTest(bad_str=bad_str):
3563 with self.assertRaises(ValueError):
3564 self.theclass.fromisoformat(bad_str)
3565
3566 def test_fromisoformat_fails_typeerror(self):
3567 # Test the fromisoformat fails when passed the wrong type
3568 import io
3569
3570 bad_types = [b'12:30:45', None, io.StringIO('12:30:45')]
3571
3572 for bad_type in bad_types:
3573 with self.assertRaises(TypeError):
3574 self.theclass.fromisoformat(bad_type)
3575
3576 def test_fromisoformat_subclass(self):
3577 class TimeSubclass(self.theclass):
3578 pass
3579
3580 tsc = TimeSubclass(12, 14, 45, 203745, tzinfo=timezone.utc)
3581 tsc_rt = TimeSubclass.fromisoformat(tsc.isoformat())
3582
3583 self.assertEqual(tsc, tsc_rt)
3584 self.assertIsInstance(tsc_rt, TimeSubclass)
3585
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003586 def test_subclass_timetz(self):
3587
3588 class C(self.theclass):
3589 theAnswer = 42
3590
3591 def __new__(cls, *args, **kws):
3592 temp = kws.copy()
3593 extra = temp.pop('extra')
3594 result = self.theclass.__new__(cls, *args, **temp)
3595 result.extra = extra
3596 return result
3597
3598 def newmeth(self, start):
3599 return start + self.hour + self.second
3600
3601 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3602
3603 dt1 = self.theclass(*args)
3604 dt2 = C(*args, **{'extra': 7})
3605
3606 self.assertEqual(dt2.__class__, C)
3607 self.assertEqual(dt2.theAnswer, 42)
3608 self.assertEqual(dt2.extra, 7)
3609 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3610 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
3611
3612
3613# Testing datetime objects with a non-None tzinfo.
3614
3615class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
3616 theclass = datetime
3617
3618 def test_trivial(self):
3619 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
3620 self.assertEqual(dt.year, 1)
3621 self.assertEqual(dt.month, 2)
3622 self.assertEqual(dt.day, 3)
3623 self.assertEqual(dt.hour, 4)
3624 self.assertEqual(dt.minute, 5)
3625 self.assertEqual(dt.second, 6)
3626 self.assertEqual(dt.microsecond, 7)
3627 self.assertEqual(dt.tzinfo, None)
3628
3629 def test_even_more_compare(self):
3630 # The test_compare() and test_more_compare() inherited from TestDate
3631 # and TestDateTime covered non-tzinfo cases.
3632
3633 # Smallest possible after UTC adjustment.
3634 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
3635 # Largest possible after UTC adjustment.
3636 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3637 tzinfo=FixedOffset(-1439, ""))
3638
3639 # Make sure those compare correctly, and w/o overflow.
3640 self.assertTrue(t1 < t2)
3641 self.assertTrue(t1 != t2)
3642 self.assertTrue(t2 > t1)
3643
3644 self.assertEqual(t1, t1)
3645 self.assertEqual(t2, t2)
3646
3647 # Equal afer adjustment.
3648 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
3649 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
3650 self.assertEqual(t1, t2)
3651
3652 # Change t1 not to subtract a minute, and t1 should be larger.
3653 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
3654 self.assertTrue(t1 > t2)
3655
3656 # Change t1 to subtract 2 minutes, and t1 should be smaller.
3657 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
3658 self.assertTrue(t1 < t2)
3659
3660 # Back to the original t1, but make seconds resolve it.
3661 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3662 second=1)
3663 self.assertTrue(t1 > t2)
3664
3665 # Likewise, but make microseconds resolve it.
3666 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3667 microsecond=1)
3668 self.assertTrue(t1 > t2)
3669
Alexander Belopolsky08313822012-06-15 20:19:47 -04003670 # Make t2 naive and it should differ.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003671 t2 = self.theclass.min
Alexander Belopolsky08313822012-06-15 20:19:47 -04003672 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003673 self.assertEqual(t2, t2)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04003674 # and > comparison should fail
3675 with self.assertRaises(TypeError):
3676 t1 > t2
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003677
3678 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
3679 class Naive(tzinfo):
3680 def utcoffset(self, dt): return None
3681 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
Alexander Belopolsky08313822012-06-15 20:19:47 -04003682 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003683 self.assertEqual(t2, t2)
3684
3685 # OTOH, it's OK to compare two of these mixing the two ways of being
3686 # naive.
3687 t1 = self.theclass(5, 6, 7)
3688 self.assertEqual(t1, t2)
3689
3690 # Try a bogus uctoffset.
3691 class Bogus(tzinfo):
3692 def utcoffset(self, dt):
3693 return timedelta(minutes=1440) # out of bounds
3694 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
3695 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
3696 self.assertRaises(ValueError, lambda: t1 == t2)
3697
3698 def test_pickling(self):
3699 # Try one without a tzinfo.
3700 args = 6, 7, 23, 20, 59, 1, 64**2
3701 orig = self.theclass(*args)
3702 for pickler, unpickler, proto in pickle_choices:
3703 green = pickler.dumps(orig, proto)
3704 derived = unpickler.loads(green)
3705 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003706 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003707
3708 # Try one with a tzinfo.
3709 tinfo = PicklableFixedOffset(-300, 'cookie')
3710 orig = self.theclass(*args, **{'tzinfo': tinfo})
3711 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
3712 for pickler, unpickler, proto in pickle_choices:
3713 green = pickler.dumps(orig, proto)
3714 derived = unpickler.loads(green)
3715 self.assertEqual(orig, derived)
3716 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3717 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3718 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003719 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003720
3721 def test_extreme_hashes(self):
3722 # If an attempt is made to hash these via subtracting the offset
3723 # then hashing a datetime object, OverflowError results. The
3724 # Python implementation used to blow up here.
3725 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
3726 hash(t)
3727 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3728 tzinfo=FixedOffset(-1439, ""))
3729 hash(t)
3730
3731 # OTOH, an OOB offset should blow up.
3732 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
3733 self.assertRaises(ValueError, hash, t)
3734
3735 def test_zones(self):
3736 est = FixedOffset(-300, "EST")
3737 utc = FixedOffset(0, "UTC")
3738 met = FixedOffset(60, "MET")
3739 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
3740 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
3741 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
3742 self.assertEqual(t1.tzinfo, est)
3743 self.assertEqual(t2.tzinfo, utc)
3744 self.assertEqual(t3.tzinfo, met)
3745 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
3746 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
3747 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
3748 self.assertEqual(t1.tzname(), "EST")
3749 self.assertEqual(t2.tzname(), "UTC")
3750 self.assertEqual(t3.tzname(), "MET")
3751 self.assertEqual(hash(t1), hash(t2))
3752 self.assertEqual(hash(t1), hash(t3))
3753 self.assertEqual(hash(t2), hash(t3))
3754 self.assertEqual(t1, t2)
3755 self.assertEqual(t1, t3)
3756 self.assertEqual(t2, t3)
3757 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
3758 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
3759 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
3760 d = 'datetime.datetime(2002, 3, 19, '
3761 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
3762 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
3763 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
3764
3765 def test_combine(self):
3766 met = FixedOffset(60, "MET")
3767 d = date(2002, 3, 4)
3768 tz = time(18, 45, 3, 1234, tzinfo=met)
3769 dt = datetime.combine(d, tz)
3770 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
3771 tzinfo=met))
3772
3773 def test_extract(self):
3774 met = FixedOffset(60, "MET")
3775 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
3776 self.assertEqual(dt.date(), date(2002, 3, 4))
3777 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
3778 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
3779
3780 def test_tz_aware_arithmetic(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003781 now = self.theclass.now()
3782 tz55 = FixedOffset(-330, "west 5:30")
3783 timeaware = now.time().replace(tzinfo=tz55)
3784 nowaware = self.theclass.combine(now.date(), timeaware)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003785 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003786 self.assertEqual(nowaware.timetz(), timeaware)
3787
3788 # Can't mix aware and non-aware.
3789 self.assertRaises(TypeError, lambda: now - nowaware)
3790 self.assertRaises(TypeError, lambda: nowaware - now)
3791
3792 # And adding datetime's doesn't make sense, aware or not.
3793 self.assertRaises(TypeError, lambda: now + nowaware)
3794 self.assertRaises(TypeError, lambda: nowaware + now)
3795 self.assertRaises(TypeError, lambda: nowaware + nowaware)
3796
3797 # Subtracting should yield 0.
3798 self.assertEqual(now - now, timedelta(0))
3799 self.assertEqual(nowaware - nowaware, timedelta(0))
3800
3801 # Adding a delta should preserve tzinfo.
3802 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
3803 nowawareplus = nowaware + delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003804 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003805 nowawareplus2 = delta + nowaware
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003806 self.assertIs(nowawareplus2.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003807 self.assertEqual(nowawareplus, nowawareplus2)
3808
3809 # that - delta should be what we started with, and that - what we
3810 # started with should be delta.
3811 diff = nowawareplus - delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003812 self.assertIs(diff.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003813 self.assertEqual(nowaware, diff)
3814 self.assertRaises(TypeError, lambda: delta - nowawareplus)
3815 self.assertEqual(nowawareplus - nowaware, delta)
3816
3817 # Make up a random timezone.
3818 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
3819 # Attach it to nowawareplus.
3820 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003821 self.assertIs(nowawareplus.tzinfo, tzr)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003822 # Make sure the difference takes the timezone adjustments into account.
3823 got = nowaware - nowawareplus
3824 # Expected: (nowaware base - nowaware offset) -
3825 # (nowawareplus base - nowawareplus offset) =
3826 # (nowaware base - nowawareplus base) +
3827 # (nowawareplus offset - nowaware offset) =
3828 # -delta + nowawareplus offset - nowaware offset
3829 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
3830 self.assertEqual(got, expected)
3831
3832 # Try max possible difference.
3833 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
3834 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3835 tzinfo=FixedOffset(-1439, "max"))
3836 maxdiff = max - min
3837 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
3838 timedelta(minutes=2*1439))
3839 # Different tzinfo, but the same offset
3840 tza = timezone(HOUR, 'A')
3841 tzb = timezone(HOUR, 'B')
3842 delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
3843 self.assertEqual(delta, self.theclass.min - self.theclass.max)
3844
3845 def test_tzinfo_now(self):
3846 meth = self.theclass.now
3847 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3848 base = meth()
3849 # Try with and without naming the keyword.
3850 off42 = FixedOffset(42, "42")
3851 another = meth(off42)
3852 again = meth(tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003853 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003854 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3855 # Bad argument with and w/o naming the keyword.
3856 self.assertRaises(TypeError, meth, 16)
3857 self.assertRaises(TypeError, meth, tzinfo=16)
3858 # Bad keyword name.
3859 self.assertRaises(TypeError, meth, tinfo=off42)
3860 # Too many args.
3861 self.assertRaises(TypeError, meth, off42, off42)
3862
3863 # We don't know which time zone we're in, and don't have a tzinfo
3864 # class to represent it, so seeing whether a tz argument actually
3865 # does a conversion is tricky.
3866 utc = FixedOffset(0, "utc", 0)
3867 for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
3868 timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
3869 for dummy in range(3):
3870 now = datetime.now(weirdtz)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003871 self.assertIs(now.tzinfo, weirdtz)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003872 utcnow = datetime.utcnow().replace(tzinfo=utc)
3873 now2 = utcnow.astimezone(weirdtz)
3874 if abs(now - now2) < timedelta(seconds=30):
3875 break
3876 # Else the code is broken, or more than 30 seconds passed between
3877 # calls; assuming the latter, just try again.
3878 else:
3879 # Three strikes and we're out.
3880 self.fail("utcnow(), now(tz), or astimezone() may be broken")
3881
3882 def test_tzinfo_fromtimestamp(self):
3883 import time
3884 meth = self.theclass.fromtimestamp
3885 ts = time.time()
3886 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3887 base = meth(ts)
3888 # Try with and without naming the keyword.
3889 off42 = FixedOffset(42, "42")
3890 another = meth(ts, off42)
3891 again = meth(ts, tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003892 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003893 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3894 # Bad argument with and w/o naming the keyword.
3895 self.assertRaises(TypeError, meth, ts, 16)
3896 self.assertRaises(TypeError, meth, ts, tzinfo=16)
3897 # Bad keyword name.
3898 self.assertRaises(TypeError, meth, ts, tinfo=off42)
3899 # Too many args.
3900 self.assertRaises(TypeError, meth, ts, off42, off42)
3901 # Too few args.
3902 self.assertRaises(TypeError, meth)
3903
3904 # Try to make sure tz= actually does some conversion.
3905 timestamp = 1000000000
3906 utcdatetime = datetime.utcfromtimestamp(timestamp)
3907 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
3908 # But on some flavor of Mac, it's nowhere near that. So we can't have
3909 # any idea here what time that actually is, we can only test that
3910 # relative changes match.
3911 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
3912 tz = FixedOffset(utcoffset, "tz", 0)
3913 expected = utcdatetime + utcoffset
3914 got = datetime.fromtimestamp(timestamp, tz)
3915 self.assertEqual(expected, got.replace(tzinfo=None))
3916
3917 def test_tzinfo_utcnow(self):
3918 meth = self.theclass.utcnow
3919 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3920 base = meth()
3921 # Try with and without naming the keyword; for whatever reason,
3922 # utcnow() doesn't accept a tzinfo argument.
3923 off42 = FixedOffset(42, "42")
3924 self.assertRaises(TypeError, meth, off42)
3925 self.assertRaises(TypeError, meth, tzinfo=off42)
3926
3927 def test_tzinfo_utcfromtimestamp(self):
3928 import time
3929 meth = self.theclass.utcfromtimestamp
3930 ts = time.time()
3931 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3932 base = meth(ts)
3933 # Try with and without naming the keyword; for whatever reason,
3934 # utcfromtimestamp() doesn't accept a tzinfo argument.
3935 off42 = FixedOffset(42, "42")
3936 self.assertRaises(TypeError, meth, ts, off42)
3937 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
3938
3939 def test_tzinfo_timetuple(self):
3940 # TestDateTime tested most of this. datetime adds a twist to the
3941 # DST flag.
3942 class DST(tzinfo):
3943 def __init__(self, dstvalue):
3944 if isinstance(dstvalue, int):
3945 dstvalue = timedelta(minutes=dstvalue)
3946 self.dstvalue = dstvalue
3947 def dst(self, dt):
3948 return self.dstvalue
3949
3950 cls = self.theclass
3951 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
3952 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
3953 t = d.timetuple()
3954 self.assertEqual(1, t.tm_year)
3955 self.assertEqual(1, t.tm_mon)
3956 self.assertEqual(1, t.tm_mday)
3957 self.assertEqual(10, t.tm_hour)
3958 self.assertEqual(20, t.tm_min)
3959 self.assertEqual(30, t.tm_sec)
3960 self.assertEqual(0, t.tm_wday)
3961 self.assertEqual(1, t.tm_yday)
3962 self.assertEqual(flag, t.tm_isdst)
3963
3964 # dst() returns wrong type.
3965 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
3966
3967 # dst() at the edge.
3968 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
3969 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
3970
3971 # dst() out of range.
3972 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
3973 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
3974
3975 def test_utctimetuple(self):
3976 class DST(tzinfo):
3977 def __init__(self, dstvalue=0):
3978 if isinstance(dstvalue, int):
3979 dstvalue = timedelta(minutes=dstvalue)
3980 self.dstvalue = dstvalue
3981 def dst(self, dt):
3982 return self.dstvalue
3983
3984 cls = self.theclass
3985 # This can't work: DST didn't implement utcoffset.
3986 self.assertRaises(NotImplementedError,
3987 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
3988
3989 class UOFS(DST):
3990 def __init__(self, uofs, dofs=None):
3991 DST.__init__(self, dofs)
3992 self.uofs = timedelta(minutes=uofs)
3993 def utcoffset(self, dt):
3994 return self.uofs
3995
3996 for dstvalue in -33, 33, 0, None:
3997 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
3998 t = d.utctimetuple()
3999 self.assertEqual(d.year, t.tm_year)
4000 self.assertEqual(d.month, t.tm_mon)
4001 self.assertEqual(d.day, t.tm_mday)
4002 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
4003 self.assertEqual(13, t.tm_min)
4004 self.assertEqual(d.second, t.tm_sec)
4005 self.assertEqual(d.weekday(), t.tm_wday)
4006 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
4007 t.tm_yday)
4008 # Ensure tm_isdst is 0 regardless of what dst() says: DST
4009 # is never in effect for a UTC time.
4010 self.assertEqual(0, t.tm_isdst)
4011
4012 # For naive datetime, utctimetuple == timetuple except for isdst
4013 d = cls(1, 2, 3, 10, 20, 30, 40)
4014 t = d.utctimetuple()
4015 self.assertEqual(t[:-1], d.timetuple()[:-1])
4016 self.assertEqual(0, t.tm_isdst)
4017 # Same if utcoffset is None
4018 class NOFS(DST):
4019 def utcoffset(self, dt):
4020 return None
4021 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
4022 t = d.utctimetuple()
4023 self.assertEqual(t[:-1], d.timetuple()[:-1])
4024 self.assertEqual(0, t.tm_isdst)
4025 # Check that bad tzinfo is detected
4026 class BOFS(DST):
4027 def utcoffset(self, dt):
4028 return "EST"
4029 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
4030 self.assertRaises(TypeError, d.utctimetuple)
4031
4032 # Check that utctimetuple() is the same as
4033 # astimezone(utc).timetuple()
4034 d = cls(2010, 11, 13, 14, 15, 16, 171819)
4035 for tz in [timezone.min, timezone.utc, timezone.max]:
4036 dtz = d.replace(tzinfo=tz)
4037 self.assertEqual(dtz.utctimetuple()[:-1],
4038 dtz.astimezone(timezone.utc).timetuple()[:-1])
4039 # At the edges, UTC adjustment can produce years out-of-range
4040 # for a datetime object. Ensure that an OverflowError is
4041 # raised.
4042 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
4043 # That goes back 1 minute less than a full day.
4044 self.assertRaises(OverflowError, tiny.utctimetuple)
4045
4046 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
4047 # That goes forward 1 minute less than a full day.
4048 self.assertRaises(OverflowError, huge.utctimetuple)
4049 # More overflow cases
4050 tiny = cls.min.replace(tzinfo=timezone(MINUTE))
4051 self.assertRaises(OverflowError, tiny.utctimetuple)
4052 huge = cls.max.replace(tzinfo=timezone(-MINUTE))
4053 self.assertRaises(OverflowError, huge.utctimetuple)
4054
4055 def test_tzinfo_isoformat(self):
4056 zero = FixedOffset(0, "+00:00")
4057 plus = FixedOffset(220, "+03:40")
4058 minus = FixedOffset(-231, "-03:51")
4059 unknown = FixedOffset(None, "")
4060
4061 cls = self.theclass
4062 datestr = '0001-02-03'
4063 for ofs in None, zero, plus, minus, unknown:
4064 for us in 0, 987001:
4065 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
4066 timestr = '04:05:59' + (us and '.987001' or '')
4067 ofsstr = ofs is not None and d.tzname() or ''
4068 tailstr = timestr + ofsstr
4069 iso = d.isoformat()
4070 self.assertEqual(iso, datestr + 'T' + tailstr)
4071 self.assertEqual(iso, d.isoformat('T'))
4072 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
4073 self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
4074 self.assertEqual(str(d), datestr + ' ' + tailstr)
4075
4076 def test_replace(self):
4077 cls = self.theclass
4078 z100 = FixedOffset(100, "+100")
4079 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
4080 args = [1, 2, 3, 4, 5, 6, 7, z100]
4081 base = cls(*args)
4082 self.assertEqual(base, base.replace())
4083
4084 i = 0
4085 for name, newval in (("year", 2),
4086 ("month", 3),
4087 ("day", 4),
4088 ("hour", 5),
4089 ("minute", 6),
4090 ("second", 7),
4091 ("microsecond", 8),
4092 ("tzinfo", zm200)):
4093 newargs = args[:]
4094 newargs[i] = newval
4095 expected = cls(*newargs)
4096 got = base.replace(**{name: newval})
4097 self.assertEqual(expected, got)
4098 i += 1
4099
4100 # Ensure we can get rid of a tzinfo.
4101 self.assertEqual(base.tzname(), "+100")
4102 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004103 self.assertIsNone(base2.tzinfo)
4104 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004105
4106 # Ensure we can add one.
4107 base3 = base2.replace(tzinfo=z100)
4108 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004109 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004110
4111 # Out of bounds.
4112 base = cls(2000, 2, 29)
4113 self.assertRaises(ValueError, base.replace, year=2001)
4114
4115 def test_more_astimezone(self):
4116 # The inherited test_astimezone covered some trivial and error cases.
4117 fnone = FixedOffset(None, "None")
4118 f44m = FixedOffset(44, "44")
4119 fm5h = FixedOffset(-timedelta(hours=5), "m300")
4120
4121 dt = self.theclass.now(tz=f44m)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004122 self.assertIs(dt.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004123 # Replacing with degenerate tzinfo raises an exception.
4124 self.assertRaises(ValueError, dt.astimezone, fnone)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004125 # Replacing with same tzinfo makes no change.
4126 x = dt.astimezone(dt.tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004127 self.assertIs(x.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004128 self.assertEqual(x.date(), dt.date())
4129 self.assertEqual(x.time(), dt.time())
4130
4131 # Replacing with different tzinfo does adjust.
4132 got = dt.astimezone(fm5h)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004133 self.assertIs(got.tzinfo, fm5h)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004134 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
4135 expected = dt - dt.utcoffset() # in effect, convert to UTC
4136 expected += fm5h.utcoffset(dt) # and from there to local time
4137 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
4138 self.assertEqual(got.date(), expected.date())
4139 self.assertEqual(got.time(), expected.time())
4140 self.assertEqual(got.timetz(), expected.timetz())
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004141 self.assertIs(got.tzinfo, expected.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004142 self.assertEqual(got, expected)
4143
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004144 @support.run_with_tz('UTC')
4145 def test_astimezone_default_utc(self):
4146 dt = self.theclass.now(timezone.utc)
4147 self.assertEqual(dt.astimezone(None), dt)
4148 self.assertEqual(dt.astimezone(), dt)
4149
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004150 # Note that offset in TZ variable has the opposite sign to that
4151 # produced by %z directive.
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004152 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4153 def test_astimezone_default_eastern(self):
4154 dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
4155 local = dt.astimezone()
4156 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004157 self.assertEqual(local.strftime("%z %Z"), "-0500 EST")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004158 dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc)
4159 local = dt.astimezone()
4160 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004161 self.assertEqual(local.strftime("%z %Z"), "-0400 EDT")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004162
Alexander Belopolsky1dcf4f92016-03-25 15:42:59 -04004163 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4164 def test_astimezone_default_near_fold(self):
4165 # Issue #26616.
4166 u = datetime(2015, 11, 1, 5, tzinfo=timezone.utc)
4167 t = u.astimezone()
4168 s = t.astimezone()
4169 self.assertEqual(t.tzinfo, s.tzinfo)
4170
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004171 def test_aware_subtract(self):
4172 cls = self.theclass
4173
4174 # Ensure that utcoffset() is ignored when the operands have the
4175 # same tzinfo member.
4176 class OperandDependentOffset(tzinfo):
4177 def utcoffset(self, t):
4178 if t.minute < 10:
4179 # d0 and d1 equal after adjustment
4180 return timedelta(minutes=t.minute)
4181 else:
4182 # d2 off in the weeds
4183 return timedelta(minutes=59)
4184
4185 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
4186 d0 = base.replace(minute=3)
4187 d1 = base.replace(minute=9)
4188 d2 = base.replace(minute=11)
4189 for x in d0, d1, d2:
4190 for y in d0, d1, d2:
4191 got = x - y
4192 expected = timedelta(minutes=x.minute - y.minute)
4193 self.assertEqual(got, expected)
4194
4195 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
4196 # ignored.
4197 base = cls(8, 9, 10, 11, 12, 13, 14)
4198 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
4199 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
4200 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
4201 for x in d0, d1, d2:
4202 for y in d0, d1, d2:
4203 got = x - y
4204 if (x is d0 or x is d1) and (y is d0 or y is d1):
4205 expected = timedelta(0)
4206 elif x is y is d2:
4207 expected = timedelta(0)
4208 elif x is d2:
4209 expected = timedelta(minutes=(11-59)-0)
4210 else:
4211 assert y is d2
4212 expected = timedelta(minutes=0-(11-59))
4213 self.assertEqual(got, expected)
4214
4215 def test_mixed_compare(self):
4216 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
4217 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
4218 self.assertEqual(t1, t2)
4219 t2 = t2.replace(tzinfo=None)
4220 self.assertEqual(t1, t2)
4221 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
4222 self.assertEqual(t1, t2)
4223 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04004224 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004225
4226 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
4227 class Varies(tzinfo):
4228 def __init__(self):
4229 self.offset = timedelta(minutes=22)
4230 def utcoffset(self, t):
4231 self.offset += timedelta(minutes=1)
4232 return self.offset
4233
4234 v = Varies()
4235 t1 = t2.replace(tzinfo=v)
4236 t2 = t2.replace(tzinfo=v)
4237 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
4238 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
4239 self.assertEqual(t1, t2)
4240
4241 # But if they're not identical, it isn't ignored.
4242 t2 = t2.replace(tzinfo=Varies())
4243 self.assertTrue(t1 < t2) # t1's offset counter still going up
4244
4245 def test_subclass_datetimetz(self):
4246
4247 class C(self.theclass):
4248 theAnswer = 42
4249
4250 def __new__(cls, *args, **kws):
4251 temp = kws.copy()
4252 extra = temp.pop('extra')
4253 result = self.theclass.__new__(cls, *args, **temp)
4254 result.extra = extra
4255 return result
4256
4257 def newmeth(self, start):
4258 return start + self.hour + self.year
4259
4260 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
4261
4262 dt1 = self.theclass(*args)
4263 dt2 = C(*args, **{'extra': 7})
4264
4265 self.assertEqual(dt2.__class__, C)
4266 self.assertEqual(dt2.theAnswer, 42)
4267 self.assertEqual(dt2.extra, 7)
4268 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
4269 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
4270
4271# Pain to set up DST-aware tzinfo classes.
4272
4273def first_sunday_on_or_after(dt):
4274 days_to_go = 6 - dt.weekday()
4275 if days_to_go:
4276 dt += timedelta(days_to_go)
4277 return dt
4278
4279ZERO = timedelta(0)
4280MINUTE = timedelta(minutes=1)
4281HOUR = timedelta(hours=1)
4282DAY = timedelta(days=1)
4283# In the US, DST starts at 2am (standard time) on the first Sunday in April.
4284DSTSTART = datetime(1, 4, 1, 2)
4285# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
4286# which is the first Sunday on or after Oct 25. Because we view 1:MM as
4287# being standard time on that day, there is no spelling in local time of
4288# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
4289DSTEND = datetime(1, 10, 25, 1)
4290
4291class USTimeZone(tzinfo):
4292
4293 def __init__(self, hours, reprname, stdname, dstname):
4294 self.stdoffset = timedelta(hours=hours)
4295 self.reprname = reprname
4296 self.stdname = stdname
4297 self.dstname = dstname
4298
4299 def __repr__(self):
4300 return self.reprname
4301
4302 def tzname(self, dt):
4303 if self.dst(dt):
4304 return self.dstname
4305 else:
4306 return self.stdname
4307
4308 def utcoffset(self, dt):
4309 return self.stdoffset + self.dst(dt)
4310
4311 def dst(self, dt):
4312 if dt is None or dt.tzinfo is None:
4313 # An exception instead may be sensible here, in one or more of
4314 # the cases.
4315 return ZERO
4316 assert dt.tzinfo is self
4317
4318 # Find first Sunday in April.
4319 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
4320 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
4321
4322 # Find last Sunday in October.
4323 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
4324 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
4325
4326 # Can't compare naive to aware objects, so strip the timezone from
4327 # dt first.
4328 if start <= dt.replace(tzinfo=None) < end:
4329 return HOUR
4330 else:
4331 return ZERO
4332
4333Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
4334Central = USTimeZone(-6, "Central", "CST", "CDT")
4335Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
4336Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
4337utc_real = FixedOffset(0, "UTC", 0)
4338# For better test coverage, we want another flavor of UTC that's west of
4339# the Eastern and Pacific timezones.
4340utc_fake = FixedOffset(-12*60, "UTCfake", 0)
4341
4342class TestTimezoneConversions(unittest.TestCase):
4343 # The DST switch times for 2002, in std time.
4344 dston = datetime(2002, 4, 7, 2)
4345 dstoff = datetime(2002, 10, 27, 1)
4346
4347 theclass = datetime
4348
4349 # Check a time that's inside DST.
4350 def checkinside(self, dt, tz, utc, dston, dstoff):
4351 self.assertEqual(dt.dst(), HOUR)
4352
4353 # Conversion to our own timezone is always an identity.
4354 self.assertEqual(dt.astimezone(tz), dt)
4355
4356 asutc = dt.astimezone(utc)
4357 there_and_back = asutc.astimezone(tz)
4358
4359 # Conversion to UTC and back isn't always an identity here,
4360 # because there are redundant spellings (in local time) of
4361 # UTC time when DST begins: the clock jumps from 1:59:59
4362 # to 3:00:00, and a local time of 2:MM:SS doesn't really
4363 # make sense then. The classes above treat 2:MM:SS as
4364 # daylight time then (it's "after 2am"), really an alias
4365 # for 1:MM:SS standard time. The latter form is what
4366 # conversion back from UTC produces.
4367 if dt.date() == dston.date() and dt.hour == 2:
4368 # We're in the redundant hour, and coming back from
4369 # UTC gives the 1:MM:SS standard-time spelling.
4370 self.assertEqual(there_and_back + HOUR, dt)
4371 # Although during was considered to be in daylight
4372 # time, there_and_back is not.
4373 self.assertEqual(there_and_back.dst(), ZERO)
4374 # They're the same times in UTC.
4375 self.assertEqual(there_and_back.astimezone(utc),
4376 dt.astimezone(utc))
4377 else:
4378 # We're not in the redundant hour.
4379 self.assertEqual(dt, there_and_back)
4380
4381 # Because we have a redundant spelling when DST begins, there is
Ezio Melotti3b3499b2011-03-16 11:35:38 +02004382 # (unfortunately) an hour when DST ends that can't be spelled at all in
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004383 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
4384 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
4385 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
4386 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
4387 # expressed in local time. Nevertheless, we want conversion back
4388 # from UTC to mimic the local clock's "repeat an hour" behavior.
4389 nexthour_utc = asutc + HOUR
4390 nexthour_tz = nexthour_utc.astimezone(tz)
4391 if dt.date() == dstoff.date() and dt.hour == 0:
4392 # We're in the hour before the last DST hour. The last DST hour
4393 # is ineffable. We want the conversion back to repeat 1:MM.
4394 self.assertEqual(nexthour_tz, dt.replace(hour=1))
4395 nexthour_utc += HOUR
4396 nexthour_tz = nexthour_utc.astimezone(tz)
4397 self.assertEqual(nexthour_tz, dt.replace(hour=1))
4398 else:
4399 self.assertEqual(nexthour_tz - dt, HOUR)
4400
4401 # Check a time that's outside DST.
4402 def checkoutside(self, dt, tz, utc):
4403 self.assertEqual(dt.dst(), ZERO)
4404
4405 # Conversion to our own timezone is always an identity.
4406 self.assertEqual(dt.astimezone(tz), dt)
4407
4408 # Converting to UTC and back is an identity too.
4409 asutc = dt.astimezone(utc)
4410 there_and_back = asutc.astimezone(tz)
4411 self.assertEqual(dt, there_and_back)
4412
4413 def convert_between_tz_and_utc(self, tz, utc):
4414 dston = self.dston.replace(tzinfo=tz)
4415 # Because 1:MM on the day DST ends is taken as being standard time,
4416 # there is no spelling in tz for the last hour of daylight time.
4417 # For purposes of the test, the last hour of DST is 0:MM, which is
4418 # taken as being daylight time (and 1:MM is taken as being standard
4419 # time).
4420 dstoff = self.dstoff.replace(tzinfo=tz)
4421 for delta in (timedelta(weeks=13),
4422 DAY,
4423 HOUR,
4424 timedelta(minutes=1),
4425 timedelta(microseconds=1)):
4426
4427 self.checkinside(dston, tz, utc, dston, dstoff)
4428 for during in dston + delta, dstoff - delta:
4429 self.checkinside(during, tz, utc, dston, dstoff)
4430
4431 self.checkoutside(dstoff, tz, utc)
4432 for outside in dston - delta, dstoff + delta:
4433 self.checkoutside(outside, tz, utc)
4434
4435 def test_easy(self):
4436 # Despite the name of this test, the endcases are excruciating.
4437 self.convert_between_tz_and_utc(Eastern, utc_real)
4438 self.convert_between_tz_and_utc(Pacific, utc_real)
4439 self.convert_between_tz_and_utc(Eastern, utc_fake)
4440 self.convert_between_tz_and_utc(Pacific, utc_fake)
4441 # The next is really dancing near the edge. It works because
4442 # Pacific and Eastern are far enough apart that their "problem
4443 # hours" don't overlap.
4444 self.convert_between_tz_and_utc(Eastern, Pacific)
4445 self.convert_between_tz_and_utc(Pacific, Eastern)
4446 # OTOH, these fail! Don't enable them. The difficulty is that
4447 # the edge case tests assume that every hour is representable in
4448 # the "utc" class. This is always true for a fixed-offset tzinfo
4449 # class (lke utc_real and utc_fake), but not for Eastern or Central.
4450 # For these adjacent DST-aware time zones, the range of time offsets
4451 # tested ends up creating hours in the one that aren't representable
4452 # in the other. For the same reason, we would see failures in the
4453 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
4454 # offset deltas in convert_between_tz_and_utc().
4455 #
4456 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
4457 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
4458
4459 def test_tricky(self):
4460 # 22:00 on day before daylight starts.
4461 fourback = self.dston - timedelta(hours=4)
4462 ninewest = FixedOffset(-9*60, "-0900", 0)
4463 fourback = fourback.replace(tzinfo=ninewest)
4464 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
4465 # 2", we should get the 3 spelling.
4466 # If we plug 22:00 the day before into Eastern, it "looks like std
4467 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
4468 # to 22:00 lands on 2:00, which makes no sense in local time (the
4469 # local clock jumps from 1 to 3). The point here is to make sure we
4470 # get the 3 spelling.
4471 expected = self.dston.replace(hour=3)
4472 got = fourback.astimezone(Eastern).replace(tzinfo=None)
4473 self.assertEqual(expected, got)
4474
4475 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
4476 # case we want the 1:00 spelling.
4477 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
4478 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
4479 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
4480 # spelling.
4481 expected = self.dston.replace(hour=1)
4482 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
4483 self.assertEqual(expected, got)
4484
4485 # Now on the day DST ends, we want "repeat an hour" behavior.
4486 # UTC 4:MM 5:MM 6:MM 7:MM checking these
4487 # EST 23:MM 0:MM 1:MM 2:MM
4488 # EDT 0:MM 1:MM 2:MM 3:MM
4489 # wall 0:MM 1:MM 1:MM 2:MM against these
4490 for utc in utc_real, utc_fake:
4491 for tz in Eastern, Pacific:
4492 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
4493 # Convert that to UTC.
4494 first_std_hour -= tz.utcoffset(None)
4495 # Adjust for possibly fake UTC.
4496 asutc = first_std_hour + utc.utcoffset(None)
4497 # First UTC hour to convert; this is 4:00 when utc=utc_real &
4498 # tz=Eastern.
4499 asutcbase = asutc.replace(tzinfo=utc)
4500 for tzhour in (0, 1, 1, 2):
4501 expectedbase = self.dstoff.replace(hour=tzhour)
4502 for minute in 0, 30, 59:
4503 expected = expectedbase.replace(minute=minute)
4504 asutc = asutcbase.replace(minute=minute)
4505 astz = asutc.astimezone(tz)
4506 self.assertEqual(astz.replace(tzinfo=None), expected)
4507 asutcbase += HOUR
4508
4509
4510 def test_bogus_dst(self):
4511 class ok(tzinfo):
4512 def utcoffset(self, dt): return HOUR
4513 def dst(self, dt): return HOUR
4514
4515 now = self.theclass.now().replace(tzinfo=utc_real)
4516 # Doesn't blow up.
4517 now.astimezone(ok())
4518
4519 # Does blow up.
4520 class notok(ok):
4521 def dst(self, dt): return None
4522 self.assertRaises(ValueError, now.astimezone, notok())
4523
4524 # Sometimes blow up. In the following, tzinfo.dst()
4525 # implementation may return None or not None depending on
4526 # whether DST is assumed to be in effect. In this situation,
4527 # a ValueError should be raised by astimezone().
4528 class tricky_notok(ok):
4529 def dst(self, dt):
4530 if dt.year == 2000:
4531 return None
4532 else:
4533 return 10*HOUR
4534 dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
4535 self.assertRaises(ValueError, dt.astimezone, tricky_notok())
4536
4537 def test_fromutc(self):
4538 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
4539 now = datetime.utcnow().replace(tzinfo=utc_real)
4540 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
4541 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
4542 enow = Eastern.fromutc(now) # doesn't blow up
4543 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
4544 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
4545 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
4546
4547 # Always converts UTC to standard time.
4548 class FauxUSTimeZone(USTimeZone):
4549 def fromutc(self, dt):
4550 return dt + self.stdoffset
4551 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
4552
4553 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
4554 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
4555 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
4556
4557 # Check around DST start.
4558 start = self.dston.replace(hour=4, tzinfo=Eastern)
4559 fstart = start.replace(tzinfo=FEastern)
4560 for wall in 23, 0, 1, 3, 4, 5:
4561 expected = start.replace(hour=wall)
4562 if wall == 23:
4563 expected -= timedelta(days=1)
4564 got = Eastern.fromutc(start)
4565 self.assertEqual(expected, got)
4566
4567 expected = fstart + FEastern.stdoffset
4568 got = FEastern.fromutc(fstart)
4569 self.assertEqual(expected, got)
4570
4571 # Ensure astimezone() calls fromutc() too.
4572 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
4573 self.assertEqual(expected, got)
4574
4575 start += HOUR
4576 fstart += HOUR
4577
4578 # Check around DST end.
4579 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
4580 fstart = start.replace(tzinfo=FEastern)
4581 for wall in 0, 1, 1, 2, 3, 4:
4582 expected = start.replace(hour=wall)
4583 got = Eastern.fromutc(start)
4584 self.assertEqual(expected, got)
4585
4586 expected = fstart + FEastern.stdoffset
4587 got = FEastern.fromutc(fstart)
4588 self.assertEqual(expected, got)
4589
4590 # Ensure astimezone() calls fromutc() too.
4591 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
4592 self.assertEqual(expected, got)
4593
4594 start += HOUR
4595 fstart += HOUR
4596
4597
4598#############################################################################
4599# oddballs
4600
4601class Oddballs(unittest.TestCase):
4602
4603 def test_bug_1028306(self):
4604 # Trying to compare a date to a datetime should act like a mixed-
4605 # type comparison, despite that datetime is a subclass of date.
4606 as_date = date.today()
4607 as_datetime = datetime.combine(as_date, time())
4608 self.assertTrue(as_date != as_datetime)
4609 self.assertTrue(as_datetime != as_date)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004610 self.assertFalse(as_date == as_datetime)
4611 self.assertFalse(as_datetime == as_date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004612 self.assertRaises(TypeError, lambda: as_date < as_datetime)
4613 self.assertRaises(TypeError, lambda: as_datetime < as_date)
4614 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
4615 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
4616 self.assertRaises(TypeError, lambda: as_date > as_datetime)
4617 self.assertRaises(TypeError, lambda: as_datetime > as_date)
4618 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
4619 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
4620
Raymond Hettinger15f44ab2016-08-30 10:47:49 -07004621 # Nevertheless, comparison should work with the base-class (date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004622 # projection if use of a date method is forced.
4623 self.assertEqual(as_date.__eq__(as_datetime), True)
4624 different_day = (as_date.day + 1) % 20 + 1
4625 as_different = as_datetime.replace(day= different_day)
4626 self.assertEqual(as_date.__eq__(as_different), False)
4627
4628 # And date should compare with other subclasses of date. If a
4629 # subclass wants to stop this, it's up to the subclass to do so.
4630 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
4631 self.assertEqual(as_date, date_sc)
4632 self.assertEqual(date_sc, as_date)
4633
4634 # Ditto for datetimes.
4635 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
4636 as_date.day, 0, 0, 0)
4637 self.assertEqual(as_datetime, datetime_sc)
4638 self.assertEqual(datetime_sc, as_datetime)
4639
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004640 def test_extra_attributes(self):
4641 for x in [date.today(),
4642 time(),
4643 datetime.utcnow(),
4644 timedelta(),
4645 tzinfo(),
4646 timezone(timedelta())]:
4647 with self.assertRaises(AttributeError):
4648 x.abc = 1
4649
4650 def test_check_arg_types(self):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004651 class Number:
4652 def __init__(self, value):
4653 self.value = value
4654 def __int__(self):
4655 return self.value
4656
4657 for xx in [decimal.Decimal(10),
4658 decimal.Decimal('10.9'),
4659 Number(10)]:
4660 self.assertEqual(datetime(10, 10, 10, 10, 10, 10, 10),
4661 datetime(xx, xx, xx, xx, xx, xx, xx))
4662
4663 with self.assertRaisesRegex(TypeError, '^an integer is required '
R David Murray44b548d2016-09-08 13:59:53 -04004664 r'\(got type str\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004665 datetime(10, 10, '10')
4666
4667 f10 = Number(10.9)
4668 with self.assertRaisesRegex(TypeError, '^__int__ returned non-int '
R David Murray44b548d2016-09-08 13:59:53 -04004669 r'\(type float\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004670 datetime(10, 10, f10)
4671
4672 class Float(float):
4673 pass
4674 s10 = Float(10.9)
4675 with self.assertRaisesRegex(TypeError, '^integer argument expected, '
4676 'got float$'):
4677 datetime(10, 10, s10)
4678
4679 with self.assertRaises(TypeError):
4680 datetime(10., 10, 10)
4681 with self.assertRaises(TypeError):
4682 datetime(10, 10., 10)
4683 with self.assertRaises(TypeError):
4684 datetime(10, 10, 10.)
4685 with self.assertRaises(TypeError):
4686 datetime(10, 10, 10, 10.)
4687 with self.assertRaises(TypeError):
4688 datetime(10, 10, 10, 10, 10.)
4689 with self.assertRaises(TypeError):
4690 datetime(10, 10, 10, 10, 10, 10.)
4691 with self.assertRaises(TypeError):
4692 datetime(10, 10, 10, 10, 10, 10, 10.)
4693
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004694#############################################################################
4695# Local Time Disambiguation
4696
4697# An experimental reimplementation of fromutc that respects the "fold" flag.
4698
4699class tzinfo2(tzinfo):
4700
4701 def fromutc(self, dt):
4702 "datetime in UTC -> datetime in local time."
4703
4704 if not isinstance(dt, datetime):
4705 raise TypeError("fromutc() requires a datetime argument")
4706 if dt.tzinfo is not self:
4707 raise ValueError("dt.tzinfo is not self")
4708 # Returned value satisfies
4709 # dt + ldt.utcoffset() = ldt
4710 off0 = dt.replace(fold=0).utcoffset()
4711 off1 = dt.replace(fold=1).utcoffset()
4712 if off0 is None or off1 is None or dt.dst() is None:
4713 raise ValueError
4714 if off0 == off1:
4715 ldt = dt + off0
4716 off1 = ldt.utcoffset()
4717 if off0 == off1:
4718 return ldt
4719 # Now, we discovered both possible offsets, so
4720 # we can just try four possible solutions:
4721 for off in [off0, off1]:
4722 ldt = dt + off
4723 if ldt.utcoffset() == off:
4724 return ldt
4725 ldt = ldt.replace(fold=1)
4726 if ldt.utcoffset() == off:
4727 return ldt
4728
4729 raise ValueError("No suitable local time found")
4730
4731# Reimplementing simplified US timezones to respect the "fold" flag:
4732
4733class USTimeZone2(tzinfo2):
4734
4735 def __init__(self, hours, reprname, stdname, dstname):
4736 self.stdoffset = timedelta(hours=hours)
4737 self.reprname = reprname
4738 self.stdname = stdname
4739 self.dstname = dstname
4740
4741 def __repr__(self):
4742 return self.reprname
4743
4744 def tzname(self, dt):
4745 if self.dst(dt):
4746 return self.dstname
4747 else:
4748 return self.stdname
4749
4750 def utcoffset(self, dt):
4751 return self.stdoffset + self.dst(dt)
4752
4753 def dst(self, dt):
4754 if dt is None or dt.tzinfo is None:
4755 # An exception instead may be sensible here, in one or more of
4756 # the cases.
4757 return ZERO
4758 assert dt.tzinfo is self
4759
4760 # Find first Sunday in April.
4761 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
4762 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
4763
4764 # Find last Sunday in October.
4765 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
4766 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
4767
4768 # Can't compare naive to aware objects, so strip the timezone from
4769 # dt first.
4770 dt = dt.replace(tzinfo=None)
4771 if start + HOUR <= dt < end:
4772 # DST is in effect.
4773 return HOUR
4774 elif end <= dt < end + HOUR:
4775 # Fold (an ambiguous hour): use dt.fold to disambiguate.
4776 return ZERO if dt.fold else HOUR
4777 elif start <= dt < start + HOUR:
4778 # Gap (a non-existent hour): reverse the fold rule.
4779 return HOUR if dt.fold else ZERO
4780 else:
4781 # DST is off.
4782 return ZERO
4783
4784Eastern2 = USTimeZone2(-5, "Eastern2", "EST", "EDT")
4785Central2 = USTimeZone2(-6, "Central2", "CST", "CDT")
4786Mountain2 = USTimeZone2(-7, "Mountain2", "MST", "MDT")
4787Pacific2 = USTimeZone2(-8, "Pacific2", "PST", "PDT")
4788
4789# Europe_Vilnius_1941 tzinfo implementation reproduces the following
4790# 1941 transition from Olson's tzdist:
4791#
4792# Zone NAME GMTOFF RULES FORMAT [UNTIL]
4793# ZoneEurope/Vilnius 1:00 - CET 1940 Aug 3
4794# 3:00 - MSK 1941 Jun 24
4795# 1:00 C-Eur CE%sT 1944 Aug
4796#
4797# $ zdump -v Europe/Vilnius | grep 1941
4798# Europe/Vilnius Mon Jun 23 20:59:59 1941 UTC = Mon Jun 23 23:59:59 1941 MSK isdst=0 gmtoff=10800
4799# Europe/Vilnius Mon Jun 23 21:00:00 1941 UTC = Mon Jun 23 23:00:00 1941 CEST isdst=1 gmtoff=7200
4800
4801class Europe_Vilnius_1941(tzinfo):
4802 def _utc_fold(self):
4803 return [datetime(1941, 6, 23, 21, tzinfo=self), # Mon Jun 23 21:00:00 1941 UTC
4804 datetime(1941, 6, 23, 22, tzinfo=self)] # Mon Jun 23 22:00:00 1941 UTC
4805
4806 def _loc_fold(self):
4807 return [datetime(1941, 6, 23, 23, tzinfo=self), # Mon Jun 23 23:00:00 1941 MSK / CEST
4808 datetime(1941, 6, 24, 0, tzinfo=self)] # Mon Jun 24 00:00:00 1941 CEST
4809
4810 def utcoffset(self, dt):
4811 fold_start, fold_stop = self._loc_fold()
4812 if dt < fold_start:
4813 return 3 * HOUR
4814 if dt < fold_stop:
4815 return (2 if dt.fold else 3) * HOUR
4816 # if dt >= fold_stop
4817 return 2 * HOUR
4818
4819 def dst(self, dt):
4820 fold_start, fold_stop = self._loc_fold()
4821 if dt < fold_start:
4822 return 0 * HOUR
4823 if dt < fold_stop:
4824 return (1 if dt.fold else 0) * HOUR
4825 # if dt >= fold_stop
4826 return 1 * HOUR
4827
4828 def tzname(self, dt):
4829 fold_start, fold_stop = self._loc_fold()
4830 if dt < fold_start:
4831 return 'MSK'
4832 if dt < fold_stop:
4833 return ('MSK', 'CEST')[dt.fold]
4834 # if dt >= fold_stop
4835 return 'CEST'
4836
4837 def fromutc(self, dt):
4838 assert dt.fold == 0
4839 assert dt.tzinfo is self
4840 if dt.year != 1941:
4841 raise NotImplementedError
4842 fold_start, fold_stop = self._utc_fold()
4843 if dt < fold_start:
4844 return dt + 3 * HOUR
4845 if dt < fold_stop:
4846 return (dt + 2 * HOUR).replace(fold=1)
4847 # if dt >= fold_stop
4848 return dt + 2 * HOUR
4849
4850
4851class TestLocalTimeDisambiguation(unittest.TestCase):
4852
4853 def test_vilnius_1941_fromutc(self):
4854 Vilnius = Europe_Vilnius_1941()
4855
4856 gdt = datetime(1941, 6, 23, 20, 59, 59, tzinfo=timezone.utc)
4857 ldt = gdt.astimezone(Vilnius)
4858 self.assertEqual(ldt.strftime("%c %Z%z"),
4859 'Mon Jun 23 23:59:59 1941 MSK+0300')
4860 self.assertEqual(ldt.fold, 0)
4861 self.assertFalse(ldt.dst())
4862
4863 gdt = datetime(1941, 6, 23, 21, tzinfo=timezone.utc)
4864 ldt = gdt.astimezone(Vilnius)
4865 self.assertEqual(ldt.strftime("%c %Z%z"),
4866 'Mon Jun 23 23:00:00 1941 CEST+0200')
4867 self.assertEqual(ldt.fold, 1)
4868 self.assertTrue(ldt.dst())
4869
4870 gdt = datetime(1941, 6, 23, 22, tzinfo=timezone.utc)
4871 ldt = gdt.astimezone(Vilnius)
4872 self.assertEqual(ldt.strftime("%c %Z%z"),
4873 'Tue Jun 24 00:00:00 1941 CEST+0200')
4874 self.assertEqual(ldt.fold, 0)
4875 self.assertTrue(ldt.dst())
4876
4877 def test_vilnius_1941_toutc(self):
4878 Vilnius = Europe_Vilnius_1941()
4879
4880 ldt = datetime(1941, 6, 23, 22, 59, 59, tzinfo=Vilnius)
4881 gdt = ldt.astimezone(timezone.utc)
4882 self.assertEqual(gdt.strftime("%c %Z"),
4883 'Mon Jun 23 19:59:59 1941 UTC')
4884
4885 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius)
4886 gdt = ldt.astimezone(timezone.utc)
4887 self.assertEqual(gdt.strftime("%c %Z"),
4888 'Mon Jun 23 20:59:59 1941 UTC')
4889
4890 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius, fold=1)
4891 gdt = ldt.astimezone(timezone.utc)
4892 self.assertEqual(gdt.strftime("%c %Z"),
4893 'Mon Jun 23 21:59:59 1941 UTC')
4894
4895 ldt = datetime(1941, 6, 24, 0, tzinfo=Vilnius)
4896 gdt = ldt.astimezone(timezone.utc)
4897 self.assertEqual(gdt.strftime("%c %Z"),
4898 'Mon Jun 23 22:00:00 1941 UTC')
4899
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004900 def test_constructors(self):
4901 t = time(0, fold=1)
4902 dt = datetime(1, 1, 1, fold=1)
4903 self.assertEqual(t.fold, 1)
4904 self.assertEqual(dt.fold, 1)
4905 with self.assertRaises(TypeError):
4906 time(0, 0, 0, 0, None, 0)
4907
4908 def test_member(self):
4909 dt = datetime(1, 1, 1, fold=1)
4910 t = dt.time()
4911 self.assertEqual(t.fold, 1)
4912 t = dt.timetz()
4913 self.assertEqual(t.fold, 1)
4914
4915 def test_replace(self):
4916 t = time(0)
4917 dt = datetime(1, 1, 1)
4918 self.assertEqual(t.replace(fold=1).fold, 1)
4919 self.assertEqual(dt.replace(fold=1).fold, 1)
4920 self.assertEqual(t.replace(fold=0).fold, 0)
4921 self.assertEqual(dt.replace(fold=0).fold, 0)
4922 # Check that replacement of other fields does not change "fold".
4923 t = t.replace(fold=1, tzinfo=Eastern)
4924 dt = dt.replace(fold=1, tzinfo=Eastern)
4925 self.assertEqual(t.replace(tzinfo=None).fold, 1)
4926 self.assertEqual(dt.replace(tzinfo=None).fold, 1)
Serhiy Storchaka314d6fc2017-03-31 22:48:16 +03004927 # Out of bounds.
4928 with self.assertRaises(ValueError):
4929 t.replace(fold=2)
4930 with self.assertRaises(ValueError):
4931 dt.replace(fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004932 # Check that fold is a keyword-only argument
4933 with self.assertRaises(TypeError):
4934 t.replace(1, 1, 1, None, 1)
4935 with self.assertRaises(TypeError):
4936 dt.replace(1, 1, 1, 1, 1, 1, 1, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04004937
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004938 def test_comparison(self):
4939 t = time(0)
4940 dt = datetime(1, 1, 1)
4941 self.assertEqual(t, t.replace(fold=1))
4942 self.assertEqual(dt, dt.replace(fold=1))
4943
4944 def test_hash(self):
4945 t = time(0)
4946 dt = datetime(1, 1, 1)
4947 self.assertEqual(hash(t), hash(t.replace(fold=1)))
4948 self.assertEqual(hash(dt), hash(dt.replace(fold=1)))
4949
4950 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4951 def test_fromtimestamp(self):
4952 s = 1414906200
4953 dt0 = datetime.fromtimestamp(s)
4954 dt1 = datetime.fromtimestamp(s + 3600)
4955 self.assertEqual(dt0.fold, 0)
4956 self.assertEqual(dt1.fold, 1)
4957
4958 @support.run_with_tz('Australia/Lord_Howe')
4959 def test_fromtimestamp_lord_howe(self):
4960 tm = _time.localtime(1.4e9)
4961 if _time.strftime('%Z%z', tm) != 'LHST+1030':
4962 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
4963 # $ TZ=Australia/Lord_Howe date -r 1428158700
4964 # Sun Apr 5 01:45:00 LHDT 2015
4965 # $ TZ=Australia/Lord_Howe date -r 1428160500
4966 # Sun Apr 5 01:45:00 LHST 2015
4967 s = 1428158700
4968 t0 = datetime.fromtimestamp(s)
4969 t1 = datetime.fromtimestamp(s + 1800)
4970 self.assertEqual(t0, t1)
4971 self.assertEqual(t0.fold, 0)
4972 self.assertEqual(t1.fold, 1)
4973
Ammar Askar96d1e692018-07-25 09:54:58 -07004974 def test_fromtimestamp_low_fold_detection(self):
4975 # Ensure that fold detection doesn't cause an
4976 # OSError for really low values, see bpo-29097
4977 self.assertEqual(datetime.fromtimestamp(0).fold, 0)
4978
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004979 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4980 def test_timestamp(self):
4981 dt0 = datetime(2014, 11, 2, 1, 30)
4982 dt1 = dt0.replace(fold=1)
4983 self.assertEqual(dt0.timestamp() + 3600,
4984 dt1.timestamp())
4985
4986 @support.run_with_tz('Australia/Lord_Howe')
4987 def test_timestamp_lord_howe(self):
4988 tm = _time.localtime(1.4e9)
4989 if _time.strftime('%Z%z', tm) != 'LHST+1030':
4990 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
4991 t = datetime(2015, 4, 5, 1, 45)
4992 s0 = t.replace(fold=0).timestamp()
4993 s1 = t.replace(fold=1).timestamp()
4994 self.assertEqual(s0 + 1800, s1)
4995
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004996 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4997 def test_astimezone(self):
4998 dt0 = datetime(2014, 11, 2, 1, 30)
4999 dt1 = dt0.replace(fold=1)
5000 # Convert both naive instances to aware.
5001 adt0 = dt0.astimezone()
5002 adt1 = dt1.astimezone()
5003 # Check that the first instance in DST zone and the second in STD
5004 self.assertEqual(adt0.tzname(), 'EDT')
5005 self.assertEqual(adt1.tzname(), 'EST')
5006 self.assertEqual(adt0 + HOUR, adt1)
5007 # Aware instances with fixed offset tzinfo's always have fold=0
5008 self.assertEqual(adt0.fold, 0)
5009 self.assertEqual(adt1.fold, 0)
5010
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005011 def test_pickle_fold(self):
5012 t = time(fold=1)
5013 dt = datetime(1, 1, 1, fold=1)
5014 for pickler, unpickler, proto in pickle_choices:
5015 for x in [t, dt]:
5016 s = pickler.dumps(x, proto)
5017 y = unpickler.loads(s)
5018 self.assertEqual(x, y)
5019 self.assertEqual((0 if proto < 4 else x.fold), y.fold)
5020
5021 def test_repr(self):
5022 t = time(fold=1)
5023 dt = datetime(1, 1, 1, fold=1)
5024 self.assertEqual(repr(t), 'datetime.time(0, 0, fold=1)')
5025 self.assertEqual(repr(dt),
5026 'datetime.datetime(1, 1, 1, 0, 0, fold=1)')
5027
5028 def test_dst(self):
5029 # Let's first establish that things work in regular times.
5030 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
5031 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
5032 self.assertEqual(dt_summer.dst(), HOUR)
5033 self.assertEqual(dt_winter.dst(), ZERO)
5034 # The disambiguation flag is ignored
5035 self.assertEqual(dt_summer.replace(fold=1).dst(), HOUR)
5036 self.assertEqual(dt_winter.replace(fold=1).dst(), ZERO)
5037
5038 # Pick local time in the fold.
5039 for minute in [0, 30, 59]:
5040 dt = datetime(2002, 10, 27, 1, minute, tzinfo=Eastern2)
5041 # With fold=0 (the default) it is in DST.
5042 self.assertEqual(dt.dst(), HOUR)
5043 # With fold=1 it is in STD.
5044 self.assertEqual(dt.replace(fold=1).dst(), ZERO)
5045
5046 # Pick local time in the gap.
5047 for minute in [0, 30, 59]:
5048 dt = datetime(2002, 4, 7, 2, minute, tzinfo=Eastern2)
5049 # With fold=0 (the default) it is in STD.
5050 self.assertEqual(dt.dst(), ZERO)
5051 # With fold=1 it is in DST.
5052 self.assertEqual(dt.replace(fold=1).dst(), HOUR)
5053
5054
5055 def test_utcoffset(self):
5056 # Let's first establish that things work in regular times.
5057 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
5058 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
5059 self.assertEqual(dt_summer.utcoffset(), -4 * HOUR)
5060 self.assertEqual(dt_winter.utcoffset(), -5 * HOUR)
5061 # The disambiguation flag is ignored
5062 self.assertEqual(dt_summer.replace(fold=1).utcoffset(), -4 * HOUR)
5063 self.assertEqual(dt_winter.replace(fold=1).utcoffset(), -5 * HOUR)
5064
5065 def test_fromutc(self):
5066 # Let's first establish that things work in regular times.
5067 u_summer = datetime(2002, 10, 27, 6, tzinfo=Eastern2) - timedelta.resolution
5068 u_winter = datetime(2002, 10, 27, 7, tzinfo=Eastern2)
5069 t_summer = Eastern2.fromutc(u_summer)
5070 t_winter = Eastern2.fromutc(u_winter)
5071 self.assertEqual(t_summer, u_summer - 4 * HOUR)
5072 self.assertEqual(t_winter, u_winter - 5 * HOUR)
5073 self.assertEqual(t_summer.fold, 0)
5074 self.assertEqual(t_winter.fold, 0)
5075
5076 # What happens in the fall-back fold?
5077 u = datetime(2002, 10, 27, 5, 30, tzinfo=Eastern2)
5078 t0 = Eastern2.fromutc(u)
5079 u += HOUR
5080 t1 = Eastern2.fromutc(u)
5081 self.assertEqual(t0, t1)
5082 self.assertEqual(t0.fold, 0)
5083 self.assertEqual(t1.fold, 1)
5084 # The tricky part is when u is in the local fold:
5085 u = datetime(2002, 10, 27, 1, 30, tzinfo=Eastern2)
5086 t = Eastern2.fromutc(u)
5087 self.assertEqual((t.day, t.hour), (26, 21))
5088 # .. or gets into the local fold after a standard time adjustment
5089 u = datetime(2002, 10, 27, 6, 30, tzinfo=Eastern2)
5090 t = Eastern2.fromutc(u)
5091 self.assertEqual((t.day, t.hour), (27, 1))
5092
5093 # What happens in the spring-forward gap?
5094 u = datetime(2002, 4, 7, 2, 0, tzinfo=Eastern2)
5095 t = Eastern2.fromutc(u)
5096 self.assertEqual((t.day, t.hour), (6, 21))
5097
5098 def test_mixed_compare_regular(self):
5099 t = datetime(2000, 1, 1, tzinfo=Eastern2)
5100 self.assertEqual(t, t.astimezone(timezone.utc))
5101 t = datetime(2000, 6, 1, tzinfo=Eastern2)
5102 self.assertEqual(t, t.astimezone(timezone.utc))
5103
5104 def test_mixed_compare_fold(self):
5105 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
5106 t_fold_utc = t_fold.astimezone(timezone.utc)
5107 self.assertNotEqual(t_fold, t_fold_utc)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04005108 self.assertNotEqual(t_fold_utc, t_fold)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005109
5110 def test_mixed_compare_gap(self):
5111 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
5112 t_gap_utc = t_gap.astimezone(timezone.utc)
5113 self.assertNotEqual(t_gap, t_gap_utc)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04005114 self.assertNotEqual(t_gap_utc, t_gap)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005115
5116 def test_hash_aware(self):
5117 t = datetime(2000, 1, 1, tzinfo=Eastern2)
5118 self.assertEqual(hash(t), hash(t.replace(fold=1)))
5119 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
5120 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
5121 self.assertEqual(hash(t_fold), hash(t_fold.replace(fold=1)))
5122 self.assertEqual(hash(t_gap), hash(t_gap.replace(fold=1)))
5123
5124SEC = timedelta(0, 1)
5125
5126def pairs(iterable):
5127 a, b = itertools.tee(iterable)
5128 next(b, None)
5129 return zip(a, b)
5130
5131class ZoneInfo(tzinfo):
5132 zoneroot = '/usr/share/zoneinfo'
5133 def __init__(self, ut, ti):
5134 """
5135
5136 :param ut: array
5137 Array of transition point timestamps
5138 :param ti: list
5139 A list of (offset, isdst, abbr) tuples
5140 :return: None
5141 """
5142 self.ut = ut
5143 self.ti = ti
5144 self.lt = self.invert(ut, ti)
5145
5146 @staticmethod
5147 def invert(ut, ti):
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04005148 lt = (array('q', ut), array('q', ut))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005149 if ut:
5150 offset = ti[0][0] // SEC
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04005151 lt[0][0] += offset
5152 lt[1][0] += offset
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005153 for i in range(1, len(ut)):
5154 lt[0][i] += ti[i-1][0] // SEC
5155 lt[1][i] += ti[i][0] // SEC
5156 return lt
5157
5158 @classmethod
5159 def fromfile(cls, fileobj):
5160 if fileobj.read(4).decode() != "TZif":
5161 raise ValueError("not a zoneinfo file")
5162 fileobj.seek(32)
5163 counts = array('i')
5164 counts.fromfile(fileobj, 3)
5165 if sys.byteorder != 'big':
5166 counts.byteswap()
5167
5168 ut = array('i')
5169 ut.fromfile(fileobj, counts[0])
5170 if sys.byteorder != 'big':
5171 ut.byteswap()
5172
5173 type_indices = array('B')
5174 type_indices.fromfile(fileobj, counts[0])
5175
5176 ttis = []
5177 for i in range(counts[1]):
5178 ttis.append(struct.unpack(">lbb", fileobj.read(6)))
5179
5180 abbrs = fileobj.read(counts[2])
5181
5182 # Convert ttis
5183 for i, (gmtoff, isdst, abbrind) in enumerate(ttis):
5184 abbr = abbrs[abbrind:abbrs.find(0, abbrind)].decode()
5185 ttis[i] = (timedelta(0, gmtoff), isdst, abbr)
5186
5187 ti = [None] * len(ut)
5188 for i, idx in enumerate(type_indices):
5189 ti[i] = ttis[idx]
5190
5191 self = cls(ut, ti)
5192
5193 return self
5194
5195 @classmethod
5196 def fromname(cls, name):
5197 path = os.path.join(cls.zoneroot, name)
5198 with open(path, 'rb') as f:
5199 return cls.fromfile(f)
5200
5201 EPOCHORDINAL = date(1970, 1, 1).toordinal()
5202
5203 def fromutc(self, dt):
5204 """datetime in UTC -> datetime in local time."""
5205
5206 if not isinstance(dt, datetime):
5207 raise TypeError("fromutc() requires a datetime argument")
5208 if dt.tzinfo is not self:
5209 raise ValueError("dt.tzinfo is not self")
5210
5211 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
5212 + dt.hour * 3600
5213 + dt.minute * 60
5214 + dt.second)
5215
5216 if timestamp < self.ut[1]:
5217 tti = self.ti[0]
5218 fold = 0
5219 else:
5220 idx = bisect.bisect_right(self.ut, timestamp)
5221 assert self.ut[idx-1] <= timestamp
5222 assert idx == len(self.ut) or timestamp < self.ut[idx]
5223 tti_prev, tti = self.ti[idx-2:idx]
5224 # Detect fold
5225 shift = tti_prev[0] - tti[0]
5226 fold = (shift > timedelta(0, timestamp - self.ut[idx-1]))
5227 dt += tti[0]
5228 if fold:
5229 return dt.replace(fold=1)
5230 else:
5231 return dt
5232
5233 def _find_ti(self, dt, i):
5234 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
5235 + dt.hour * 3600
5236 + dt.minute * 60
5237 + dt.second)
5238 lt = self.lt[dt.fold]
5239 idx = bisect.bisect_right(lt, timestamp)
5240
5241 return self.ti[max(0, idx - 1)][i]
5242
5243 def utcoffset(self, dt):
5244 return self._find_ti(dt, 0)
5245
5246 def dst(self, dt):
5247 isdst = self._find_ti(dt, 1)
5248 # XXX: We cannot accurately determine the "save" value,
5249 # so let's return 1h whenever DST is in effect. Since
5250 # we don't use dst() in fromutc(), it is unlikely that
5251 # it will be needed for anything more than bool(dst()).
5252 return ZERO if isdst else HOUR
5253
5254 def tzname(self, dt):
5255 return self._find_ti(dt, 2)
5256
5257 @classmethod
5258 def zonenames(cls, zonedir=None):
5259 if zonedir is None:
5260 zonedir = cls.zoneroot
Alexander Belopolsky1b8f26c2016-08-11 11:01:45 -04005261 zone_tab = os.path.join(zonedir, 'zone.tab')
5262 try:
5263 f = open(zone_tab)
5264 except OSError:
5265 return
5266 with f:
5267 for line in f:
5268 line = line.strip()
5269 if line and not line.startswith('#'):
5270 yield line.split()[2]
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005271
5272 @classmethod
5273 def stats(cls, start_year=1):
5274 count = gap_count = fold_count = zeros_count = 0
5275 min_gap = min_fold = timedelta.max
5276 max_gap = max_fold = ZERO
5277 min_gap_datetime = max_gap_datetime = datetime.min
5278 min_gap_zone = max_gap_zone = None
5279 min_fold_datetime = max_fold_datetime = datetime.min
5280 min_fold_zone = max_fold_zone = None
5281 stats_since = datetime(start_year, 1, 1) # Starting from 1970 eliminates a lot of noise
5282 for zonename in cls.zonenames():
5283 count += 1
5284 tz = cls.fromname(zonename)
5285 for dt, shift in tz.transitions():
5286 if dt < stats_since:
5287 continue
5288 if shift > ZERO:
5289 gap_count += 1
5290 if (shift, dt) > (max_gap, max_gap_datetime):
5291 max_gap = shift
5292 max_gap_zone = zonename
5293 max_gap_datetime = dt
5294 if (shift, datetime.max - dt) < (min_gap, datetime.max - min_gap_datetime):
5295 min_gap = shift
5296 min_gap_zone = zonename
5297 min_gap_datetime = dt
5298 elif shift < ZERO:
5299 fold_count += 1
5300 shift = -shift
5301 if (shift, dt) > (max_fold, max_fold_datetime):
5302 max_fold = shift
5303 max_fold_zone = zonename
5304 max_fold_datetime = dt
5305 if (shift, datetime.max - dt) < (min_fold, datetime.max - min_fold_datetime):
5306 min_fold = shift
5307 min_fold_zone = zonename
5308 min_fold_datetime = dt
5309 else:
5310 zeros_count += 1
5311 trans_counts = (gap_count, fold_count, zeros_count)
5312 print("Number of zones: %5d" % count)
5313 print("Number of transitions: %5d = %d (gaps) + %d (folds) + %d (zeros)" %
5314 ((sum(trans_counts),) + trans_counts))
5315 print("Min gap: %16s at %s in %s" % (min_gap, min_gap_datetime, min_gap_zone))
5316 print("Max gap: %16s at %s in %s" % (max_gap, max_gap_datetime, max_gap_zone))
5317 print("Min fold: %16s at %s in %s" % (min_fold, min_fold_datetime, min_fold_zone))
5318 print("Max fold: %16s at %s in %s" % (max_fold, max_fold_datetime, max_fold_zone))
5319
5320
5321 def transitions(self):
5322 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
5323 shift = ti[0] - prev_ti[0]
5324 yield datetime.utcfromtimestamp(t), shift
5325
5326 def nondst_folds(self):
5327 """Find all folds with the same value of isdst on both sides of the transition."""
5328 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
5329 shift = ti[0] - prev_ti[0]
5330 if shift < ZERO and ti[1] == prev_ti[1]:
5331 yield datetime.utcfromtimestamp(t), -shift, prev_ti[2], ti[2]
5332
5333 @classmethod
5334 def print_all_nondst_folds(cls, same_abbr=False, start_year=1):
5335 count = 0
5336 for zonename in cls.zonenames():
5337 tz = cls.fromname(zonename)
5338 for dt, shift, prev_abbr, abbr in tz.nondst_folds():
5339 if dt.year < start_year or same_abbr and prev_abbr != abbr:
5340 continue
5341 count += 1
5342 print("%3d) %-30s %s %10s %5s -> %s" %
5343 (count, zonename, dt, shift, prev_abbr, abbr))
5344
5345 def folds(self):
5346 for t, shift in self.transitions():
5347 if shift < ZERO:
5348 yield t, -shift
5349
5350 def gaps(self):
5351 for t, shift in self.transitions():
5352 if shift > ZERO:
5353 yield t, shift
5354
5355 def zeros(self):
5356 for t, shift in self.transitions():
5357 if not shift:
5358 yield t
5359
5360
5361class ZoneInfoTest(unittest.TestCase):
5362 zonename = 'America/New_York'
5363
5364 def setUp(self):
5365 if sys.platform == "win32":
5366 self.skipTest("Skipping zoneinfo tests on Windows")
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04005367 try:
5368 self.tz = ZoneInfo.fromname(self.zonename)
5369 except FileNotFoundError as err:
5370 self.skipTest("Skipping %s: %s" % (self.zonename, err))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005371
5372 def assertEquivDatetimes(self, a, b):
5373 self.assertEqual((a.replace(tzinfo=None), a.fold, id(a.tzinfo)),
5374 (b.replace(tzinfo=None), b.fold, id(b.tzinfo)))
5375
5376 def test_folds(self):
5377 tz = self.tz
5378 for dt, shift in tz.folds():
5379 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
5380 udt = dt + x
5381 ldt = tz.fromutc(udt.replace(tzinfo=tz))
5382 self.assertEqual(ldt.fold, 1)
5383 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
5384 self.assertEquivDatetimes(adt, ldt)
5385 utcoffset = ldt.utcoffset()
5386 self.assertEqual(ldt.replace(tzinfo=None), udt + utcoffset)
5387 # Round trip
5388 self.assertEquivDatetimes(ldt.astimezone(timezone.utc),
5389 udt.replace(tzinfo=timezone.utc))
5390
5391
5392 for x in [-timedelta.resolution, shift]:
5393 udt = dt + x
5394 udt = udt.replace(tzinfo=tz)
5395 ldt = tz.fromutc(udt)
5396 self.assertEqual(ldt.fold, 0)
5397
5398 def test_gaps(self):
5399 tz = self.tz
5400 for dt, shift in tz.gaps():
5401 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
5402 udt = dt + x
5403 udt = udt.replace(tzinfo=tz)
5404 ldt = tz.fromutc(udt)
5405 self.assertEqual(ldt.fold, 0)
5406 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
5407 self.assertEquivDatetimes(adt, ldt)
5408 utcoffset = ldt.utcoffset()
5409 self.assertEqual(ldt.replace(tzinfo=None), udt.replace(tzinfo=None) + utcoffset)
5410 # Create a local time inside the gap
5411 ldt = tz.fromutc(dt.replace(tzinfo=tz)) - shift + x
5412 self.assertLess(ldt.replace(fold=1).utcoffset(),
5413 ldt.replace(fold=0).utcoffset(),
5414 "At %s." % ldt)
5415
5416 for x in [-timedelta.resolution, shift]:
5417 udt = dt + x
5418 ldt = tz.fromutc(udt.replace(tzinfo=tz))
5419 self.assertEqual(ldt.fold, 0)
5420
5421 def test_system_transitions(self):
5422 if ('Riyadh8' in self.zonename or
5423 # From tzdata NEWS file:
5424 # The files solar87, solar88, and solar89 are no longer distributed.
5425 # They were a negative experiment - that is, a demonstration that
5426 # tz data can represent solar time only with some difficulty and error.
5427 # Their presence in the distribution caused confusion, as Riyadh
5428 # civil time was generally not solar time in those years.
5429 self.zonename.startswith('right/')):
5430 self.skipTest("Skipping %s" % self.zonename)
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04005431 tz = self.tz
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005432 TZ = os.environ.get('TZ')
5433 os.environ['TZ'] = self.zonename
5434 try:
5435 _time.tzset()
5436 for udt, shift in tz.transitions():
Alexander Belopolsky10c2dd22016-08-12 19:08:15 -04005437 if udt.year >= 2037:
5438 # System support for times around the end of 32-bit time_t
5439 # and later is flaky on many systems.
5440 break
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005441 s0 = (udt - datetime(1970, 1, 1)) // SEC
5442 ss = shift // SEC # shift seconds
5443 for x in [-40 * 3600, -20*3600, -1, 0,
5444 ss - 1, ss + 20 * 3600, ss + 40 * 3600]:
5445 s = s0 + x
5446 sdt = datetime.fromtimestamp(s)
5447 tzdt = datetime.fromtimestamp(s, tz).replace(tzinfo=None)
5448 self.assertEquivDatetimes(sdt, tzdt)
5449 s1 = sdt.timestamp()
5450 self.assertEqual(s, s1)
5451 if ss > 0: # gap
5452 # Create local time inside the gap
5453 dt = datetime.fromtimestamp(s0) - shift / 2
5454 ts0 = dt.timestamp()
5455 ts1 = dt.replace(fold=1).timestamp()
5456 self.assertEqual(ts0, s0 + ss / 2)
5457 self.assertEqual(ts1, s0 - ss / 2)
5458 finally:
5459 if TZ is None:
5460 del os.environ['TZ']
5461 else:
5462 os.environ['TZ'] = TZ
5463 _time.tzset()
5464
5465
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04005466class ZoneInfoCompleteTest(unittest.TestSuite):
5467 def __init__(self):
5468 tests = []
5469 if is_resource_enabled('tzdata'):
5470 for name in ZoneInfo.zonenames():
5471 Test = type('ZoneInfoTest[%s]' % name, (ZoneInfoTest,), {})
5472 Test.zonename = name
5473 for method in dir(Test):
5474 if method.startswith('test_'):
5475 tests.append(Test(method))
5476 super().__init__(tests)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005477
5478# Iran had a sub-minute UTC offset before 1946.
5479class IranTest(ZoneInfoTest):
Alexander Belopolsky07e2a0a2016-07-30 11:41:02 -04005480 zonename = 'Asia/Tehran'
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005481
Paul Ganssle04af5b12018-01-24 17:29:30 -05005482
5483class CapiTest(unittest.TestCase):
5484 def setUp(self):
5485 # Since the C API is not present in the _Pure tests, skip all tests
5486 if self.__class__.__name__.endswith('Pure'):
5487 self.skipTest('Not relevant in pure Python')
5488
5489 # This *must* be called, and it must be called first, so until either
5490 # restriction is loosened, we'll call it as part of test setup
5491 _testcapi.test_datetime_capi()
5492
5493 def test_utc_capi(self):
5494 for use_macro in (True, False):
5495 capi_utc = _testcapi.get_timezone_utc_capi(use_macro)
5496
5497 with self.subTest(use_macro=use_macro):
5498 self.assertIs(capi_utc, timezone.utc)
5499
5500 def test_timezones_capi(self):
5501 est_capi, est_macro, est_macro_nn = _testcapi.make_timezones_capi()
5502
5503 exp_named = timezone(timedelta(hours=-5), "EST")
5504 exp_unnamed = timezone(timedelta(hours=-5))
5505
5506 cases = [
5507 ('est_capi', est_capi, exp_named),
5508 ('est_macro', est_macro, exp_named),
5509 ('est_macro_nn', est_macro_nn, exp_unnamed)
5510 ]
5511
5512 for name, tz_act, tz_exp in cases:
5513 with self.subTest(name=name):
5514 self.assertEqual(tz_act, tz_exp)
5515
5516 dt1 = datetime(2000, 2, 4, tzinfo=tz_act)
5517 dt2 = datetime(2000, 2, 4, tzinfo=tz_exp)
5518
5519 self.assertEqual(dt1, dt2)
5520 self.assertEqual(dt1.tzname(), dt2.tzname())
5521
5522 dt_utc = datetime(2000, 2, 4, 5, tzinfo=timezone.utc)
5523
5524 self.assertEqual(dt1.astimezone(timezone.utc), dt_utc)
5525
Paul Gansslea049f572018-02-22 15:15:32 -05005526 def test_timezones_offset_zero(self):
5527 utc0, utc1, non_utc = _testcapi.get_timezones_offset_zero()
5528
5529 with self.subTest(testname="utc0"):
5530 self.assertIs(utc0, timezone.utc)
5531
5532 with self.subTest(testname="utc1"):
5533 self.assertIs(utc1, timezone.utc)
5534
5535 with self.subTest(testname="non_utc"):
5536 self.assertIsNot(non_utc, timezone.utc)
5537
5538 non_utc_exp = timezone(timedelta(hours=0), "")
5539
5540 self.assertEqual(non_utc, non_utc_exp)
5541
5542 dt1 = datetime(2000, 2, 4, tzinfo=non_utc)
5543 dt2 = datetime(2000, 2, 4, tzinfo=non_utc_exp)
5544
5545 self.assertEqual(dt1, dt2)
5546 self.assertEqual(dt1.tzname(), dt2.tzname())
5547
Paul Ganssle04af5b12018-01-24 17:29:30 -05005548 def test_check_date(self):
5549 class DateSubclass(date):
5550 pass
5551
5552 d = date(2011, 1, 1)
5553 ds = DateSubclass(2011, 1, 1)
5554 dt = datetime(2011, 1, 1)
5555
5556 is_date = _testcapi.datetime_check_date
5557
5558 # Check the ones that should be valid
5559 self.assertTrue(is_date(d))
5560 self.assertTrue(is_date(dt))
5561 self.assertTrue(is_date(ds))
5562 self.assertTrue(is_date(d, True))
5563
5564 # Check that the subclasses do not match exactly
5565 self.assertFalse(is_date(dt, True))
5566 self.assertFalse(is_date(ds, True))
5567
5568 # Check that various other things are not dates at all
5569 args = [tuple(), list(), 1, '2011-01-01',
5570 timedelta(1), timezone.utc, time(12, 00)]
5571 for arg in args:
5572 for exact in (True, False):
5573 with self.subTest(arg=arg, exact=exact):
5574 self.assertFalse(is_date(arg, exact))
5575
5576 def test_check_time(self):
5577 class TimeSubclass(time):
5578 pass
5579
5580 t = time(12, 30)
5581 ts = TimeSubclass(12, 30)
5582
5583 is_time = _testcapi.datetime_check_time
5584
5585 # Check the ones that should be valid
5586 self.assertTrue(is_time(t))
5587 self.assertTrue(is_time(ts))
5588 self.assertTrue(is_time(t, True))
5589
5590 # Check that the subclass does not match exactly
5591 self.assertFalse(is_time(ts, True))
5592
5593 # Check that various other things are not times
5594 args = [tuple(), list(), 1, '2011-01-01',
5595 timedelta(1), timezone.utc, date(2011, 1, 1)]
5596
5597 for arg in args:
5598 for exact in (True, False):
5599 with self.subTest(arg=arg, exact=exact):
5600 self.assertFalse(is_time(arg, exact))
5601
5602 def test_check_datetime(self):
5603 class DateTimeSubclass(datetime):
5604 pass
5605
5606 dt = datetime(2011, 1, 1, 12, 30)
5607 dts = DateTimeSubclass(2011, 1, 1, 12, 30)
5608
5609 is_datetime = _testcapi.datetime_check_datetime
5610
5611 # Check the ones that should be valid
5612 self.assertTrue(is_datetime(dt))
5613 self.assertTrue(is_datetime(dts))
5614 self.assertTrue(is_datetime(dt, True))
5615
5616 # Check that the subclass does not match exactly
5617 self.assertFalse(is_datetime(dts, True))
5618
5619 # Check that various other things are not datetimes
5620 args = [tuple(), list(), 1, '2011-01-01',
5621 timedelta(1), timezone.utc, date(2011, 1, 1)]
5622
5623 for arg in args:
5624 for exact in (True, False):
5625 with self.subTest(arg=arg, exact=exact):
5626 self.assertFalse(is_datetime(arg, exact))
5627
5628 def test_check_delta(self):
5629 class TimeDeltaSubclass(timedelta):
5630 pass
5631
5632 td = timedelta(1)
5633 tds = TimeDeltaSubclass(1)
5634
5635 is_timedelta = _testcapi.datetime_check_delta
5636
5637 # Check the ones that should be valid
5638 self.assertTrue(is_timedelta(td))
5639 self.assertTrue(is_timedelta(tds))
5640 self.assertTrue(is_timedelta(td, True))
5641
5642 # Check that the subclass does not match exactly
5643 self.assertFalse(is_timedelta(tds, True))
5644
5645 # Check that various other things are not timedeltas
5646 args = [tuple(), list(), 1, '2011-01-01',
5647 timezone.utc, date(2011, 1, 1), datetime(2011, 1, 1)]
5648
5649 for arg in args:
5650 for exact in (True, False):
5651 with self.subTest(arg=arg, exact=exact):
5652 self.assertFalse(is_timedelta(arg, exact))
5653
5654 def test_check_tzinfo(self):
5655 class TZInfoSubclass(tzinfo):
5656 pass
5657
5658 tzi = tzinfo()
5659 tzis = TZInfoSubclass()
5660 tz = timezone(timedelta(hours=-5))
5661
5662 is_tzinfo = _testcapi.datetime_check_tzinfo
5663
5664 # Check the ones that should be valid
5665 self.assertTrue(is_tzinfo(tzi))
5666 self.assertTrue(is_tzinfo(tz))
5667 self.assertTrue(is_tzinfo(tzis))
5668 self.assertTrue(is_tzinfo(tzi, True))
5669
5670 # Check that the subclasses do not match exactly
5671 self.assertFalse(is_tzinfo(tz, True))
5672 self.assertFalse(is_tzinfo(tzis, True))
5673
5674 # Check that various other things are not tzinfos
5675 args = [tuple(), list(), 1, '2011-01-01',
5676 date(2011, 1, 1), datetime(2011, 1, 1)]
5677
5678 for arg in args:
5679 for exact in (True, False):
5680 with self.subTest(arg=arg, exact=exact):
5681 self.assertFalse(is_tzinfo(arg, exact))
5682
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04005683def load_tests(loader, standard_tests, pattern):
5684 standard_tests.addTest(ZoneInfoCompleteTest())
5685 return standard_tests
5686
5687
Alexander Belopolskycf86e362010-07-23 19:25:47 +00005688if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -05005689 unittest.main()