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