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