blob: 9c6e71c54d79d3ece0719c4512ad4ab6d930e2aa [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',
Ammar Askar96d1e692018-07-25 09:54:58 -070073 'tzinfo', 'sys'])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -040074 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
Paul Ganssle096329f2018-08-23 11:06:20 -04001670 '\ud800', # bpo-34454: Surrogate code point
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001671 '009-03-04', # Not 10 characters
1672 '123456789', # Not a date
1673 '200a-12-04', # Invalid character in year
1674 '2009-1a-04', # Invalid character in month
1675 '2009-12-0a', # Invalid character in day
1676 '2009-01-32', # Invalid day
1677 '2009-02-29', # Invalid leap day
1678 '20090228', # Valid ISO8601 output not from isoformat()
Paul Ganssle096329f2018-08-23 11:06:20 -04001679 '2009\ud80002\ud80028', # Separators are surrogate codepoints
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001680 ]
1681
1682 for bad_str in bad_strs:
1683 with self.assertRaises(ValueError):
1684 self.theclass.fromisoformat(bad_str)
1685
1686 def test_fromisoformat_fails_typeerror(self):
1687 # Test that fromisoformat fails when passed the wrong type
1688 import io
1689
1690 bad_types = [b'2009-03-01', None, io.StringIO('2009-03-01')]
1691 for bad_type in bad_types:
1692 with self.assertRaises(TypeError):
1693 self.theclass.fromisoformat(bad_type)
1694
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001695#############################################################################
1696# datetime tests
1697
1698class SubclassDatetime(datetime):
1699 sub_var = 1
1700
1701class TestDateTime(TestDate):
1702
1703 theclass = datetime
1704
1705 def test_basic_attributes(self):
1706 dt = self.theclass(2002, 3, 1, 12, 0)
1707 self.assertEqual(dt.year, 2002)
1708 self.assertEqual(dt.month, 3)
1709 self.assertEqual(dt.day, 1)
1710 self.assertEqual(dt.hour, 12)
1711 self.assertEqual(dt.minute, 0)
1712 self.assertEqual(dt.second, 0)
1713 self.assertEqual(dt.microsecond, 0)
1714
1715 def test_basic_attributes_nonzero(self):
1716 # Make sure all attributes are non-zero so bugs in
1717 # bit-shifting access show up.
1718 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1719 self.assertEqual(dt.year, 2002)
1720 self.assertEqual(dt.month, 3)
1721 self.assertEqual(dt.day, 1)
1722 self.assertEqual(dt.hour, 12)
1723 self.assertEqual(dt.minute, 59)
1724 self.assertEqual(dt.second, 59)
1725 self.assertEqual(dt.microsecond, 8000)
1726
1727 def test_roundtrip(self):
1728 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1729 self.theclass.now()):
1730 # Verify dt -> string -> datetime identity.
1731 s = repr(dt)
1732 self.assertTrue(s.startswith('datetime.'))
1733 s = s[9:]
1734 dt2 = eval(s)
1735 self.assertEqual(dt, dt2)
1736
1737 # Verify identity via reconstructing from pieces.
1738 dt2 = self.theclass(dt.year, dt.month, dt.day,
1739 dt.hour, dt.minute, dt.second,
1740 dt.microsecond)
1741 self.assertEqual(dt, dt2)
1742
1743 def test_isoformat(self):
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001744 t = self.theclass(1, 2, 3, 4, 5, 1, 123)
1745 self.assertEqual(t.isoformat(), "0001-02-03T04:05:01.000123")
1746 self.assertEqual(t.isoformat('T'), "0001-02-03T04:05:01.000123")
1747 self.assertEqual(t.isoformat(' '), "0001-02-03 04:05:01.000123")
1748 self.assertEqual(t.isoformat('\x00'), "0001-02-03\x0004:05:01.000123")
1749 self.assertEqual(t.isoformat(timespec='hours'), "0001-02-03T04")
1750 self.assertEqual(t.isoformat(timespec='minutes'), "0001-02-03T04:05")
1751 self.assertEqual(t.isoformat(timespec='seconds'), "0001-02-03T04:05:01")
1752 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1753 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000123")
1754 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01.000123")
1755 self.assertEqual(t.isoformat(sep=' ', timespec='minutes'), "0001-02-03 04:05")
1756 self.assertRaises(ValueError, t.isoformat, timespec='foo')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001757 # str is ISO format with the separator forced to a blank.
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001758 self.assertEqual(str(t), "0001-02-03 04:05:01.000123")
1759
1760 t = self.theclass(1, 2, 3, 4, 5, 1, 999500, tzinfo=timezone.utc)
1761 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999+00:00")
1762
1763 t = self.theclass(1, 2, 3, 4, 5, 1, 999500)
1764 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999")
1765
1766 t = self.theclass(1, 2, 3, 4, 5, 1)
1767 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01")
1768 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1769 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000000")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001770
1771 t = self.theclass(2, 3, 2)
1772 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1773 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1774 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1775 # str is ISO format with the separator forced to a blank.
1776 self.assertEqual(str(t), "0002-03-02 00:00:00")
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001777 # ISO format with timezone
1778 tz = FixedOffset(timedelta(seconds=16), 'XXX')
1779 t = self.theclass(2, 3, 2, tzinfo=tz)
1780 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00+00:00:16")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001781
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001782 def test_isoformat_timezone(self):
1783 tzoffsets = [
1784 ('05:00', timedelta(hours=5)),
1785 ('02:00', timedelta(hours=2)),
1786 ('06:27', timedelta(hours=6, minutes=27)),
1787 ('12:32:30', timedelta(hours=12, minutes=32, seconds=30)),
1788 ('02:04:09.123456', timedelta(hours=2, minutes=4, seconds=9, microseconds=123456))
1789 ]
1790
1791 tzinfos = [
1792 ('', None),
1793 ('+00:00', timezone.utc),
1794 ('+00:00', timezone(timedelta(0))),
1795 ]
1796
1797 tzinfos += [
1798 (prefix + expected, timezone(sign * td))
1799 for expected, td in tzoffsets
1800 for prefix, sign in [('-', -1), ('+', 1)]
1801 ]
1802
1803 dt_base = self.theclass(2016, 4, 1, 12, 37, 9)
1804 exp_base = '2016-04-01T12:37:09'
1805
1806 for exp_tz, tzi in tzinfos:
1807 dt = dt_base.replace(tzinfo=tzi)
1808 exp = exp_base + exp_tz
1809 with self.subTest(tzi=tzi):
1810 assert dt.isoformat() == exp
1811
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001812 def test_format(self):
1813 dt = self.theclass(2007, 9, 10, 4, 5, 1, 123)
1814 self.assertEqual(dt.__format__(''), str(dt))
1815
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02001816 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001817 dt.__format__(123)
1818
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001819 # check that a derived class's __str__() gets called
1820 class A(self.theclass):
1821 def __str__(self):
1822 return 'A'
1823 a = A(2007, 9, 10, 4, 5, 1, 123)
1824 self.assertEqual(a.__format__(''), 'A')
1825
1826 # check that a derived class's strftime gets called
1827 class B(self.theclass):
1828 def strftime(self, format_spec):
1829 return 'B'
1830 b = B(2007, 9, 10, 4, 5, 1, 123)
1831 self.assertEqual(b.__format__(''), str(dt))
1832
1833 for fmt in ["m:%m d:%d y:%y",
1834 "m:%m d:%d y:%y H:%H M:%M S:%S",
1835 "%z %Z",
1836 ]:
1837 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1838 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1839 self.assertEqual(b.__format__(fmt), 'B')
1840
1841 def test_more_ctime(self):
1842 # Test fields that TestDate doesn't touch.
1843 import time
1844
1845 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1846 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1847 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1848 # out. The difference is that t.ctime() produces " 2" for the day,
1849 # but platform ctime() produces "02" for the day. According to
1850 # C99, t.ctime() is correct here.
1851 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1852
1853 # So test a case where that difference doesn't matter.
1854 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1855 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1856
1857 def test_tz_independent_comparing(self):
1858 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1859 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1860 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1861 self.assertEqual(dt1, dt3)
1862 self.assertTrue(dt2 > dt3)
1863
1864 # Make sure comparison doesn't forget microseconds, and isn't done
1865 # via comparing a float timestamp (an IEEE double doesn't have enough
Leo Ariasc3d95082018-02-03 18:36:10 -06001866 # precision to span microsecond resolution across years 1 through 9999,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001867 # so comparing via timestamp necessarily calls some distinct values
1868 # equal).
1869 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1870 us = timedelta(microseconds=1)
1871 dt2 = dt1 + us
1872 self.assertEqual(dt2 - dt1, us)
1873 self.assertTrue(dt1 < dt2)
1874
1875 def test_strftime_with_bad_tzname_replace(self):
1876 # verify ok if tzinfo.tzname().replace() returns a non-string
1877 class MyTzInfo(FixedOffset):
1878 def tzname(self, dt):
1879 class MyStr(str):
1880 def replace(self, *args):
1881 return None
1882 return MyStr('name')
1883 t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
1884 self.assertRaises(TypeError, t.strftime, '%Z')
1885
1886 def test_bad_constructor_arguments(self):
1887 # bad years
1888 self.theclass(MINYEAR, 1, 1) # no exception
1889 self.theclass(MAXYEAR, 1, 1) # no exception
1890 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1891 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1892 # bad months
1893 self.theclass(2000, 1, 1) # no exception
1894 self.theclass(2000, 12, 1) # no exception
1895 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1896 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1897 # bad days
1898 self.theclass(2000, 2, 29) # no exception
1899 self.theclass(2004, 2, 29) # no exception
1900 self.theclass(2400, 2, 29) # no exception
1901 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1902 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1903 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1904 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1905 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1906 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1907 # bad hours
1908 self.theclass(2000, 1, 31, 0) # no exception
1909 self.theclass(2000, 1, 31, 23) # no exception
1910 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1911 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1912 # bad minutes
1913 self.theclass(2000, 1, 31, 23, 0) # no exception
1914 self.theclass(2000, 1, 31, 23, 59) # no exception
1915 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1916 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1917 # bad seconds
1918 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1919 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1920 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1921 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1922 # bad microseconds
1923 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1924 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1925 self.assertRaises(ValueError, self.theclass,
1926 2000, 1, 31, 23, 59, 59, -1)
1927 self.assertRaises(ValueError, self.theclass,
1928 2000, 1, 31, 23, 59, 59,
1929 1000000)
Alexander Belopolsky47649ab2016-08-08 17:05:40 -04001930 # bad fold
1931 self.assertRaises(ValueError, self.theclass,
1932 2000, 1, 31, fold=-1)
1933 self.assertRaises(ValueError, self.theclass,
1934 2000, 1, 31, fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001935 # Positional fold:
1936 self.assertRaises(TypeError, self.theclass,
1937 2000, 1, 31, 23, 59, 59, 0, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04001938
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001939 def test_hash_equality(self):
1940 d = self.theclass(2000, 12, 31, 23, 30, 17)
1941 e = self.theclass(2000, 12, 31, 23, 30, 17)
1942 self.assertEqual(d, e)
1943 self.assertEqual(hash(d), hash(e))
1944
1945 dic = {d: 1}
1946 dic[e] = 2
1947 self.assertEqual(len(dic), 1)
1948 self.assertEqual(dic[d], 2)
1949 self.assertEqual(dic[e], 2)
1950
1951 d = self.theclass(2001, 1, 1, 0, 5, 17)
1952 e = self.theclass(2001, 1, 1, 0, 5, 17)
1953 self.assertEqual(d, e)
1954 self.assertEqual(hash(d), hash(e))
1955
1956 dic = {d: 1}
1957 dic[e] = 2
1958 self.assertEqual(len(dic), 1)
1959 self.assertEqual(dic[d], 2)
1960 self.assertEqual(dic[e], 2)
1961
1962 def test_computations(self):
1963 a = self.theclass(2002, 1, 31)
1964 b = self.theclass(1956, 1, 31)
1965 diff = a-b
1966 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1967 self.assertEqual(diff.seconds, 0)
1968 self.assertEqual(diff.microseconds, 0)
1969 a = self.theclass(2002, 3, 2, 17, 6)
1970 millisec = timedelta(0, 0, 1000)
1971 hour = timedelta(0, 3600)
1972 day = timedelta(1)
1973 week = timedelta(7)
1974 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1975 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1976 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1977 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1978 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1979 self.assertEqual(a - hour, a + -hour)
1980 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1981 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1982 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1983 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1984 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1985 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1986 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1987 self.assertEqual((a + week) - a, week)
1988 self.assertEqual((a + day) - a, day)
1989 self.assertEqual((a + hour) - a, hour)
1990 self.assertEqual((a + millisec) - a, millisec)
1991 self.assertEqual((a - week) - a, -week)
1992 self.assertEqual((a - day) - a, -day)
1993 self.assertEqual((a - hour) - a, -hour)
1994 self.assertEqual((a - millisec) - a, -millisec)
1995 self.assertEqual(a - (a + week), -week)
1996 self.assertEqual(a - (a + day), -day)
1997 self.assertEqual(a - (a + hour), -hour)
1998 self.assertEqual(a - (a + millisec), -millisec)
1999 self.assertEqual(a - (a - week), week)
2000 self.assertEqual(a - (a - day), day)
2001 self.assertEqual(a - (a - hour), hour)
2002 self.assertEqual(a - (a - millisec), millisec)
2003 self.assertEqual(a + (week + day + hour + millisec),
2004 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
2005 self.assertEqual(a + (week + day + hour + millisec),
2006 (((a + week) + day) + hour) + millisec)
2007 self.assertEqual(a - (week + day + hour + millisec),
2008 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
2009 self.assertEqual(a - (week + day + hour + millisec),
2010 (((a - week) - day) - hour) - millisec)
2011 # Add/sub ints or floats should be illegal
2012 for i in 1, 1.0:
2013 self.assertRaises(TypeError, lambda: a+i)
2014 self.assertRaises(TypeError, lambda: a-i)
2015 self.assertRaises(TypeError, lambda: i+a)
2016 self.assertRaises(TypeError, lambda: i-a)
2017
2018 # delta - datetime is senseless.
2019 self.assertRaises(TypeError, lambda: day - a)
2020 # mixing datetime and (delta or datetime) via * or // is senseless
2021 self.assertRaises(TypeError, lambda: day * a)
2022 self.assertRaises(TypeError, lambda: a * day)
2023 self.assertRaises(TypeError, lambda: day // a)
2024 self.assertRaises(TypeError, lambda: a // day)
2025 self.assertRaises(TypeError, lambda: a * a)
2026 self.assertRaises(TypeError, lambda: a // a)
2027 # datetime + datetime is senseless
2028 self.assertRaises(TypeError, lambda: a + a)
2029
2030 def test_pickling(self):
2031 args = 6, 7, 23, 20, 59, 1, 64**2
2032 orig = self.theclass(*args)
2033 for pickler, unpickler, proto in pickle_choices:
2034 green = pickler.dumps(orig, proto)
2035 derived = unpickler.loads(green)
2036 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02002037 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002038
2039 def test_more_pickling(self):
2040 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
Serhiy Storchakabad12572014-12-15 14:03:42 +02002041 for proto in range(pickle.HIGHEST_PROTOCOL + 1):
2042 s = pickle.dumps(a, proto)
2043 b = pickle.loads(s)
2044 self.assertEqual(b.year, 2003)
2045 self.assertEqual(b.month, 2)
2046 self.assertEqual(b.day, 7)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002047
2048 def test_pickling_subclass_datetime(self):
2049 args = 6, 7, 23, 20, 59, 1, 64**2
2050 orig = SubclassDatetime(*args)
2051 for pickler, unpickler, proto in pickle_choices:
2052 green = pickler.dumps(orig, proto)
2053 derived = unpickler.loads(green)
2054 self.assertEqual(orig, derived)
2055
2056 def test_more_compare(self):
2057 # The test_compare() inherited from TestDate covers the error cases.
2058 # We just want to test lexicographic ordering on the members datetime
2059 # has that date lacks.
2060 args = [2000, 11, 29, 20, 58, 16, 999998]
2061 t1 = self.theclass(*args)
2062 t2 = self.theclass(*args)
2063 self.assertEqual(t1, t2)
2064 self.assertTrue(t1 <= t2)
2065 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002066 self.assertFalse(t1 != t2)
2067 self.assertFalse(t1 < t2)
2068 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002069
2070 for i in range(len(args)):
2071 newargs = args[:]
2072 newargs[i] = args[i] + 1
2073 t2 = self.theclass(*newargs) # this is larger than t1
2074 self.assertTrue(t1 < t2)
2075 self.assertTrue(t2 > t1)
2076 self.assertTrue(t1 <= t2)
2077 self.assertTrue(t2 >= t1)
2078 self.assertTrue(t1 != t2)
2079 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002080 self.assertFalse(t1 == t2)
2081 self.assertFalse(t2 == t1)
2082 self.assertFalse(t1 > t2)
2083 self.assertFalse(t2 < t1)
2084 self.assertFalse(t1 >= t2)
2085 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002086
2087
2088 # A helper for timestamp constructor tests.
2089 def verify_field_equality(self, expected, got):
2090 self.assertEqual(expected.tm_year, got.year)
2091 self.assertEqual(expected.tm_mon, got.month)
2092 self.assertEqual(expected.tm_mday, got.day)
2093 self.assertEqual(expected.tm_hour, got.hour)
2094 self.assertEqual(expected.tm_min, got.minute)
2095 self.assertEqual(expected.tm_sec, got.second)
2096
2097 def test_fromtimestamp(self):
2098 import time
2099
2100 ts = time.time()
2101 expected = time.localtime(ts)
2102 got = self.theclass.fromtimestamp(ts)
2103 self.verify_field_equality(expected, got)
2104
2105 def test_utcfromtimestamp(self):
2106 import time
2107
2108 ts = time.time()
2109 expected = time.gmtime(ts)
2110 got = self.theclass.utcfromtimestamp(ts)
2111 self.verify_field_equality(expected, got)
2112
Alexander Belopolskya4415142012-06-08 12:33:09 -04002113 # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in
2114 # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0).
2115 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
2116 def test_timestamp_naive(self):
2117 t = self.theclass(1970, 1, 1)
2118 self.assertEqual(t.timestamp(), 18000.0)
2119 t = self.theclass(1970, 1, 1, 1, 2, 3, 4)
2120 self.assertEqual(t.timestamp(),
2121 18000.0 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002122 # Missing hour
2123 t0 = self.theclass(2012, 3, 11, 2, 30)
2124 t1 = t0.replace(fold=1)
2125 self.assertEqual(self.theclass.fromtimestamp(t1.timestamp()),
2126 t0 - timedelta(hours=1))
2127 self.assertEqual(self.theclass.fromtimestamp(t0.timestamp()),
2128 t1 + timedelta(hours=1))
Alexander Belopolskya4415142012-06-08 12:33:09 -04002129 # Ambiguous hour defaults to DST
2130 t = self.theclass(2012, 11, 4, 1, 30)
2131 self.assertEqual(self.theclass.fromtimestamp(t.timestamp()), t)
2132
2133 # Timestamp may raise an overflow error on some platforms
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002134 # XXX: Do we care to support the first and last year?
2135 for t in [self.theclass(2,1,1), self.theclass(9998,12,12)]:
Alexander Belopolskya4415142012-06-08 12:33:09 -04002136 try:
2137 s = t.timestamp()
2138 except OverflowError:
2139 pass
2140 else:
2141 self.assertEqual(self.theclass.fromtimestamp(s), t)
2142
2143 def test_timestamp_aware(self):
2144 t = self.theclass(1970, 1, 1, tzinfo=timezone.utc)
2145 self.assertEqual(t.timestamp(), 0.0)
2146 t = self.theclass(1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone.utc)
2147 self.assertEqual(t.timestamp(),
2148 3600 + 2*60 + 3 + 4*1e-6)
2149 t = self.theclass(1970, 1, 1, 1, 2, 3, 4,
2150 tzinfo=timezone(timedelta(hours=-5), 'EST'))
2151 self.assertEqual(t.timestamp(),
2152 18000 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002153
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002154 @support.run_with_tz('MSK-03') # Something east of Greenwich
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002155 def test_microsecond_rounding(self):
Victor Stinner5ebfe422015-09-18 14:52:15 +02002156 for fts in [self.theclass.fromtimestamp,
2157 self.theclass.utcfromtimestamp]:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002158 zero = fts(0)
2159 self.assertEqual(zero.second, 0)
2160 self.assertEqual(zero.microsecond, 0)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002161 one = fts(1e-6)
Victor Stinner8050ca92012-03-14 00:17:05 +01002162 try:
2163 minus_one = fts(-1e-6)
2164 except OSError:
2165 # localtime(-1) and gmtime(-1) is not supported on Windows
2166 pass
2167 else:
2168 self.assertEqual(minus_one.second, 59)
2169 self.assertEqual(minus_one.microsecond, 999999)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002170
Victor Stinner8050ca92012-03-14 00:17:05 +01002171 t = fts(-1e-8)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002172 self.assertEqual(t, zero)
Victor Stinner8050ca92012-03-14 00:17:05 +01002173 t = fts(-9e-7)
2174 self.assertEqual(t, minus_one)
2175 t = fts(-1e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002176 self.assertEqual(t, zero)
Victor Stinner8820a352015-09-05 10:50:20 +02002177 t = fts(-1/2**7)
2178 self.assertEqual(t.second, 59)
Victor Stinner7667f582015-09-09 01:02:23 +02002179 self.assertEqual(t.microsecond, 992188)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002180
2181 t = fts(1e-7)
2182 self.assertEqual(t, zero)
2183 t = fts(9e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002184 self.assertEqual(t, one)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002185 t = fts(0.99999949)
2186 self.assertEqual(t.second, 0)
2187 self.assertEqual(t.microsecond, 999999)
2188 t = fts(0.9999999)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002189 self.assertEqual(t.second, 1)
2190 self.assertEqual(t.microsecond, 0)
Victor Stinneradfefa52015-09-04 23:57:25 +02002191 t = fts(1/2**7)
2192 self.assertEqual(t.second, 0)
Victor Stinner7667f582015-09-09 01:02:23 +02002193 self.assertEqual(t.microsecond, 7812)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002194
Victor Stinnerb67f0962017-02-10 10:34:02 +01002195 def test_timestamp_limits(self):
2196 # minimum timestamp
2197 min_dt = self.theclass.min.replace(tzinfo=timezone.utc)
2198 min_ts = min_dt.timestamp()
Victor Stinner6f37e362017-02-10 11:45:14 +01002199 try:
2200 # date 0001-01-01 00:00:00+00:00: timestamp=-62135596800
2201 self.assertEqual(self.theclass.fromtimestamp(min_ts, tz=timezone.utc),
2202 min_dt)
Victor Stinner2a35c932017-02-10 12:37:21 +01002203 except (OverflowError, OSError) as exc:
2204 # the date 0001-01-01 doesn't fit into 32-bit time_t,
2205 # or platform doesn't support such very old date
Victor Stinner6f37e362017-02-10 11:45:14 +01002206 self.skipTest(str(exc))
Victor Stinnerb67f0962017-02-10 10:34:02 +01002207
2208 # maximum timestamp: set seconds to zero to avoid rounding issues
2209 max_dt = self.theclass.max.replace(tzinfo=timezone.utc,
2210 second=0, microsecond=0)
2211 max_ts = max_dt.timestamp()
2212 # date 9999-12-31 23:59:00+00:00: timestamp 253402300740
2213 self.assertEqual(self.theclass.fromtimestamp(max_ts, tz=timezone.utc),
2214 max_dt)
2215
2216 # number of seconds greater than 1 year: make sure that the new date
2217 # is not valid in datetime.datetime limits
2218 delta = 3600 * 24 * 400
2219
2220 # too small
2221 ts = min_ts - delta
2222 # converting a Python int to C time_t can raise a OverflowError,
2223 # especially on 32-bit platforms.
2224 with self.assertRaises((ValueError, OverflowError)):
2225 self.theclass.fromtimestamp(ts)
2226 with self.assertRaises((ValueError, OverflowError)):
2227 self.theclass.utcfromtimestamp(ts)
2228
2229 # too big
2230 ts = max_dt.timestamp() + delta
2231 with self.assertRaises((ValueError, OverflowError)):
2232 self.theclass.fromtimestamp(ts)
2233 with self.assertRaises((ValueError, OverflowError)):
2234 self.theclass.utcfromtimestamp(ts)
2235
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002236 def test_insane_fromtimestamp(self):
2237 # It's possible that some platform maps time_t to double,
2238 # and that this test will fail there. This test should
2239 # exempt such platforms (provided they return reasonable
2240 # results!).
2241 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002242 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002243 insane)
2244
2245 def test_insane_utcfromtimestamp(self):
2246 # It's possible that some platform maps time_t to double,
2247 # and that this test will fail there. This test should
2248 # exempt such platforms (provided they return reasonable
2249 # results!).
2250 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002251 self.assertRaises(OverflowError, self.theclass.utcfromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002252 insane)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002253
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002254 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2255 def test_negative_float_fromtimestamp(self):
2256 # The result is tz-dependent; at least test that this doesn't
2257 # fail (like it did before bug 1646728 was fixed).
2258 self.theclass.fromtimestamp(-1.05)
2259
2260 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2261 def test_negative_float_utcfromtimestamp(self):
2262 d = self.theclass.utcfromtimestamp(-1.05)
2263 self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
2264
2265 def test_utcnow(self):
2266 import time
2267
2268 # Call it a success if utcnow() and utcfromtimestamp() are within
2269 # a second of each other.
2270 tolerance = timedelta(seconds=1)
2271 for dummy in range(3):
2272 from_now = self.theclass.utcnow()
2273 from_timestamp = self.theclass.utcfromtimestamp(time.time())
2274 if abs(from_timestamp - from_now) <= tolerance:
2275 break
2276 # Else try again a few times.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002277 self.assertLessEqual(abs(from_timestamp - from_now), tolerance)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002278
2279 def test_strptime(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002280 string = '2004-12-01 13:02:47.197'
2281 format = '%Y-%m-%d %H:%M:%S.%f'
2282 expected = _strptime._strptime_datetime(self.theclass, string, format)
2283 got = self.theclass.strptime(string, format)
2284 self.assertEqual(expected, got)
2285 self.assertIs(type(expected), self.theclass)
2286 self.assertIs(type(got), self.theclass)
2287
2288 strptime = self.theclass.strptime
2289 self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
2290 self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
Mario Corchero32318932017-10-26 01:35:41 +01002291 self.assertEqual(
2292 strptime("-00:02:01.000003", "%z").utcoffset(),
2293 -timedelta(minutes=2, seconds=1, microseconds=3)
2294 )
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002295 # Only local timezone and UTC are supported
2296 for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
2297 (-_time.timezone, _time.tzname[0])):
2298 if tzseconds < 0:
2299 sign = '-'
2300 seconds = -tzseconds
2301 else:
2302 sign ='+'
2303 seconds = tzseconds
2304 hours, minutes = divmod(seconds//60, 60)
2305 dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
Martin Panterfca22322015-11-16 09:22:19 +00002306 dt = strptime(dtstr, "%z %Z")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002307 self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
2308 self.assertEqual(dt.tzname(), tzname)
2309 # Can produce inconsistent datetime
2310 dtstr, fmt = "+1234 UTC", "%z %Z"
2311 dt = strptime(dtstr, fmt)
2312 self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE)
2313 self.assertEqual(dt.tzname(), 'UTC')
2314 # yet will roundtrip
2315 self.assertEqual(dt.strftime(fmt), dtstr)
2316
2317 # Produce naive datetime if no %z is provided
2318 self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
2319
2320 with self.assertRaises(ValueError): strptime("-2400", "%z")
2321 with self.assertRaises(ValueError): strptime("-000", "%z")
2322
2323 def test_more_timetuple(self):
2324 # This tests fields beyond those tested by the TestDate.test_timetuple.
2325 t = self.theclass(2004, 12, 31, 6, 22, 33)
2326 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
2327 self.assertEqual(t.timetuple(),
2328 (t.year, t.month, t.day,
2329 t.hour, t.minute, t.second,
2330 t.weekday(),
2331 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
2332 -1))
2333 tt = t.timetuple()
2334 self.assertEqual(tt.tm_year, t.year)
2335 self.assertEqual(tt.tm_mon, t.month)
2336 self.assertEqual(tt.tm_mday, t.day)
2337 self.assertEqual(tt.tm_hour, t.hour)
2338 self.assertEqual(tt.tm_min, t.minute)
2339 self.assertEqual(tt.tm_sec, t.second)
2340 self.assertEqual(tt.tm_wday, t.weekday())
2341 self.assertEqual(tt.tm_yday, t.toordinal() -
2342 date(t.year, 1, 1).toordinal() + 1)
2343 self.assertEqual(tt.tm_isdst, -1)
2344
2345 def test_more_strftime(self):
2346 # This tests fields beyond those tested by the TestDate.test_strftime.
2347 t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
2348 self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
2349 "12 31 04 000047 33 22 06 366")
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04002350 for (s, us), z in [((33, 123), "33.000123"), ((33, 0), "33"),]:
2351 tz = timezone(-timedelta(hours=2, seconds=s, microseconds=us))
2352 t = t.replace(tzinfo=tz)
2353 self.assertEqual(t.strftime("%z"), "-0200" + z)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002354
2355 def test_extract(self):
2356 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2357 self.assertEqual(dt.date(), date(2002, 3, 4))
2358 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2359
2360 def test_combine(self):
2361 d = date(2002, 3, 4)
2362 t = time(18, 45, 3, 1234)
2363 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2364 combine = self.theclass.combine
2365 dt = combine(d, t)
2366 self.assertEqual(dt, expected)
2367
2368 dt = combine(time=t, date=d)
2369 self.assertEqual(dt, expected)
2370
2371 self.assertEqual(d, dt.date())
2372 self.assertEqual(t, dt.time())
2373 self.assertEqual(dt, combine(dt.date(), dt.time()))
2374
2375 self.assertRaises(TypeError, combine) # need an arg
2376 self.assertRaises(TypeError, combine, d) # need two args
2377 self.assertRaises(TypeError, combine, t, d) # args reversed
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002378 self.assertRaises(TypeError, combine, d, t, 1) # wrong tzinfo type
2379 self.assertRaises(TypeError, combine, d, t, 1, 2) # too many args
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002380 self.assertRaises(TypeError, combine, "date", "time") # wrong types
2381 self.assertRaises(TypeError, combine, d, "time") # wrong type
2382 self.assertRaises(TypeError, combine, "date", t) # wrong type
2383
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002384 # tzinfo= argument
2385 dt = combine(d, t, timezone.utc)
2386 self.assertIs(dt.tzinfo, timezone.utc)
2387 dt = combine(d, t, tzinfo=timezone.utc)
2388 self.assertIs(dt.tzinfo, timezone.utc)
2389 t = time()
2390 dt = combine(dt, t)
2391 self.assertEqual(dt.date(), d)
2392 self.assertEqual(dt.time(), t)
2393
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002394 def test_replace(self):
2395 cls = self.theclass
2396 args = [1, 2, 3, 4, 5, 6, 7]
2397 base = cls(*args)
2398 self.assertEqual(base, base.replace())
2399
2400 i = 0
2401 for name, newval in (("year", 2),
2402 ("month", 3),
2403 ("day", 4),
2404 ("hour", 5),
2405 ("minute", 6),
2406 ("second", 7),
2407 ("microsecond", 8)):
2408 newargs = args[:]
2409 newargs[i] = newval
2410 expected = cls(*newargs)
2411 got = base.replace(**{name: newval})
2412 self.assertEqual(expected, got)
2413 i += 1
2414
2415 # Out of bounds.
2416 base = cls(2000, 2, 29)
2417 self.assertRaises(ValueError, base.replace, year=2001)
2418
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002419 @support.run_with_tz('EDT4')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002420 def test_astimezone(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002421 dt = self.theclass.now()
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002422 f = FixedOffset(44, "0044")
2423 dt_utc = dt.replace(tzinfo=timezone(timedelta(hours=-4), 'EDT'))
2424 self.assertEqual(dt.astimezone(), dt_utc) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002425 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
2426 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002427 dt_f = dt.replace(tzinfo=f) + timedelta(hours=4, minutes=44)
2428 self.assertEqual(dt.astimezone(f), dt_f) # naive
2429 self.assertEqual(dt.astimezone(tz=f), dt_f) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002430
2431 class Bogus(tzinfo):
2432 def utcoffset(self, dt): return None
2433 def dst(self, dt): return timedelta(0)
2434 bog = Bogus()
2435 self.assertRaises(ValueError, dt.astimezone, bog) # naive
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002436 self.assertEqual(dt.replace(tzinfo=bog).astimezone(f), dt_f)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002437
2438 class AlsoBogus(tzinfo):
2439 def utcoffset(self, dt): return timedelta(0)
2440 def dst(self, dt): return None
2441 alsobog = AlsoBogus()
2442 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
2443
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002444 class Broken(tzinfo):
2445 def utcoffset(self, dt): return 1
2446 def dst(self, dt): return 1
2447 broken = Broken()
2448 dt_broken = dt.replace(tzinfo=broken)
2449 with self.assertRaises(TypeError):
2450 dt_broken.astimezone()
2451
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002452 def test_subclass_datetime(self):
2453
2454 class C(self.theclass):
2455 theAnswer = 42
2456
2457 def __new__(cls, *args, **kws):
2458 temp = kws.copy()
2459 extra = temp.pop('extra')
2460 result = self.theclass.__new__(cls, *args, **temp)
2461 result.extra = extra
2462 return result
2463
2464 def newmeth(self, start):
2465 return start + self.year + self.month + self.second
2466
2467 args = 2003, 4, 14, 12, 13, 41
2468
2469 dt1 = self.theclass(*args)
2470 dt2 = C(*args, **{'extra': 7})
2471
2472 self.assertEqual(dt2.__class__, C)
2473 self.assertEqual(dt2.theAnswer, 42)
2474 self.assertEqual(dt2.extra, 7)
2475 self.assertEqual(dt1.toordinal(), dt2.toordinal())
2476 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
2477 dt1.second - 7)
2478
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002479 def test_subclass_alternate_constructors_datetime(self):
2480 # Test that alternate constructors call the constructor
2481 class DateTimeSubclass(self.theclass):
2482 def __new__(cls, *args, **kwargs):
2483 result = self.theclass.__new__(cls, *args, **kwargs)
2484 result.extra = 7
2485
2486 return result
2487
2488 args = (2003, 4, 14, 12, 30, 15, 123456)
2489 d_isoformat = '2003-04-14T12:30:15.123456' # Equivalent isoformat()
2490 utc_ts = 1050323415.123456 # UTC timestamp
2491
2492 base_d = DateTimeSubclass(*args)
2493 self.assertIsInstance(base_d, DateTimeSubclass)
2494 self.assertEqual(base_d.extra, 7)
2495
2496 # Timestamp depends on time zone, so we'll calculate the equivalent here
2497 ts = base_d.timestamp()
2498
2499 test_cases = [
2500 ('fromtimestamp', (ts,)),
2501 # See https://bugs.python.org/issue32417
2502 # ('fromtimestamp', (ts, timezone.utc)),
2503 ('utcfromtimestamp', (utc_ts,)),
2504 ('fromisoformat', (d_isoformat,)),
2505 ('strptime', (d_isoformat, '%Y-%m-%dT%H:%M:%S.%f')),
2506 ('combine', (date(*args[0:3]), time(*args[3:]))),
2507 ]
2508
2509 for constr_name, constr_args in test_cases:
2510 for base_obj in (DateTimeSubclass, base_d):
2511 # Test both the classmethod and method
2512 with self.subTest(base_obj_type=type(base_obj),
2513 constr_name=constr_name):
2514 constr = getattr(base_obj, constr_name)
2515
2516 dt = constr(*constr_args)
2517
2518 # Test that it creates the right subclass
2519 self.assertIsInstance(dt, DateTimeSubclass)
2520
2521 # Test that it's equal to the base object
2522 self.assertEqual(dt, base_d.replace(tzinfo=None))
2523
2524 # Test that it called the constructor
2525 self.assertEqual(dt.extra, 7)
2526
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002527 def test_fromisoformat_datetime(self):
2528 # Test that isoformat() is reversible
2529 base_dates = [
2530 (1, 1, 1),
2531 (1900, 1, 1),
2532 (2004, 11, 12),
2533 (2017, 5, 30)
2534 ]
2535
2536 base_times = [
2537 (0, 0, 0, 0),
2538 (0, 0, 0, 241000),
2539 (0, 0, 0, 234567),
2540 (12, 30, 45, 234567)
2541 ]
2542
2543 separators = [' ', 'T']
2544
2545 tzinfos = [None, timezone.utc,
2546 timezone(timedelta(hours=-5)),
2547 timezone(timedelta(hours=2))]
2548
2549 dts = [self.theclass(*date_tuple, *time_tuple, tzinfo=tzi)
2550 for date_tuple in base_dates
2551 for time_tuple in base_times
2552 for tzi in tzinfos]
2553
2554 for dt in dts:
2555 for sep in separators:
2556 dtstr = dt.isoformat(sep=sep)
2557
2558 with self.subTest(dtstr=dtstr):
2559 dt_rt = self.theclass.fromisoformat(dtstr)
2560 self.assertEqual(dt, dt_rt)
2561
2562 def test_fromisoformat_timezone(self):
2563 base_dt = self.theclass(2014, 12, 30, 12, 30, 45, 217456)
2564
2565 tzoffsets = [
2566 timedelta(hours=5), timedelta(hours=2),
2567 timedelta(hours=6, minutes=27),
2568 timedelta(hours=12, minutes=32, seconds=30),
2569 timedelta(hours=2, minutes=4, seconds=9, microseconds=123456)
2570 ]
2571
2572 tzoffsets += [-1 * td for td in tzoffsets]
2573
2574 tzinfos = [None, timezone.utc,
2575 timezone(timedelta(hours=0))]
2576
2577 tzinfos += [timezone(td) for td in tzoffsets]
2578
2579 for tzi in tzinfos:
2580 dt = base_dt.replace(tzinfo=tzi)
2581 dtstr = dt.isoformat()
2582
2583 with self.subTest(tstr=dtstr):
2584 dt_rt = self.theclass.fromisoformat(dtstr)
2585 assert dt == dt_rt, dt_rt
2586
2587 def test_fromisoformat_separators(self):
2588 separators = [
2589 ' ', 'T', '\u007f', # 1-bit widths
2590 '\u0080', 'ʁ', # 2-bit widths
2591 'ᛇ', '時', # 3-bit widths
Paul Ganssle096329f2018-08-23 11:06:20 -04002592 '🐍', # 4-bit widths
2593 '\ud800', # bpo-34454: Surrogate code point
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002594 ]
2595
2596 for sep in separators:
2597 dt = self.theclass(2018, 1, 31, 23, 59, 47, 124789)
2598 dtstr = dt.isoformat(sep=sep)
2599
2600 with self.subTest(dtstr=dtstr):
2601 dt_rt = self.theclass.fromisoformat(dtstr)
2602 self.assertEqual(dt, dt_rt)
2603
2604 def test_fromisoformat_ambiguous(self):
2605 # Test strings like 2018-01-31+12:15 (where +12:15 is not a time zone)
2606 separators = ['+', '-']
2607 for sep in separators:
2608 dt = self.theclass(2018, 1, 31, 12, 15)
2609 dtstr = dt.isoformat(sep=sep)
2610
2611 with self.subTest(dtstr=dtstr):
2612 dt_rt = self.theclass.fromisoformat(dtstr)
2613 self.assertEqual(dt, dt_rt)
2614
2615 def test_fromisoformat_timespecs(self):
2616 datetime_bases = [
2617 (2009, 12, 4, 8, 17, 45, 123456),
2618 (2009, 12, 4, 8, 17, 45, 0)]
2619
2620 tzinfos = [None, timezone.utc,
2621 timezone(timedelta(hours=-5)),
2622 timezone(timedelta(hours=2)),
2623 timezone(timedelta(hours=6, minutes=27))]
2624
2625 timespecs = ['hours', 'minutes', 'seconds',
2626 'milliseconds', 'microseconds']
2627
2628 for ip, ts in enumerate(timespecs):
2629 for tzi in tzinfos:
2630 for dt_tuple in datetime_bases:
2631 if ts == 'milliseconds':
2632 new_microseconds = 1000 * (dt_tuple[6] // 1000)
2633 dt_tuple = dt_tuple[0:6] + (new_microseconds,)
2634
2635 dt = self.theclass(*(dt_tuple[0:(4 + ip)]), tzinfo=tzi)
2636 dtstr = dt.isoformat(timespec=ts)
2637 with self.subTest(dtstr=dtstr):
2638 dt_rt = self.theclass.fromisoformat(dtstr)
2639 self.assertEqual(dt, dt_rt)
2640
2641 def test_fromisoformat_fails_datetime(self):
2642 # Test that fromisoformat() fails on invalid values
2643 bad_strs = [
2644 '', # Empty string
Paul Ganssle096329f2018-08-23 11:06:20 -04002645 '\ud800', # bpo-34454: Surrogate code point
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002646 '2009.04-19T03', # Wrong first separator
2647 '2009-04.19T03', # Wrong second separator
2648 '2009-04-19T0a', # Invalid hours
2649 '2009-04-19T03:1a:45', # Invalid minutes
2650 '2009-04-19T03:15:4a', # Invalid seconds
2651 '2009-04-19T03;15:45', # Bad first time separator
2652 '2009-04-19T03:15;45', # Bad second time separator
2653 '2009-04-19T03:15:4500:00', # Bad time zone separator
2654 '2009-04-19T03:15:45.2345', # Too many digits for milliseconds
2655 '2009-04-19T03:15:45.1234567', # Too many digits for microseconds
2656 '2009-04-19T03:15:45.123456+24:30', # Invalid time zone offset
2657 '2009-04-19T03:15:45.123456-24:30', # Invalid negative offset
2658 '2009-04-10ᛇᛇᛇᛇᛇ12:15', # Too many unicode separators
Paul Ganssle096329f2018-08-23 11:06:20 -04002659 '2009-04\ud80010T12:15', # Surrogate char in date
2660 '2009-04-10T12\ud80015', # Surrogate char in time
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002661 '2009-04-19T1', # Incomplete hours
2662 '2009-04-19T12:3', # Incomplete minutes
2663 '2009-04-19T12:30:4', # Incomplete seconds
2664 '2009-04-19T12:', # Ends with time separator
2665 '2009-04-19T12:30:', # Ends with time separator
2666 '2009-04-19T12:30:45.', # Ends with time separator
2667 '2009-04-19T12:30:45.123456+', # Ends with timzone separator
2668 '2009-04-19T12:30:45.123456-', # Ends with timzone separator
2669 '2009-04-19T12:30:45.123456-05:00a', # Extra text
2670 '2009-04-19T12:30:45.123-05:00a', # Extra text
2671 '2009-04-19T12:30:45-05:00a', # Extra text
2672 ]
2673
2674 for bad_str in bad_strs:
2675 with self.subTest(bad_str=bad_str):
2676 with self.assertRaises(ValueError):
2677 self.theclass.fromisoformat(bad_str)
2678
2679 def test_fromisoformat_utc(self):
2680 dt_str = '2014-04-19T13:21:13+00:00'
2681 dt = self.theclass.fromisoformat(dt_str)
2682
2683 self.assertIs(dt.tzinfo, timezone.utc)
2684
2685 def test_fromisoformat_subclass(self):
2686 class DateTimeSubclass(self.theclass):
2687 pass
2688
2689 dt = DateTimeSubclass(2014, 12, 14, 9, 30, 45, 457390,
2690 tzinfo=timezone(timedelta(hours=10, minutes=45)))
2691
2692 dt_rt = DateTimeSubclass.fromisoformat(dt.isoformat())
2693
2694 self.assertEqual(dt, dt_rt)
2695 self.assertIsInstance(dt_rt, DateTimeSubclass)
2696
2697
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002698class TestSubclassDateTime(TestDateTime):
2699 theclass = SubclassDatetime
2700 # Override tests not designed for subclass
Zachary Ware9fe6d862013-12-08 00:20:35 -06002701 @unittest.skip('not appropriate for subclasses')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002702 def test_roundtrip(self):
2703 pass
2704
2705class SubclassTime(time):
2706 sub_var = 1
2707
2708class TestTime(HarmlessMixedComparison, unittest.TestCase):
2709
2710 theclass = time
2711
2712 def test_basic_attributes(self):
2713 t = self.theclass(12, 0)
2714 self.assertEqual(t.hour, 12)
2715 self.assertEqual(t.minute, 0)
2716 self.assertEqual(t.second, 0)
2717 self.assertEqual(t.microsecond, 0)
2718
2719 def test_basic_attributes_nonzero(self):
2720 # Make sure all attributes are non-zero so bugs in
2721 # bit-shifting access show up.
2722 t = self.theclass(12, 59, 59, 8000)
2723 self.assertEqual(t.hour, 12)
2724 self.assertEqual(t.minute, 59)
2725 self.assertEqual(t.second, 59)
2726 self.assertEqual(t.microsecond, 8000)
2727
2728 def test_roundtrip(self):
2729 t = self.theclass(1, 2, 3, 4)
2730
2731 # Verify t -> string -> time identity.
2732 s = repr(t)
2733 self.assertTrue(s.startswith('datetime.'))
2734 s = s[9:]
2735 t2 = eval(s)
2736 self.assertEqual(t, t2)
2737
2738 # Verify identity via reconstructing from pieces.
2739 t2 = self.theclass(t.hour, t.minute, t.second,
2740 t.microsecond)
2741 self.assertEqual(t, t2)
2742
2743 def test_comparing(self):
2744 args = [1, 2, 3, 4]
2745 t1 = self.theclass(*args)
2746 t2 = self.theclass(*args)
2747 self.assertEqual(t1, t2)
2748 self.assertTrue(t1 <= t2)
2749 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002750 self.assertFalse(t1 != t2)
2751 self.assertFalse(t1 < t2)
2752 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002753
2754 for i in range(len(args)):
2755 newargs = args[:]
2756 newargs[i] = args[i] + 1
2757 t2 = self.theclass(*newargs) # this is larger than t1
2758 self.assertTrue(t1 < t2)
2759 self.assertTrue(t2 > t1)
2760 self.assertTrue(t1 <= t2)
2761 self.assertTrue(t2 >= t1)
2762 self.assertTrue(t1 != t2)
2763 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002764 self.assertFalse(t1 == t2)
2765 self.assertFalse(t2 == t1)
2766 self.assertFalse(t1 > t2)
2767 self.assertFalse(t2 < t1)
2768 self.assertFalse(t1 >= t2)
2769 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002770
2771 for badarg in OTHERSTUFF:
2772 self.assertEqual(t1 == badarg, False)
2773 self.assertEqual(t1 != badarg, True)
2774 self.assertEqual(badarg == t1, False)
2775 self.assertEqual(badarg != t1, True)
2776
2777 self.assertRaises(TypeError, lambda: t1 <= badarg)
2778 self.assertRaises(TypeError, lambda: t1 < badarg)
2779 self.assertRaises(TypeError, lambda: t1 > badarg)
2780 self.assertRaises(TypeError, lambda: t1 >= badarg)
2781 self.assertRaises(TypeError, lambda: badarg <= t1)
2782 self.assertRaises(TypeError, lambda: badarg < t1)
2783 self.assertRaises(TypeError, lambda: badarg > t1)
2784 self.assertRaises(TypeError, lambda: badarg >= t1)
2785
2786 def test_bad_constructor_arguments(self):
2787 # bad hours
2788 self.theclass(0, 0) # no exception
2789 self.theclass(23, 0) # no exception
2790 self.assertRaises(ValueError, self.theclass, -1, 0)
2791 self.assertRaises(ValueError, self.theclass, 24, 0)
2792 # bad minutes
2793 self.theclass(23, 0) # no exception
2794 self.theclass(23, 59) # no exception
2795 self.assertRaises(ValueError, self.theclass, 23, -1)
2796 self.assertRaises(ValueError, self.theclass, 23, 60)
2797 # bad seconds
2798 self.theclass(23, 59, 0) # no exception
2799 self.theclass(23, 59, 59) # no exception
2800 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
2801 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
2802 # bad microseconds
2803 self.theclass(23, 59, 59, 0) # no exception
2804 self.theclass(23, 59, 59, 999999) # no exception
2805 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
2806 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
2807
2808 def test_hash_equality(self):
2809 d = self.theclass(23, 30, 17)
2810 e = self.theclass(23, 30, 17)
2811 self.assertEqual(d, e)
2812 self.assertEqual(hash(d), hash(e))
2813
2814 dic = {d: 1}
2815 dic[e] = 2
2816 self.assertEqual(len(dic), 1)
2817 self.assertEqual(dic[d], 2)
2818 self.assertEqual(dic[e], 2)
2819
2820 d = self.theclass(0, 5, 17)
2821 e = self.theclass(0, 5, 17)
2822 self.assertEqual(d, e)
2823 self.assertEqual(hash(d), hash(e))
2824
2825 dic = {d: 1}
2826 dic[e] = 2
2827 self.assertEqual(len(dic), 1)
2828 self.assertEqual(dic[d], 2)
2829 self.assertEqual(dic[e], 2)
2830
2831 def test_isoformat(self):
2832 t = self.theclass(4, 5, 1, 123)
2833 self.assertEqual(t.isoformat(), "04:05:01.000123")
2834 self.assertEqual(t.isoformat(), str(t))
2835
2836 t = self.theclass()
2837 self.assertEqual(t.isoformat(), "00:00:00")
2838 self.assertEqual(t.isoformat(), str(t))
2839
2840 t = self.theclass(microsecond=1)
2841 self.assertEqual(t.isoformat(), "00:00:00.000001")
2842 self.assertEqual(t.isoformat(), str(t))
2843
2844 t = self.theclass(microsecond=10)
2845 self.assertEqual(t.isoformat(), "00:00:00.000010")
2846 self.assertEqual(t.isoformat(), str(t))
2847
2848 t = self.theclass(microsecond=100)
2849 self.assertEqual(t.isoformat(), "00:00:00.000100")
2850 self.assertEqual(t.isoformat(), str(t))
2851
2852 t = self.theclass(microsecond=1000)
2853 self.assertEqual(t.isoformat(), "00:00:00.001000")
2854 self.assertEqual(t.isoformat(), str(t))
2855
2856 t = self.theclass(microsecond=10000)
2857 self.assertEqual(t.isoformat(), "00:00:00.010000")
2858 self.assertEqual(t.isoformat(), str(t))
2859
2860 t = self.theclass(microsecond=100000)
2861 self.assertEqual(t.isoformat(), "00:00:00.100000")
2862 self.assertEqual(t.isoformat(), str(t))
2863
Alexander Belopolskya2998a62016-03-06 14:58:43 -05002864 t = self.theclass(hour=12, minute=34, second=56, microsecond=123456)
2865 self.assertEqual(t.isoformat(timespec='hours'), "12")
2866 self.assertEqual(t.isoformat(timespec='minutes'), "12:34")
2867 self.assertEqual(t.isoformat(timespec='seconds'), "12:34:56")
2868 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.123")
2869 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.123456")
2870 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56.123456")
2871 self.assertRaises(ValueError, t.isoformat, timespec='monkey')
2872
2873 t = self.theclass(hour=12, minute=34, second=56, microsecond=999500)
2874 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.999")
2875
2876 t = self.theclass(hour=12, minute=34, second=56, microsecond=0)
2877 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.000")
2878 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.000000")
2879 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56")
2880
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002881 def test_isoformat_timezone(self):
2882 tzoffsets = [
2883 ('05:00', timedelta(hours=5)),
2884 ('02:00', timedelta(hours=2)),
2885 ('06:27', timedelta(hours=6, minutes=27)),
2886 ('12:32:30', timedelta(hours=12, minutes=32, seconds=30)),
2887 ('02:04:09.123456', timedelta(hours=2, minutes=4, seconds=9, microseconds=123456))
2888 ]
2889
2890 tzinfos = [
2891 ('', None),
2892 ('+00:00', timezone.utc),
2893 ('+00:00', timezone(timedelta(0))),
2894 ]
2895
2896 tzinfos += [
2897 (prefix + expected, timezone(sign * td))
2898 for expected, td in tzoffsets
2899 for prefix, sign in [('-', -1), ('+', 1)]
2900 ]
2901
2902 t_base = self.theclass(12, 37, 9)
2903 exp_base = '12:37:09'
2904
2905 for exp_tz, tzi in tzinfos:
2906 t = t_base.replace(tzinfo=tzi)
2907 exp = exp_base + exp_tz
2908 with self.subTest(tzi=tzi):
2909 assert t.isoformat() == exp
2910
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002911 def test_1653736(self):
2912 # verify it doesn't accept extra keyword arguments
2913 t = self.theclass(second=1)
2914 self.assertRaises(TypeError, t.isoformat, foo=3)
2915
2916 def test_strftime(self):
2917 t = self.theclass(1, 2, 3, 4)
2918 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
2919 # A naive object replaces %z and %Z with empty strings.
2920 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
2921
2922 def test_format(self):
2923 t = self.theclass(1, 2, 3, 4)
2924 self.assertEqual(t.__format__(''), str(t))
2925
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02002926 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002927 t.__format__(123)
2928
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002929 # check that a derived class's __str__() gets called
2930 class A(self.theclass):
2931 def __str__(self):
2932 return 'A'
2933 a = A(1, 2, 3, 4)
2934 self.assertEqual(a.__format__(''), 'A')
2935
2936 # check that a derived class's strftime gets called
2937 class B(self.theclass):
2938 def strftime(self, format_spec):
2939 return 'B'
2940 b = B(1, 2, 3, 4)
2941 self.assertEqual(b.__format__(''), str(t))
2942
2943 for fmt in ['%H %M %S',
2944 ]:
2945 self.assertEqual(t.__format__(fmt), t.strftime(fmt))
2946 self.assertEqual(a.__format__(fmt), t.strftime(fmt))
2947 self.assertEqual(b.__format__(fmt), 'B')
2948
2949 def test_str(self):
2950 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
2951 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
2952 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
2953 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
2954 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
2955
2956 def test_repr(self):
2957 name = 'datetime.' + self.theclass.__name__
2958 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
2959 "%s(1, 2, 3, 4)" % name)
2960 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
2961 "%s(10, 2, 3, 4000)" % name)
2962 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
2963 "%s(0, 2, 3, 400000)" % name)
2964 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
2965 "%s(12, 2, 3)" % name)
2966 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
2967 "%s(23, 15)" % name)
2968
2969 def test_resolution_info(self):
2970 self.assertIsInstance(self.theclass.min, self.theclass)
2971 self.assertIsInstance(self.theclass.max, self.theclass)
2972 self.assertIsInstance(self.theclass.resolution, timedelta)
2973 self.assertTrue(self.theclass.max > self.theclass.min)
2974
2975 def test_pickling(self):
2976 args = 20, 59, 16, 64**2
2977 orig = self.theclass(*args)
2978 for pickler, unpickler, proto in pickle_choices:
2979 green = pickler.dumps(orig, proto)
2980 derived = unpickler.loads(green)
2981 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02002982 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002983
2984 def test_pickling_subclass_time(self):
2985 args = 20, 59, 16, 64**2
2986 orig = SubclassTime(*args)
2987 for pickler, unpickler, proto in pickle_choices:
2988 green = pickler.dumps(orig, proto)
2989 derived = unpickler.loads(green)
2990 self.assertEqual(orig, derived)
2991
2992 def test_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002993 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002994 cls = self.theclass
2995 self.assertTrue(cls(1))
2996 self.assertTrue(cls(0, 1))
2997 self.assertTrue(cls(0, 0, 1))
2998 self.assertTrue(cls(0, 0, 0, 1))
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002999 self.assertTrue(cls(0))
3000 self.assertTrue(cls())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003001
3002 def test_replace(self):
3003 cls = self.theclass
3004 args = [1, 2, 3, 4]
3005 base = cls(*args)
3006 self.assertEqual(base, base.replace())
3007
3008 i = 0
3009 for name, newval in (("hour", 5),
3010 ("minute", 6),
3011 ("second", 7),
3012 ("microsecond", 8)):
3013 newargs = args[:]
3014 newargs[i] = newval
3015 expected = cls(*newargs)
3016 got = base.replace(**{name: newval})
3017 self.assertEqual(expected, got)
3018 i += 1
3019
3020 # Out of bounds.
3021 base = cls(1)
3022 self.assertRaises(ValueError, base.replace, hour=24)
3023 self.assertRaises(ValueError, base.replace, minute=-1)
3024 self.assertRaises(ValueError, base.replace, second=100)
3025 self.assertRaises(ValueError, base.replace, microsecond=1000000)
3026
Paul Ganssle191e9932017-11-09 16:34:29 -05003027 def test_subclass_replace(self):
3028 class TimeSubclass(self.theclass):
3029 pass
3030
3031 ctime = TimeSubclass(12, 30)
3032 self.assertIs(type(ctime.replace(hour=10)), TimeSubclass)
3033
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003034 def test_subclass_time(self):
3035
3036 class C(self.theclass):
3037 theAnswer = 42
3038
3039 def __new__(cls, *args, **kws):
3040 temp = kws.copy()
3041 extra = temp.pop('extra')
3042 result = self.theclass.__new__(cls, *args, **temp)
3043 result.extra = extra
3044 return result
3045
3046 def newmeth(self, start):
3047 return start + self.hour + self.second
3048
3049 args = 4, 5, 6
3050
3051 dt1 = self.theclass(*args)
3052 dt2 = C(*args, **{'extra': 7})
3053
3054 self.assertEqual(dt2.__class__, C)
3055 self.assertEqual(dt2.theAnswer, 42)
3056 self.assertEqual(dt2.extra, 7)
3057 self.assertEqual(dt1.isoformat(), dt2.isoformat())
3058 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
3059
3060 def test_backdoor_resistance(self):
3061 # see TestDate.test_backdoor_resistance().
3062 base = '2:59.0'
3063 for hour_byte in ' ', '9', chr(24), '\xff':
3064 self.assertRaises(TypeError, self.theclass,
3065 hour_byte + base[1:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04003066 # Good bytes, but bad tzinfo:
3067 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
3068 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003069
3070# A mixin for classes with a tzinfo= argument. Subclasses must define
Martin Panter46f50722016-05-26 05:35:26 +00003071# theclass as a class attribute, and theclass(1, 1, 1, tzinfo=whatever)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003072# must be legit (which is true for time and datetime).
3073class TZInfoBase:
3074
3075 def test_argument_passing(self):
3076 cls = self.theclass
3077 # A datetime passes itself on, a time passes None.
3078 class introspective(tzinfo):
3079 def tzname(self, dt): return dt and "real" or "none"
3080 def utcoffset(self, dt):
3081 return timedelta(minutes = dt and 42 or -42)
3082 dst = utcoffset
3083
3084 obj = cls(1, 2, 3, tzinfo=introspective())
3085
3086 expected = cls is time and "none" or "real"
3087 self.assertEqual(obj.tzname(), expected)
3088
3089 expected = timedelta(minutes=(cls is time and -42 or 42))
3090 self.assertEqual(obj.utcoffset(), expected)
3091 self.assertEqual(obj.dst(), expected)
3092
3093 def test_bad_tzinfo_classes(self):
3094 cls = self.theclass
3095 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
3096
3097 class NiceTry(object):
3098 def __init__(self): pass
3099 def utcoffset(self, dt): pass
3100 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
3101
3102 class BetterTry(tzinfo):
3103 def __init__(self): pass
3104 def utcoffset(self, dt): pass
3105 b = BetterTry()
3106 t = cls(1, 1, 1, tzinfo=b)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003107 self.assertIs(t.tzinfo, b)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003108
3109 def test_utc_offset_out_of_bounds(self):
3110 class Edgy(tzinfo):
3111 def __init__(self, offset):
3112 self.offset = timedelta(minutes=offset)
3113 def utcoffset(self, dt):
3114 return self.offset
3115
3116 cls = self.theclass
3117 for offset, legit in ((-1440, False),
3118 (-1439, True),
3119 (1439, True),
3120 (1440, False)):
3121 if cls is time:
3122 t = cls(1, 2, 3, tzinfo=Edgy(offset))
3123 elif cls is datetime:
3124 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
3125 else:
3126 assert 0, "impossible"
3127 if legit:
3128 aofs = abs(offset)
3129 h, m = divmod(aofs, 60)
3130 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
3131 if isinstance(t, datetime):
3132 t = t.timetz()
3133 self.assertEqual(str(t), "01:02:03" + tag)
3134 else:
3135 self.assertRaises(ValueError, str, t)
3136
3137 def test_tzinfo_classes(self):
3138 cls = self.theclass
3139 class C1(tzinfo):
3140 def utcoffset(self, dt): return None
3141 def dst(self, dt): return None
3142 def tzname(self, dt): return None
3143 for t in (cls(1, 1, 1),
3144 cls(1, 1, 1, tzinfo=None),
3145 cls(1, 1, 1, tzinfo=C1())):
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003146 self.assertIsNone(t.utcoffset())
3147 self.assertIsNone(t.dst())
3148 self.assertIsNone(t.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003149
3150 class C3(tzinfo):
3151 def utcoffset(self, dt): return timedelta(minutes=-1439)
3152 def dst(self, dt): return timedelta(minutes=1439)
3153 def tzname(self, dt): return "aname"
3154 t = cls(1, 1, 1, tzinfo=C3())
3155 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
3156 self.assertEqual(t.dst(), timedelta(minutes=1439))
3157 self.assertEqual(t.tzname(), "aname")
3158
3159 # Wrong types.
3160 class C4(tzinfo):
3161 def utcoffset(self, dt): return "aname"
3162 def dst(self, dt): return 7
3163 def tzname(self, dt): return 0
3164 t = cls(1, 1, 1, tzinfo=C4())
3165 self.assertRaises(TypeError, t.utcoffset)
3166 self.assertRaises(TypeError, t.dst)
3167 self.assertRaises(TypeError, t.tzname)
3168
3169 # Offset out of range.
3170 class C6(tzinfo):
3171 def utcoffset(self, dt): return timedelta(hours=-24)
3172 def dst(self, dt): return timedelta(hours=24)
3173 t = cls(1, 1, 1, tzinfo=C6())
3174 self.assertRaises(ValueError, t.utcoffset)
3175 self.assertRaises(ValueError, t.dst)
3176
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04003177 # Not a whole number of seconds.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003178 class C7(tzinfo):
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04003179 def utcoffset(self, dt): return timedelta(microseconds=61)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003180 def dst(self, dt): return timedelta(microseconds=-81)
3181 t = cls(1, 1, 1, tzinfo=C7())
Alexander Belopolsky018d3532017-07-31 10:26:50 -04003182 self.assertEqual(t.utcoffset(), timedelta(microseconds=61))
3183 self.assertEqual(t.dst(), timedelta(microseconds=-81))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003184
3185 def test_aware_compare(self):
3186 cls = self.theclass
3187
3188 # Ensure that utcoffset() gets ignored if the comparands have
3189 # the same tzinfo member.
3190 class OperandDependentOffset(tzinfo):
3191 def utcoffset(self, t):
3192 if t.minute < 10:
3193 # d0 and d1 equal after adjustment
3194 return timedelta(minutes=t.minute)
3195 else:
3196 # d2 off in the weeds
3197 return timedelta(minutes=59)
3198
3199 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
3200 d0 = base.replace(minute=3)
3201 d1 = base.replace(minute=9)
3202 d2 = base.replace(minute=11)
3203 for x in d0, d1, d2:
3204 for y in d0, d1, d2:
3205 for op in lt, le, gt, ge, eq, ne:
3206 got = op(x, y)
3207 expected = op(x.minute, y.minute)
3208 self.assertEqual(got, expected)
3209
3210 # However, if they're different members, uctoffset is not ignored.
3211 # Note that a time can't actually have an operand-depedent offset,
3212 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
3213 # so skip this test for time.
3214 if cls is not time:
3215 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
3216 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
3217 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
3218 for x in d0, d1, d2:
3219 for y in d0, d1, d2:
3220 got = (x > y) - (x < y)
3221 if (x is d0 or x is d1) and (y is d0 or y is d1):
3222 expected = 0
3223 elif x is y is d2:
3224 expected = 0
3225 elif x is d2:
3226 expected = -1
3227 else:
3228 assert y is d2
3229 expected = 1
3230 self.assertEqual(got, expected)
3231
3232
3233# Testing time objects with a non-None tzinfo.
3234class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
3235 theclass = time
3236
3237 def test_empty(self):
3238 t = self.theclass()
3239 self.assertEqual(t.hour, 0)
3240 self.assertEqual(t.minute, 0)
3241 self.assertEqual(t.second, 0)
3242 self.assertEqual(t.microsecond, 0)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003243 self.assertIsNone(t.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003244
3245 def test_zones(self):
3246 est = FixedOffset(-300, "EST", 1)
3247 utc = FixedOffset(0, "UTC", -2)
3248 met = FixedOffset(60, "MET", 3)
3249 t1 = time( 7, 47, tzinfo=est)
3250 t2 = time(12, 47, tzinfo=utc)
3251 t3 = time(13, 47, tzinfo=met)
3252 t4 = time(microsecond=40)
3253 t5 = time(microsecond=40, tzinfo=utc)
3254
3255 self.assertEqual(t1.tzinfo, est)
3256 self.assertEqual(t2.tzinfo, utc)
3257 self.assertEqual(t3.tzinfo, met)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003258 self.assertIsNone(t4.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003259 self.assertEqual(t5.tzinfo, utc)
3260
3261 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
3262 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
3263 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003264 self.assertIsNone(t4.utcoffset())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003265 self.assertRaises(TypeError, t1.utcoffset, "no args")
3266
3267 self.assertEqual(t1.tzname(), "EST")
3268 self.assertEqual(t2.tzname(), "UTC")
3269 self.assertEqual(t3.tzname(), "MET")
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003270 self.assertIsNone(t4.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003271 self.assertRaises(TypeError, t1.tzname, "no args")
3272
3273 self.assertEqual(t1.dst(), timedelta(minutes=1))
3274 self.assertEqual(t2.dst(), timedelta(minutes=-2))
3275 self.assertEqual(t3.dst(), timedelta(minutes=3))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003276 self.assertIsNone(t4.dst())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003277 self.assertRaises(TypeError, t1.dst, "no args")
3278
3279 self.assertEqual(hash(t1), hash(t2))
3280 self.assertEqual(hash(t1), hash(t3))
3281 self.assertEqual(hash(t2), hash(t3))
3282
3283 self.assertEqual(t1, t2)
3284 self.assertEqual(t1, t3)
3285 self.assertEqual(t2, t3)
Alexander Belopolsky08313822012-06-15 20:19:47 -04003286 self.assertNotEqual(t4, t5) # mixed tz-aware & naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003287 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
3288 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
3289
3290 self.assertEqual(str(t1), "07:47:00-05:00")
3291 self.assertEqual(str(t2), "12:47:00+00:00")
3292 self.assertEqual(str(t3), "13:47:00+01:00")
3293 self.assertEqual(str(t4), "00:00:00.000040")
3294 self.assertEqual(str(t5), "00:00:00.000040+00:00")
3295
3296 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
3297 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
3298 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
3299 self.assertEqual(t4.isoformat(), "00:00:00.000040")
3300 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
3301
3302 d = 'datetime.time'
3303 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
3304 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
3305 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
3306 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
3307 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
3308
3309 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
3310 "07:47:00 %Z=EST %z=-0500")
3311 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
3312 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
3313
3314 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
3315 t1 = time(23, 59, tzinfo=yuck)
3316 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
3317 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
3318
3319 # Check that an invalid tzname result raises an exception.
3320 class Badtzname(tzinfo):
Alexander Belopolskye239d232010-12-08 23:31:48 +00003321 tz = 42
3322 def tzname(self, dt): return self.tz
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003323 t = time(2, 3, 4, tzinfo=Badtzname())
3324 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
3325 self.assertRaises(TypeError, t.strftime, "%Z")
3326
Alexander Belopolskye239d232010-12-08 23:31:48 +00003327 # Issue #6697:
Utkarsh Upadhyay287c5592017-07-21 02:14:54 +02003328 if '_Fast' in self.__class__.__name__:
Alexander Belopolskye239d232010-12-08 23:31:48 +00003329 Badtzname.tz = '\ud800'
3330 self.assertRaises(ValueError, t.strftime, "%Z")
3331
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003332 def test_hash_edge_cases(self):
3333 # Offsets that overflow a basic time.
3334 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
3335 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
3336 self.assertEqual(hash(t1), hash(t2))
3337
3338 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
3339 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
3340 self.assertEqual(hash(t1), hash(t2))
3341
3342 def test_pickling(self):
3343 # Try one without a tzinfo.
3344 args = 20, 59, 16, 64**2
3345 orig = self.theclass(*args)
3346 for pickler, unpickler, proto in pickle_choices:
3347 green = pickler.dumps(orig, proto)
3348 derived = unpickler.loads(green)
3349 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003350 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003351
3352 # Try one with a tzinfo.
3353 tinfo = PicklableFixedOffset(-300, 'cookie')
3354 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
3355 for pickler, unpickler, proto in pickle_choices:
3356 green = pickler.dumps(orig, proto)
3357 derived = unpickler.loads(green)
3358 self.assertEqual(orig, derived)
3359 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3360 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3361 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003362 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003363
3364 def test_more_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003365 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003366 cls = self.theclass
3367
3368 t = cls(0, tzinfo=FixedOffset(-300, ""))
3369 self.assertTrue(t)
3370
3371 t = cls(5, tzinfo=FixedOffset(-300, ""))
3372 self.assertTrue(t)
3373
3374 t = cls(5, tzinfo=FixedOffset(300, ""))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003375 self.assertTrue(t)
3376
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003377 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
3378 self.assertTrue(t)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003379
3380 def test_replace(self):
3381 cls = self.theclass
3382 z100 = FixedOffset(100, "+100")
3383 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3384 args = [1, 2, 3, 4, z100]
3385 base = cls(*args)
3386 self.assertEqual(base, base.replace())
3387
3388 i = 0
3389 for name, newval in (("hour", 5),
3390 ("minute", 6),
3391 ("second", 7),
3392 ("microsecond", 8),
3393 ("tzinfo", zm200)):
3394 newargs = args[:]
3395 newargs[i] = newval
3396 expected = cls(*newargs)
3397 got = base.replace(**{name: newval})
3398 self.assertEqual(expected, got)
3399 i += 1
3400
3401 # Ensure we can get rid of a tzinfo.
3402 self.assertEqual(base.tzname(), "+100")
3403 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003404 self.assertIsNone(base2.tzinfo)
3405 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003406
3407 # Ensure we can add one.
3408 base3 = base2.replace(tzinfo=z100)
3409 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003410 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003411
3412 # Out of bounds.
3413 base = cls(1)
3414 self.assertRaises(ValueError, base.replace, hour=24)
3415 self.assertRaises(ValueError, base.replace, minute=-1)
3416 self.assertRaises(ValueError, base.replace, second=100)
3417 self.assertRaises(ValueError, base.replace, microsecond=1000000)
3418
3419 def test_mixed_compare(self):
3420 t1 = time(1, 2, 3)
3421 t2 = time(1, 2, 3)
3422 self.assertEqual(t1, t2)
3423 t2 = t2.replace(tzinfo=None)
3424 self.assertEqual(t1, t2)
3425 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3426 self.assertEqual(t1, t2)
3427 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04003428 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003429
3430 # In time w/ identical tzinfo objects, utcoffset is ignored.
3431 class Varies(tzinfo):
3432 def __init__(self):
3433 self.offset = timedelta(minutes=22)
3434 def utcoffset(self, t):
3435 self.offset += timedelta(minutes=1)
3436 return self.offset
3437
3438 v = Varies()
3439 t1 = t2.replace(tzinfo=v)
3440 t2 = t2.replace(tzinfo=v)
3441 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3442 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3443 self.assertEqual(t1, t2)
3444
3445 # But if they're not identical, it isn't ignored.
3446 t2 = t2.replace(tzinfo=Varies())
3447 self.assertTrue(t1 < t2) # t1's offset counter still going up
3448
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003449 def test_fromisoformat(self):
3450 time_examples = [
3451 (0, 0, 0, 0),
3452 (23, 59, 59, 999999),
3453 ]
3454
3455 hh = (9, 12, 20)
3456 mm = (5, 30)
3457 ss = (4, 45)
3458 usec = (0, 245000, 678901)
3459
3460 time_examples += list(itertools.product(hh, mm, ss, usec))
3461
3462 tzinfos = [None, timezone.utc,
3463 timezone(timedelta(hours=2)),
3464 timezone(timedelta(hours=6, minutes=27))]
3465
3466 for ttup in time_examples:
3467 for tzi in tzinfos:
3468 t = self.theclass(*ttup, tzinfo=tzi)
3469 tstr = t.isoformat()
3470
3471 with self.subTest(tstr=tstr):
3472 t_rt = self.theclass.fromisoformat(tstr)
3473 self.assertEqual(t, t_rt)
3474
3475 def test_fromisoformat_timezone(self):
3476 base_time = self.theclass(12, 30, 45, 217456)
3477
3478 tzoffsets = [
3479 timedelta(hours=5), timedelta(hours=2),
3480 timedelta(hours=6, minutes=27),
3481 timedelta(hours=12, minutes=32, seconds=30),
3482 timedelta(hours=2, minutes=4, seconds=9, microseconds=123456)
3483 ]
3484
3485 tzoffsets += [-1 * td for td in tzoffsets]
3486
3487 tzinfos = [None, timezone.utc,
3488 timezone(timedelta(hours=0))]
3489
3490 tzinfos += [timezone(td) for td in tzoffsets]
3491
3492 for tzi in tzinfos:
3493 t = base_time.replace(tzinfo=tzi)
3494 tstr = t.isoformat()
3495
3496 with self.subTest(tstr=tstr):
3497 t_rt = self.theclass.fromisoformat(tstr)
3498 assert t == t_rt, t_rt
3499
3500 def test_fromisoformat_timespecs(self):
3501 time_bases = [
3502 (8, 17, 45, 123456),
3503 (8, 17, 45, 0)
3504 ]
3505
3506 tzinfos = [None, timezone.utc,
3507 timezone(timedelta(hours=-5)),
3508 timezone(timedelta(hours=2)),
3509 timezone(timedelta(hours=6, minutes=27))]
3510
3511 timespecs = ['hours', 'minutes', 'seconds',
3512 'milliseconds', 'microseconds']
3513
3514 for ip, ts in enumerate(timespecs):
3515 for tzi in tzinfos:
3516 for t_tuple in time_bases:
3517 if ts == 'milliseconds':
3518 new_microseconds = 1000 * (t_tuple[-1] // 1000)
3519 t_tuple = t_tuple[0:-1] + (new_microseconds,)
3520
3521 t = self.theclass(*(t_tuple[0:(1 + ip)]), tzinfo=tzi)
3522 tstr = t.isoformat(timespec=ts)
3523 with self.subTest(tstr=tstr):
3524 t_rt = self.theclass.fromisoformat(tstr)
3525 self.assertEqual(t, t_rt)
3526
3527 def test_fromisoformat_fails(self):
3528 bad_strs = [
3529 '', # Empty string
Paul Ganssle096329f2018-08-23 11:06:20 -04003530 '12\ud80000', # Invalid separator - surrogate char
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003531 '12:', # Ends on a separator
3532 '12:30:', # Ends on a separator
3533 '12:30:15.', # Ends on a separator
3534 '1', # Incomplete hours
3535 '12:3', # Incomplete minutes
3536 '12:30:1', # Incomplete seconds
3537 '1a:30:45.334034', # Invalid character in hours
3538 '12:a0:45.334034', # Invalid character in minutes
3539 '12:30:a5.334034', # Invalid character in seconds
3540 '12:30:45.1234', # Too many digits for milliseconds
3541 '12:30:45.1234567', # Too many digits for microseconds
3542 '12:30:45.123456+24:30', # Invalid time zone offset
3543 '12:30:45.123456-24:30', # Invalid negative offset
3544 '12:30:45', # Uses full-width unicode colons
3545 '12:30:45․123456', # Uses \u2024 in place of decimal point
3546 '12:30:45a', # Extra at tend of basic time
3547 '12:30:45.123a', # Extra at end of millisecond time
3548 '12:30:45.123456a', # Extra at end of microsecond time
3549 '12:30:45.123456+12:00:30a', # Extra at end of full time
3550 ]
3551
3552 for bad_str in bad_strs:
3553 with self.subTest(bad_str=bad_str):
3554 with self.assertRaises(ValueError):
3555 self.theclass.fromisoformat(bad_str)
3556
3557 def test_fromisoformat_fails_typeerror(self):
3558 # Test the fromisoformat fails when passed the wrong type
3559 import io
3560
3561 bad_types = [b'12:30:45', None, io.StringIO('12:30:45')]
3562
3563 for bad_type in bad_types:
3564 with self.assertRaises(TypeError):
3565 self.theclass.fromisoformat(bad_type)
3566
3567 def test_fromisoformat_subclass(self):
3568 class TimeSubclass(self.theclass):
3569 pass
3570
3571 tsc = TimeSubclass(12, 14, 45, 203745, tzinfo=timezone.utc)
3572 tsc_rt = TimeSubclass.fromisoformat(tsc.isoformat())
3573
3574 self.assertEqual(tsc, tsc_rt)
3575 self.assertIsInstance(tsc_rt, TimeSubclass)
3576
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003577 def test_subclass_timetz(self):
3578
3579 class C(self.theclass):
3580 theAnswer = 42
3581
3582 def __new__(cls, *args, **kws):
3583 temp = kws.copy()
3584 extra = temp.pop('extra')
3585 result = self.theclass.__new__(cls, *args, **temp)
3586 result.extra = extra
3587 return result
3588
3589 def newmeth(self, start):
3590 return start + self.hour + self.second
3591
3592 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3593
3594 dt1 = self.theclass(*args)
3595 dt2 = C(*args, **{'extra': 7})
3596
3597 self.assertEqual(dt2.__class__, C)
3598 self.assertEqual(dt2.theAnswer, 42)
3599 self.assertEqual(dt2.extra, 7)
3600 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3601 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
3602
3603
3604# Testing datetime objects with a non-None tzinfo.
3605
3606class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
3607 theclass = datetime
3608
3609 def test_trivial(self):
3610 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
3611 self.assertEqual(dt.year, 1)
3612 self.assertEqual(dt.month, 2)
3613 self.assertEqual(dt.day, 3)
3614 self.assertEqual(dt.hour, 4)
3615 self.assertEqual(dt.minute, 5)
3616 self.assertEqual(dt.second, 6)
3617 self.assertEqual(dt.microsecond, 7)
3618 self.assertEqual(dt.tzinfo, None)
3619
3620 def test_even_more_compare(self):
3621 # The test_compare() and test_more_compare() inherited from TestDate
3622 # and TestDateTime covered non-tzinfo cases.
3623
3624 # Smallest possible after UTC adjustment.
3625 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
3626 # Largest possible after UTC adjustment.
3627 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3628 tzinfo=FixedOffset(-1439, ""))
3629
3630 # Make sure those compare correctly, and w/o overflow.
3631 self.assertTrue(t1 < t2)
3632 self.assertTrue(t1 != t2)
3633 self.assertTrue(t2 > t1)
3634
3635 self.assertEqual(t1, t1)
3636 self.assertEqual(t2, t2)
3637
3638 # Equal afer adjustment.
3639 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
3640 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
3641 self.assertEqual(t1, t2)
3642
3643 # Change t1 not to subtract a minute, and t1 should be larger.
3644 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
3645 self.assertTrue(t1 > t2)
3646
3647 # Change t1 to subtract 2 minutes, and t1 should be smaller.
3648 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
3649 self.assertTrue(t1 < t2)
3650
3651 # Back to the original t1, but make seconds resolve it.
3652 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3653 second=1)
3654 self.assertTrue(t1 > t2)
3655
3656 # Likewise, but make microseconds resolve it.
3657 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3658 microsecond=1)
3659 self.assertTrue(t1 > t2)
3660
Alexander Belopolsky08313822012-06-15 20:19:47 -04003661 # Make t2 naive and it should differ.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003662 t2 = self.theclass.min
Alexander Belopolsky08313822012-06-15 20:19:47 -04003663 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003664 self.assertEqual(t2, t2)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04003665 # and > comparison should fail
3666 with self.assertRaises(TypeError):
3667 t1 > t2
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003668
3669 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
3670 class Naive(tzinfo):
3671 def utcoffset(self, dt): return None
3672 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
Alexander Belopolsky08313822012-06-15 20:19:47 -04003673 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003674 self.assertEqual(t2, t2)
3675
3676 # OTOH, it's OK to compare two of these mixing the two ways of being
3677 # naive.
3678 t1 = self.theclass(5, 6, 7)
3679 self.assertEqual(t1, t2)
3680
3681 # Try a bogus uctoffset.
3682 class Bogus(tzinfo):
3683 def utcoffset(self, dt):
3684 return timedelta(minutes=1440) # out of bounds
3685 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
3686 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
3687 self.assertRaises(ValueError, lambda: t1 == t2)
3688
3689 def test_pickling(self):
3690 # Try one without a tzinfo.
3691 args = 6, 7, 23, 20, 59, 1, 64**2
3692 orig = self.theclass(*args)
3693 for pickler, unpickler, proto in pickle_choices:
3694 green = pickler.dumps(orig, proto)
3695 derived = unpickler.loads(green)
3696 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003697 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003698
3699 # Try one with a tzinfo.
3700 tinfo = PicklableFixedOffset(-300, 'cookie')
3701 orig = self.theclass(*args, **{'tzinfo': tinfo})
3702 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
3703 for pickler, unpickler, proto in pickle_choices:
3704 green = pickler.dumps(orig, proto)
3705 derived = unpickler.loads(green)
3706 self.assertEqual(orig, derived)
3707 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3708 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3709 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003710 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003711
3712 def test_extreme_hashes(self):
3713 # If an attempt is made to hash these via subtracting the offset
3714 # then hashing a datetime object, OverflowError results. The
3715 # Python implementation used to blow up here.
3716 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
3717 hash(t)
3718 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3719 tzinfo=FixedOffset(-1439, ""))
3720 hash(t)
3721
3722 # OTOH, an OOB offset should blow up.
3723 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
3724 self.assertRaises(ValueError, hash, t)
3725
3726 def test_zones(self):
3727 est = FixedOffset(-300, "EST")
3728 utc = FixedOffset(0, "UTC")
3729 met = FixedOffset(60, "MET")
3730 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
3731 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
3732 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
3733 self.assertEqual(t1.tzinfo, est)
3734 self.assertEqual(t2.tzinfo, utc)
3735 self.assertEqual(t3.tzinfo, met)
3736 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
3737 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
3738 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
3739 self.assertEqual(t1.tzname(), "EST")
3740 self.assertEqual(t2.tzname(), "UTC")
3741 self.assertEqual(t3.tzname(), "MET")
3742 self.assertEqual(hash(t1), hash(t2))
3743 self.assertEqual(hash(t1), hash(t3))
3744 self.assertEqual(hash(t2), hash(t3))
3745 self.assertEqual(t1, t2)
3746 self.assertEqual(t1, t3)
3747 self.assertEqual(t2, t3)
3748 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
3749 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
3750 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
3751 d = 'datetime.datetime(2002, 3, 19, '
3752 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
3753 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
3754 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
3755
3756 def test_combine(self):
3757 met = FixedOffset(60, "MET")
3758 d = date(2002, 3, 4)
3759 tz = time(18, 45, 3, 1234, tzinfo=met)
3760 dt = datetime.combine(d, tz)
3761 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
3762 tzinfo=met))
3763
3764 def test_extract(self):
3765 met = FixedOffset(60, "MET")
3766 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
3767 self.assertEqual(dt.date(), date(2002, 3, 4))
3768 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
3769 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
3770
3771 def test_tz_aware_arithmetic(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003772 now = self.theclass.now()
3773 tz55 = FixedOffset(-330, "west 5:30")
3774 timeaware = now.time().replace(tzinfo=tz55)
3775 nowaware = self.theclass.combine(now.date(), timeaware)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003776 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003777 self.assertEqual(nowaware.timetz(), timeaware)
3778
3779 # Can't mix aware and non-aware.
3780 self.assertRaises(TypeError, lambda: now - nowaware)
3781 self.assertRaises(TypeError, lambda: nowaware - now)
3782
3783 # And adding datetime's doesn't make sense, aware or not.
3784 self.assertRaises(TypeError, lambda: now + nowaware)
3785 self.assertRaises(TypeError, lambda: nowaware + now)
3786 self.assertRaises(TypeError, lambda: nowaware + nowaware)
3787
3788 # Subtracting should yield 0.
3789 self.assertEqual(now - now, timedelta(0))
3790 self.assertEqual(nowaware - nowaware, timedelta(0))
3791
3792 # Adding a delta should preserve tzinfo.
3793 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
3794 nowawareplus = nowaware + delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003795 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003796 nowawareplus2 = delta + nowaware
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003797 self.assertIs(nowawareplus2.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003798 self.assertEqual(nowawareplus, nowawareplus2)
3799
3800 # that - delta should be what we started with, and that - what we
3801 # started with should be delta.
3802 diff = nowawareplus - delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003803 self.assertIs(diff.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003804 self.assertEqual(nowaware, diff)
3805 self.assertRaises(TypeError, lambda: delta - nowawareplus)
3806 self.assertEqual(nowawareplus - nowaware, delta)
3807
3808 # Make up a random timezone.
3809 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
3810 # Attach it to nowawareplus.
3811 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003812 self.assertIs(nowawareplus.tzinfo, tzr)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003813 # Make sure the difference takes the timezone adjustments into account.
3814 got = nowaware - nowawareplus
3815 # Expected: (nowaware base - nowaware offset) -
3816 # (nowawareplus base - nowawareplus offset) =
3817 # (nowaware base - nowawareplus base) +
3818 # (nowawareplus offset - nowaware offset) =
3819 # -delta + nowawareplus offset - nowaware offset
3820 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
3821 self.assertEqual(got, expected)
3822
3823 # Try max possible difference.
3824 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
3825 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3826 tzinfo=FixedOffset(-1439, "max"))
3827 maxdiff = max - min
3828 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
3829 timedelta(minutes=2*1439))
3830 # Different tzinfo, but the same offset
3831 tza = timezone(HOUR, 'A')
3832 tzb = timezone(HOUR, 'B')
3833 delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
3834 self.assertEqual(delta, self.theclass.min - self.theclass.max)
3835
3836 def test_tzinfo_now(self):
3837 meth = self.theclass.now
3838 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3839 base = meth()
3840 # Try with and without naming the keyword.
3841 off42 = FixedOffset(42, "42")
3842 another = meth(off42)
3843 again = meth(tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003844 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003845 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3846 # Bad argument with and w/o naming the keyword.
3847 self.assertRaises(TypeError, meth, 16)
3848 self.assertRaises(TypeError, meth, tzinfo=16)
3849 # Bad keyword name.
3850 self.assertRaises(TypeError, meth, tinfo=off42)
3851 # Too many args.
3852 self.assertRaises(TypeError, meth, off42, off42)
3853
3854 # We don't know which time zone we're in, and don't have a tzinfo
3855 # class to represent it, so seeing whether a tz argument actually
3856 # does a conversion is tricky.
3857 utc = FixedOffset(0, "utc", 0)
3858 for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
3859 timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
3860 for dummy in range(3):
3861 now = datetime.now(weirdtz)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003862 self.assertIs(now.tzinfo, weirdtz)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003863 utcnow = datetime.utcnow().replace(tzinfo=utc)
3864 now2 = utcnow.astimezone(weirdtz)
3865 if abs(now - now2) < timedelta(seconds=30):
3866 break
3867 # Else the code is broken, or more than 30 seconds passed between
3868 # calls; assuming the latter, just try again.
3869 else:
3870 # Three strikes and we're out.
3871 self.fail("utcnow(), now(tz), or astimezone() may be broken")
3872
3873 def test_tzinfo_fromtimestamp(self):
3874 import time
3875 meth = self.theclass.fromtimestamp
3876 ts = time.time()
3877 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3878 base = meth(ts)
3879 # Try with and without naming the keyword.
3880 off42 = FixedOffset(42, "42")
3881 another = meth(ts, off42)
3882 again = meth(ts, tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003883 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003884 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3885 # Bad argument with and w/o naming the keyword.
3886 self.assertRaises(TypeError, meth, ts, 16)
3887 self.assertRaises(TypeError, meth, ts, tzinfo=16)
3888 # Bad keyword name.
3889 self.assertRaises(TypeError, meth, ts, tinfo=off42)
3890 # Too many args.
3891 self.assertRaises(TypeError, meth, ts, off42, off42)
3892 # Too few args.
3893 self.assertRaises(TypeError, meth)
3894
3895 # Try to make sure tz= actually does some conversion.
3896 timestamp = 1000000000
3897 utcdatetime = datetime.utcfromtimestamp(timestamp)
3898 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
3899 # But on some flavor of Mac, it's nowhere near that. So we can't have
3900 # any idea here what time that actually is, we can only test that
3901 # relative changes match.
3902 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
3903 tz = FixedOffset(utcoffset, "tz", 0)
3904 expected = utcdatetime + utcoffset
3905 got = datetime.fromtimestamp(timestamp, tz)
3906 self.assertEqual(expected, got.replace(tzinfo=None))
3907
3908 def test_tzinfo_utcnow(self):
3909 meth = self.theclass.utcnow
3910 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3911 base = meth()
3912 # Try with and without naming the keyword; for whatever reason,
3913 # utcnow() doesn't accept a tzinfo argument.
3914 off42 = FixedOffset(42, "42")
3915 self.assertRaises(TypeError, meth, off42)
3916 self.assertRaises(TypeError, meth, tzinfo=off42)
3917
3918 def test_tzinfo_utcfromtimestamp(self):
3919 import time
3920 meth = self.theclass.utcfromtimestamp
3921 ts = time.time()
3922 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3923 base = meth(ts)
3924 # Try with and without naming the keyword; for whatever reason,
3925 # utcfromtimestamp() doesn't accept a tzinfo argument.
3926 off42 = FixedOffset(42, "42")
3927 self.assertRaises(TypeError, meth, ts, off42)
3928 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
3929
3930 def test_tzinfo_timetuple(self):
3931 # TestDateTime tested most of this. datetime adds a twist to the
3932 # DST flag.
3933 class DST(tzinfo):
3934 def __init__(self, dstvalue):
3935 if isinstance(dstvalue, int):
3936 dstvalue = timedelta(minutes=dstvalue)
3937 self.dstvalue = dstvalue
3938 def dst(self, dt):
3939 return self.dstvalue
3940
3941 cls = self.theclass
3942 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
3943 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
3944 t = d.timetuple()
3945 self.assertEqual(1, t.tm_year)
3946 self.assertEqual(1, t.tm_mon)
3947 self.assertEqual(1, t.tm_mday)
3948 self.assertEqual(10, t.tm_hour)
3949 self.assertEqual(20, t.tm_min)
3950 self.assertEqual(30, t.tm_sec)
3951 self.assertEqual(0, t.tm_wday)
3952 self.assertEqual(1, t.tm_yday)
3953 self.assertEqual(flag, t.tm_isdst)
3954
3955 # dst() returns wrong type.
3956 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
3957
3958 # dst() at the edge.
3959 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
3960 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
3961
3962 # dst() out of range.
3963 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
3964 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
3965
3966 def test_utctimetuple(self):
3967 class DST(tzinfo):
3968 def __init__(self, dstvalue=0):
3969 if isinstance(dstvalue, int):
3970 dstvalue = timedelta(minutes=dstvalue)
3971 self.dstvalue = dstvalue
3972 def dst(self, dt):
3973 return self.dstvalue
3974
3975 cls = self.theclass
3976 # This can't work: DST didn't implement utcoffset.
3977 self.assertRaises(NotImplementedError,
3978 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
3979
3980 class UOFS(DST):
3981 def __init__(self, uofs, dofs=None):
3982 DST.__init__(self, dofs)
3983 self.uofs = timedelta(minutes=uofs)
3984 def utcoffset(self, dt):
3985 return self.uofs
3986
3987 for dstvalue in -33, 33, 0, None:
3988 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
3989 t = d.utctimetuple()
3990 self.assertEqual(d.year, t.tm_year)
3991 self.assertEqual(d.month, t.tm_mon)
3992 self.assertEqual(d.day, t.tm_mday)
3993 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
3994 self.assertEqual(13, t.tm_min)
3995 self.assertEqual(d.second, t.tm_sec)
3996 self.assertEqual(d.weekday(), t.tm_wday)
3997 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
3998 t.tm_yday)
3999 # Ensure tm_isdst is 0 regardless of what dst() says: DST
4000 # is never in effect for a UTC time.
4001 self.assertEqual(0, t.tm_isdst)
4002
4003 # For naive datetime, utctimetuple == timetuple except for isdst
4004 d = cls(1, 2, 3, 10, 20, 30, 40)
4005 t = d.utctimetuple()
4006 self.assertEqual(t[:-1], d.timetuple()[:-1])
4007 self.assertEqual(0, t.tm_isdst)
4008 # Same if utcoffset is None
4009 class NOFS(DST):
4010 def utcoffset(self, dt):
4011 return None
4012 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
4013 t = d.utctimetuple()
4014 self.assertEqual(t[:-1], d.timetuple()[:-1])
4015 self.assertEqual(0, t.tm_isdst)
4016 # Check that bad tzinfo is detected
4017 class BOFS(DST):
4018 def utcoffset(self, dt):
4019 return "EST"
4020 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
4021 self.assertRaises(TypeError, d.utctimetuple)
4022
4023 # Check that utctimetuple() is the same as
4024 # astimezone(utc).timetuple()
4025 d = cls(2010, 11, 13, 14, 15, 16, 171819)
4026 for tz in [timezone.min, timezone.utc, timezone.max]:
4027 dtz = d.replace(tzinfo=tz)
4028 self.assertEqual(dtz.utctimetuple()[:-1],
4029 dtz.astimezone(timezone.utc).timetuple()[:-1])
4030 # At the edges, UTC adjustment can produce years out-of-range
4031 # for a datetime object. Ensure that an OverflowError is
4032 # raised.
4033 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
4034 # That goes back 1 minute less than a full day.
4035 self.assertRaises(OverflowError, tiny.utctimetuple)
4036
4037 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
4038 # That goes forward 1 minute less than a full day.
4039 self.assertRaises(OverflowError, huge.utctimetuple)
4040 # More overflow cases
4041 tiny = cls.min.replace(tzinfo=timezone(MINUTE))
4042 self.assertRaises(OverflowError, tiny.utctimetuple)
4043 huge = cls.max.replace(tzinfo=timezone(-MINUTE))
4044 self.assertRaises(OverflowError, huge.utctimetuple)
4045
4046 def test_tzinfo_isoformat(self):
4047 zero = FixedOffset(0, "+00:00")
4048 plus = FixedOffset(220, "+03:40")
4049 minus = FixedOffset(-231, "-03:51")
4050 unknown = FixedOffset(None, "")
4051
4052 cls = self.theclass
4053 datestr = '0001-02-03'
4054 for ofs in None, zero, plus, minus, unknown:
4055 for us in 0, 987001:
4056 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
4057 timestr = '04:05:59' + (us and '.987001' or '')
4058 ofsstr = ofs is not None and d.tzname() or ''
4059 tailstr = timestr + ofsstr
4060 iso = d.isoformat()
4061 self.assertEqual(iso, datestr + 'T' + tailstr)
4062 self.assertEqual(iso, d.isoformat('T'))
4063 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
4064 self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
4065 self.assertEqual(str(d), datestr + ' ' + tailstr)
4066
4067 def test_replace(self):
4068 cls = self.theclass
4069 z100 = FixedOffset(100, "+100")
4070 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
4071 args = [1, 2, 3, 4, 5, 6, 7, z100]
4072 base = cls(*args)
4073 self.assertEqual(base, base.replace())
4074
4075 i = 0
4076 for name, newval in (("year", 2),
4077 ("month", 3),
4078 ("day", 4),
4079 ("hour", 5),
4080 ("minute", 6),
4081 ("second", 7),
4082 ("microsecond", 8),
4083 ("tzinfo", zm200)):
4084 newargs = args[:]
4085 newargs[i] = newval
4086 expected = cls(*newargs)
4087 got = base.replace(**{name: newval})
4088 self.assertEqual(expected, got)
4089 i += 1
4090
4091 # Ensure we can get rid of a tzinfo.
4092 self.assertEqual(base.tzname(), "+100")
4093 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004094 self.assertIsNone(base2.tzinfo)
4095 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004096
4097 # Ensure we can add one.
4098 base3 = base2.replace(tzinfo=z100)
4099 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004100 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004101
4102 # Out of bounds.
4103 base = cls(2000, 2, 29)
4104 self.assertRaises(ValueError, base.replace, year=2001)
4105
4106 def test_more_astimezone(self):
4107 # The inherited test_astimezone covered some trivial and error cases.
4108 fnone = FixedOffset(None, "None")
4109 f44m = FixedOffset(44, "44")
4110 fm5h = FixedOffset(-timedelta(hours=5), "m300")
4111
4112 dt = self.theclass.now(tz=f44m)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004113 self.assertIs(dt.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004114 # Replacing with degenerate tzinfo raises an exception.
4115 self.assertRaises(ValueError, dt.astimezone, fnone)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004116 # Replacing with same tzinfo makes no change.
4117 x = dt.astimezone(dt.tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004118 self.assertIs(x.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004119 self.assertEqual(x.date(), dt.date())
4120 self.assertEqual(x.time(), dt.time())
4121
4122 # Replacing with different tzinfo does adjust.
4123 got = dt.astimezone(fm5h)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004124 self.assertIs(got.tzinfo, fm5h)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004125 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
4126 expected = dt - dt.utcoffset() # in effect, convert to UTC
4127 expected += fm5h.utcoffset(dt) # and from there to local time
4128 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
4129 self.assertEqual(got.date(), expected.date())
4130 self.assertEqual(got.time(), expected.time())
4131 self.assertEqual(got.timetz(), expected.timetz())
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004132 self.assertIs(got.tzinfo, expected.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004133 self.assertEqual(got, expected)
4134
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004135 @support.run_with_tz('UTC')
4136 def test_astimezone_default_utc(self):
4137 dt = self.theclass.now(timezone.utc)
4138 self.assertEqual(dt.astimezone(None), dt)
4139 self.assertEqual(dt.astimezone(), dt)
4140
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004141 # Note that offset in TZ variable has the opposite sign to that
4142 # produced by %z directive.
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004143 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4144 def test_astimezone_default_eastern(self):
4145 dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
4146 local = dt.astimezone()
4147 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004148 self.assertEqual(local.strftime("%z %Z"), "-0500 EST")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004149 dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc)
4150 local = dt.astimezone()
4151 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004152 self.assertEqual(local.strftime("%z %Z"), "-0400 EDT")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004153
Alexander Belopolsky1dcf4f92016-03-25 15:42:59 -04004154 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4155 def test_astimezone_default_near_fold(self):
4156 # Issue #26616.
4157 u = datetime(2015, 11, 1, 5, tzinfo=timezone.utc)
4158 t = u.astimezone()
4159 s = t.astimezone()
4160 self.assertEqual(t.tzinfo, s.tzinfo)
4161
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004162 def test_aware_subtract(self):
4163 cls = self.theclass
4164
4165 # Ensure that utcoffset() is ignored when the operands have the
4166 # same tzinfo member.
4167 class OperandDependentOffset(tzinfo):
4168 def utcoffset(self, t):
4169 if t.minute < 10:
4170 # d0 and d1 equal after adjustment
4171 return timedelta(minutes=t.minute)
4172 else:
4173 # d2 off in the weeds
4174 return timedelta(minutes=59)
4175
4176 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
4177 d0 = base.replace(minute=3)
4178 d1 = base.replace(minute=9)
4179 d2 = base.replace(minute=11)
4180 for x in d0, d1, d2:
4181 for y in d0, d1, d2:
4182 got = x - y
4183 expected = timedelta(minutes=x.minute - y.minute)
4184 self.assertEqual(got, expected)
4185
4186 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
4187 # ignored.
4188 base = cls(8, 9, 10, 11, 12, 13, 14)
4189 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
4190 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
4191 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
4192 for x in d0, d1, d2:
4193 for y in d0, d1, d2:
4194 got = x - y
4195 if (x is d0 or x is d1) and (y is d0 or y is d1):
4196 expected = timedelta(0)
4197 elif x is y is d2:
4198 expected = timedelta(0)
4199 elif x is d2:
4200 expected = timedelta(minutes=(11-59)-0)
4201 else:
4202 assert y is d2
4203 expected = timedelta(minutes=0-(11-59))
4204 self.assertEqual(got, expected)
4205
4206 def test_mixed_compare(self):
4207 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
4208 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
4209 self.assertEqual(t1, t2)
4210 t2 = t2.replace(tzinfo=None)
4211 self.assertEqual(t1, t2)
4212 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
4213 self.assertEqual(t1, t2)
4214 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04004215 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004216
4217 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
4218 class Varies(tzinfo):
4219 def __init__(self):
4220 self.offset = timedelta(minutes=22)
4221 def utcoffset(self, t):
4222 self.offset += timedelta(minutes=1)
4223 return self.offset
4224
4225 v = Varies()
4226 t1 = t2.replace(tzinfo=v)
4227 t2 = t2.replace(tzinfo=v)
4228 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
4229 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
4230 self.assertEqual(t1, t2)
4231
4232 # But if they're not identical, it isn't ignored.
4233 t2 = t2.replace(tzinfo=Varies())
4234 self.assertTrue(t1 < t2) # t1's offset counter still going up
4235
4236 def test_subclass_datetimetz(self):
4237
4238 class C(self.theclass):
4239 theAnswer = 42
4240
4241 def __new__(cls, *args, **kws):
4242 temp = kws.copy()
4243 extra = temp.pop('extra')
4244 result = self.theclass.__new__(cls, *args, **temp)
4245 result.extra = extra
4246 return result
4247
4248 def newmeth(self, start):
4249 return start + self.hour + self.year
4250
4251 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
4252
4253 dt1 = self.theclass(*args)
4254 dt2 = C(*args, **{'extra': 7})
4255
4256 self.assertEqual(dt2.__class__, C)
4257 self.assertEqual(dt2.theAnswer, 42)
4258 self.assertEqual(dt2.extra, 7)
4259 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
4260 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
4261
4262# Pain to set up DST-aware tzinfo classes.
4263
4264def first_sunday_on_or_after(dt):
4265 days_to_go = 6 - dt.weekday()
4266 if days_to_go:
4267 dt += timedelta(days_to_go)
4268 return dt
4269
4270ZERO = timedelta(0)
4271MINUTE = timedelta(minutes=1)
4272HOUR = timedelta(hours=1)
4273DAY = timedelta(days=1)
4274# In the US, DST starts at 2am (standard time) on the first Sunday in April.
4275DSTSTART = datetime(1, 4, 1, 2)
4276# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
4277# which is the first Sunday on or after Oct 25. Because we view 1:MM as
4278# being standard time on that day, there is no spelling in local time of
4279# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
4280DSTEND = datetime(1, 10, 25, 1)
4281
4282class USTimeZone(tzinfo):
4283
4284 def __init__(self, hours, reprname, stdname, dstname):
4285 self.stdoffset = timedelta(hours=hours)
4286 self.reprname = reprname
4287 self.stdname = stdname
4288 self.dstname = dstname
4289
4290 def __repr__(self):
4291 return self.reprname
4292
4293 def tzname(self, dt):
4294 if self.dst(dt):
4295 return self.dstname
4296 else:
4297 return self.stdname
4298
4299 def utcoffset(self, dt):
4300 return self.stdoffset + self.dst(dt)
4301
4302 def dst(self, dt):
4303 if dt is None or dt.tzinfo is None:
4304 # An exception instead may be sensible here, in one or more of
4305 # the cases.
4306 return ZERO
4307 assert dt.tzinfo is self
4308
4309 # Find first Sunday in April.
4310 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
4311 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
4312
4313 # Find last Sunday in October.
4314 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
4315 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
4316
4317 # Can't compare naive to aware objects, so strip the timezone from
4318 # dt first.
4319 if start <= dt.replace(tzinfo=None) < end:
4320 return HOUR
4321 else:
4322 return ZERO
4323
4324Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
4325Central = USTimeZone(-6, "Central", "CST", "CDT")
4326Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
4327Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
4328utc_real = FixedOffset(0, "UTC", 0)
4329# For better test coverage, we want another flavor of UTC that's west of
4330# the Eastern and Pacific timezones.
4331utc_fake = FixedOffset(-12*60, "UTCfake", 0)
4332
4333class TestTimezoneConversions(unittest.TestCase):
4334 # The DST switch times for 2002, in std time.
4335 dston = datetime(2002, 4, 7, 2)
4336 dstoff = datetime(2002, 10, 27, 1)
4337
4338 theclass = datetime
4339
4340 # Check a time that's inside DST.
4341 def checkinside(self, dt, tz, utc, dston, dstoff):
4342 self.assertEqual(dt.dst(), HOUR)
4343
4344 # Conversion to our own timezone is always an identity.
4345 self.assertEqual(dt.astimezone(tz), dt)
4346
4347 asutc = dt.astimezone(utc)
4348 there_and_back = asutc.astimezone(tz)
4349
4350 # Conversion to UTC and back isn't always an identity here,
4351 # because there are redundant spellings (in local time) of
4352 # UTC time when DST begins: the clock jumps from 1:59:59
4353 # to 3:00:00, and a local time of 2:MM:SS doesn't really
4354 # make sense then. The classes above treat 2:MM:SS as
4355 # daylight time then (it's "after 2am"), really an alias
4356 # for 1:MM:SS standard time. The latter form is what
4357 # conversion back from UTC produces.
4358 if dt.date() == dston.date() and dt.hour == 2:
4359 # We're in the redundant hour, and coming back from
4360 # UTC gives the 1:MM:SS standard-time spelling.
4361 self.assertEqual(there_and_back + HOUR, dt)
4362 # Although during was considered to be in daylight
4363 # time, there_and_back is not.
4364 self.assertEqual(there_and_back.dst(), ZERO)
4365 # They're the same times in UTC.
4366 self.assertEqual(there_and_back.astimezone(utc),
4367 dt.astimezone(utc))
4368 else:
4369 # We're not in the redundant hour.
4370 self.assertEqual(dt, there_and_back)
4371
4372 # Because we have a redundant spelling when DST begins, there is
Ezio Melotti3b3499b2011-03-16 11:35:38 +02004373 # (unfortunately) an hour when DST ends that can't be spelled at all in
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004374 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
4375 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
4376 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
4377 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
4378 # expressed in local time. Nevertheless, we want conversion back
4379 # from UTC to mimic the local clock's "repeat an hour" behavior.
4380 nexthour_utc = asutc + HOUR
4381 nexthour_tz = nexthour_utc.astimezone(tz)
4382 if dt.date() == dstoff.date() and dt.hour == 0:
4383 # We're in the hour before the last DST hour. The last DST hour
4384 # is ineffable. We want the conversion back to repeat 1:MM.
4385 self.assertEqual(nexthour_tz, dt.replace(hour=1))
4386 nexthour_utc += HOUR
4387 nexthour_tz = nexthour_utc.astimezone(tz)
4388 self.assertEqual(nexthour_tz, dt.replace(hour=1))
4389 else:
4390 self.assertEqual(nexthour_tz - dt, HOUR)
4391
4392 # Check a time that's outside DST.
4393 def checkoutside(self, dt, tz, utc):
4394 self.assertEqual(dt.dst(), ZERO)
4395
4396 # Conversion to our own timezone is always an identity.
4397 self.assertEqual(dt.astimezone(tz), dt)
4398
4399 # Converting to UTC and back is an identity too.
4400 asutc = dt.astimezone(utc)
4401 there_and_back = asutc.astimezone(tz)
4402 self.assertEqual(dt, there_and_back)
4403
4404 def convert_between_tz_and_utc(self, tz, utc):
4405 dston = self.dston.replace(tzinfo=tz)
4406 # Because 1:MM on the day DST ends is taken as being standard time,
4407 # there is no spelling in tz for the last hour of daylight time.
4408 # For purposes of the test, the last hour of DST is 0:MM, which is
4409 # taken as being daylight time (and 1:MM is taken as being standard
4410 # time).
4411 dstoff = self.dstoff.replace(tzinfo=tz)
4412 for delta in (timedelta(weeks=13),
4413 DAY,
4414 HOUR,
4415 timedelta(minutes=1),
4416 timedelta(microseconds=1)):
4417
4418 self.checkinside(dston, tz, utc, dston, dstoff)
4419 for during in dston + delta, dstoff - delta:
4420 self.checkinside(during, tz, utc, dston, dstoff)
4421
4422 self.checkoutside(dstoff, tz, utc)
4423 for outside in dston - delta, dstoff + delta:
4424 self.checkoutside(outside, tz, utc)
4425
4426 def test_easy(self):
4427 # Despite the name of this test, the endcases are excruciating.
4428 self.convert_between_tz_and_utc(Eastern, utc_real)
4429 self.convert_between_tz_and_utc(Pacific, utc_real)
4430 self.convert_between_tz_and_utc(Eastern, utc_fake)
4431 self.convert_between_tz_and_utc(Pacific, utc_fake)
4432 # The next is really dancing near the edge. It works because
4433 # Pacific and Eastern are far enough apart that their "problem
4434 # hours" don't overlap.
4435 self.convert_between_tz_and_utc(Eastern, Pacific)
4436 self.convert_between_tz_and_utc(Pacific, Eastern)
4437 # OTOH, these fail! Don't enable them. The difficulty is that
4438 # the edge case tests assume that every hour is representable in
4439 # the "utc" class. This is always true for a fixed-offset tzinfo
4440 # class (lke utc_real and utc_fake), but not for Eastern or Central.
4441 # For these adjacent DST-aware time zones, the range of time offsets
4442 # tested ends up creating hours in the one that aren't representable
4443 # in the other. For the same reason, we would see failures in the
4444 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
4445 # offset deltas in convert_between_tz_and_utc().
4446 #
4447 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
4448 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
4449
4450 def test_tricky(self):
4451 # 22:00 on day before daylight starts.
4452 fourback = self.dston - timedelta(hours=4)
4453 ninewest = FixedOffset(-9*60, "-0900", 0)
4454 fourback = fourback.replace(tzinfo=ninewest)
4455 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
4456 # 2", we should get the 3 spelling.
4457 # If we plug 22:00 the day before into Eastern, it "looks like std
4458 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
4459 # to 22:00 lands on 2:00, which makes no sense in local time (the
4460 # local clock jumps from 1 to 3). The point here is to make sure we
4461 # get the 3 spelling.
4462 expected = self.dston.replace(hour=3)
4463 got = fourback.astimezone(Eastern).replace(tzinfo=None)
4464 self.assertEqual(expected, got)
4465
4466 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
4467 # case we want the 1:00 spelling.
4468 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
4469 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
4470 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
4471 # spelling.
4472 expected = self.dston.replace(hour=1)
4473 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
4474 self.assertEqual(expected, got)
4475
4476 # Now on the day DST ends, we want "repeat an hour" behavior.
4477 # UTC 4:MM 5:MM 6:MM 7:MM checking these
4478 # EST 23:MM 0:MM 1:MM 2:MM
4479 # EDT 0:MM 1:MM 2:MM 3:MM
4480 # wall 0:MM 1:MM 1:MM 2:MM against these
4481 for utc in utc_real, utc_fake:
4482 for tz in Eastern, Pacific:
4483 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
4484 # Convert that to UTC.
4485 first_std_hour -= tz.utcoffset(None)
4486 # Adjust for possibly fake UTC.
4487 asutc = first_std_hour + utc.utcoffset(None)
4488 # First UTC hour to convert; this is 4:00 when utc=utc_real &
4489 # tz=Eastern.
4490 asutcbase = asutc.replace(tzinfo=utc)
4491 for tzhour in (0, 1, 1, 2):
4492 expectedbase = self.dstoff.replace(hour=tzhour)
4493 for minute in 0, 30, 59:
4494 expected = expectedbase.replace(minute=minute)
4495 asutc = asutcbase.replace(minute=minute)
4496 astz = asutc.astimezone(tz)
4497 self.assertEqual(astz.replace(tzinfo=None), expected)
4498 asutcbase += HOUR
4499
4500
4501 def test_bogus_dst(self):
4502 class ok(tzinfo):
4503 def utcoffset(self, dt): return HOUR
4504 def dst(self, dt): return HOUR
4505
4506 now = self.theclass.now().replace(tzinfo=utc_real)
4507 # Doesn't blow up.
4508 now.astimezone(ok())
4509
4510 # Does blow up.
4511 class notok(ok):
4512 def dst(self, dt): return None
4513 self.assertRaises(ValueError, now.astimezone, notok())
4514
4515 # Sometimes blow up. In the following, tzinfo.dst()
4516 # implementation may return None or not None depending on
4517 # whether DST is assumed to be in effect. In this situation,
4518 # a ValueError should be raised by astimezone().
4519 class tricky_notok(ok):
4520 def dst(self, dt):
4521 if dt.year == 2000:
4522 return None
4523 else:
4524 return 10*HOUR
4525 dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
4526 self.assertRaises(ValueError, dt.astimezone, tricky_notok())
4527
4528 def test_fromutc(self):
4529 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
4530 now = datetime.utcnow().replace(tzinfo=utc_real)
4531 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
4532 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
4533 enow = Eastern.fromutc(now) # doesn't blow up
4534 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
4535 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
4536 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
4537
4538 # Always converts UTC to standard time.
4539 class FauxUSTimeZone(USTimeZone):
4540 def fromutc(self, dt):
4541 return dt + self.stdoffset
4542 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
4543
4544 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
4545 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
4546 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
4547
4548 # Check around DST start.
4549 start = self.dston.replace(hour=4, tzinfo=Eastern)
4550 fstart = start.replace(tzinfo=FEastern)
4551 for wall in 23, 0, 1, 3, 4, 5:
4552 expected = start.replace(hour=wall)
4553 if wall == 23:
4554 expected -= timedelta(days=1)
4555 got = Eastern.fromutc(start)
4556 self.assertEqual(expected, got)
4557
4558 expected = fstart + FEastern.stdoffset
4559 got = FEastern.fromutc(fstart)
4560 self.assertEqual(expected, got)
4561
4562 # Ensure astimezone() calls fromutc() too.
4563 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
4564 self.assertEqual(expected, got)
4565
4566 start += HOUR
4567 fstart += HOUR
4568
4569 # Check around DST end.
4570 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
4571 fstart = start.replace(tzinfo=FEastern)
4572 for wall in 0, 1, 1, 2, 3, 4:
4573 expected = start.replace(hour=wall)
4574 got = Eastern.fromutc(start)
4575 self.assertEqual(expected, got)
4576
4577 expected = fstart + FEastern.stdoffset
4578 got = FEastern.fromutc(fstart)
4579 self.assertEqual(expected, got)
4580
4581 # Ensure astimezone() calls fromutc() too.
4582 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
4583 self.assertEqual(expected, got)
4584
4585 start += HOUR
4586 fstart += HOUR
4587
4588
4589#############################################################################
4590# oddballs
4591
4592class Oddballs(unittest.TestCase):
4593
4594 def test_bug_1028306(self):
4595 # Trying to compare a date to a datetime should act like a mixed-
4596 # type comparison, despite that datetime is a subclass of date.
4597 as_date = date.today()
4598 as_datetime = datetime.combine(as_date, time())
4599 self.assertTrue(as_date != as_datetime)
4600 self.assertTrue(as_datetime != as_date)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004601 self.assertFalse(as_date == as_datetime)
4602 self.assertFalse(as_datetime == as_date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004603 self.assertRaises(TypeError, lambda: as_date < as_datetime)
4604 self.assertRaises(TypeError, lambda: as_datetime < as_date)
4605 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
4606 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
4607 self.assertRaises(TypeError, lambda: as_date > as_datetime)
4608 self.assertRaises(TypeError, lambda: as_datetime > as_date)
4609 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
4610 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
4611
Raymond Hettinger15f44ab2016-08-30 10:47:49 -07004612 # Nevertheless, comparison should work with the base-class (date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004613 # projection if use of a date method is forced.
4614 self.assertEqual(as_date.__eq__(as_datetime), True)
4615 different_day = (as_date.day + 1) % 20 + 1
4616 as_different = as_datetime.replace(day= different_day)
4617 self.assertEqual(as_date.__eq__(as_different), False)
4618
4619 # And date should compare with other subclasses of date. If a
4620 # subclass wants to stop this, it's up to the subclass to do so.
4621 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
4622 self.assertEqual(as_date, date_sc)
4623 self.assertEqual(date_sc, as_date)
4624
4625 # Ditto for datetimes.
4626 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
4627 as_date.day, 0, 0, 0)
4628 self.assertEqual(as_datetime, datetime_sc)
4629 self.assertEqual(datetime_sc, as_datetime)
4630
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004631 def test_extra_attributes(self):
4632 for x in [date.today(),
4633 time(),
4634 datetime.utcnow(),
4635 timedelta(),
4636 tzinfo(),
4637 timezone(timedelta())]:
4638 with self.assertRaises(AttributeError):
4639 x.abc = 1
4640
4641 def test_check_arg_types(self):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004642 class Number:
4643 def __init__(self, value):
4644 self.value = value
4645 def __int__(self):
4646 return self.value
4647
4648 for xx in [decimal.Decimal(10),
4649 decimal.Decimal('10.9'),
4650 Number(10)]:
4651 self.assertEqual(datetime(10, 10, 10, 10, 10, 10, 10),
4652 datetime(xx, xx, xx, xx, xx, xx, xx))
4653
4654 with self.assertRaisesRegex(TypeError, '^an integer is required '
R David Murray44b548d2016-09-08 13:59:53 -04004655 r'\(got type str\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004656 datetime(10, 10, '10')
4657
4658 f10 = Number(10.9)
4659 with self.assertRaisesRegex(TypeError, '^__int__ returned non-int '
R David Murray44b548d2016-09-08 13:59:53 -04004660 r'\(type float\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004661 datetime(10, 10, f10)
4662
4663 class Float(float):
4664 pass
4665 s10 = Float(10.9)
4666 with self.assertRaisesRegex(TypeError, '^integer argument expected, '
4667 'got float$'):
4668 datetime(10, 10, s10)
4669
4670 with self.assertRaises(TypeError):
4671 datetime(10., 10, 10)
4672 with self.assertRaises(TypeError):
4673 datetime(10, 10., 10)
4674 with self.assertRaises(TypeError):
4675 datetime(10, 10, 10.)
4676 with self.assertRaises(TypeError):
4677 datetime(10, 10, 10, 10.)
4678 with self.assertRaises(TypeError):
4679 datetime(10, 10, 10, 10, 10.)
4680 with self.assertRaises(TypeError):
4681 datetime(10, 10, 10, 10, 10, 10.)
4682 with self.assertRaises(TypeError):
4683 datetime(10, 10, 10, 10, 10, 10, 10.)
4684
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004685#############################################################################
4686# Local Time Disambiguation
4687
4688# An experimental reimplementation of fromutc that respects the "fold" flag.
4689
4690class tzinfo2(tzinfo):
4691
4692 def fromutc(self, dt):
4693 "datetime in UTC -> datetime in local time."
4694
4695 if not isinstance(dt, datetime):
4696 raise TypeError("fromutc() requires a datetime argument")
4697 if dt.tzinfo is not self:
4698 raise ValueError("dt.tzinfo is not self")
4699 # Returned value satisfies
4700 # dt + ldt.utcoffset() = ldt
4701 off0 = dt.replace(fold=0).utcoffset()
4702 off1 = dt.replace(fold=1).utcoffset()
4703 if off0 is None or off1 is None or dt.dst() is None:
4704 raise ValueError
4705 if off0 == off1:
4706 ldt = dt + off0
4707 off1 = ldt.utcoffset()
4708 if off0 == off1:
4709 return ldt
4710 # Now, we discovered both possible offsets, so
4711 # we can just try four possible solutions:
4712 for off in [off0, off1]:
4713 ldt = dt + off
4714 if ldt.utcoffset() == off:
4715 return ldt
4716 ldt = ldt.replace(fold=1)
4717 if ldt.utcoffset() == off:
4718 return ldt
4719
4720 raise ValueError("No suitable local time found")
4721
4722# Reimplementing simplified US timezones to respect the "fold" flag:
4723
4724class USTimeZone2(tzinfo2):
4725
4726 def __init__(self, hours, reprname, stdname, dstname):
4727 self.stdoffset = timedelta(hours=hours)
4728 self.reprname = reprname
4729 self.stdname = stdname
4730 self.dstname = dstname
4731
4732 def __repr__(self):
4733 return self.reprname
4734
4735 def tzname(self, dt):
4736 if self.dst(dt):
4737 return self.dstname
4738 else:
4739 return self.stdname
4740
4741 def utcoffset(self, dt):
4742 return self.stdoffset + self.dst(dt)
4743
4744 def dst(self, dt):
4745 if dt is None or dt.tzinfo is None:
4746 # An exception instead may be sensible here, in one or more of
4747 # the cases.
4748 return ZERO
4749 assert dt.tzinfo is self
4750
4751 # Find first Sunday in April.
4752 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
4753 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
4754
4755 # Find last Sunday in October.
4756 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
4757 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
4758
4759 # Can't compare naive to aware objects, so strip the timezone from
4760 # dt first.
4761 dt = dt.replace(tzinfo=None)
4762 if start + HOUR <= dt < end:
4763 # DST is in effect.
4764 return HOUR
4765 elif end <= dt < end + HOUR:
4766 # Fold (an ambiguous hour): use dt.fold to disambiguate.
4767 return ZERO if dt.fold else HOUR
4768 elif start <= dt < start + HOUR:
4769 # Gap (a non-existent hour): reverse the fold rule.
4770 return HOUR if dt.fold else ZERO
4771 else:
4772 # DST is off.
4773 return ZERO
4774
4775Eastern2 = USTimeZone2(-5, "Eastern2", "EST", "EDT")
4776Central2 = USTimeZone2(-6, "Central2", "CST", "CDT")
4777Mountain2 = USTimeZone2(-7, "Mountain2", "MST", "MDT")
4778Pacific2 = USTimeZone2(-8, "Pacific2", "PST", "PDT")
4779
4780# Europe_Vilnius_1941 tzinfo implementation reproduces the following
4781# 1941 transition from Olson's tzdist:
4782#
4783# Zone NAME GMTOFF RULES FORMAT [UNTIL]
4784# ZoneEurope/Vilnius 1:00 - CET 1940 Aug 3
4785# 3:00 - MSK 1941 Jun 24
4786# 1:00 C-Eur CE%sT 1944 Aug
4787#
4788# $ zdump -v Europe/Vilnius | grep 1941
4789# Europe/Vilnius Mon Jun 23 20:59:59 1941 UTC = Mon Jun 23 23:59:59 1941 MSK isdst=0 gmtoff=10800
4790# Europe/Vilnius Mon Jun 23 21:00:00 1941 UTC = Mon Jun 23 23:00:00 1941 CEST isdst=1 gmtoff=7200
4791
4792class Europe_Vilnius_1941(tzinfo):
4793 def _utc_fold(self):
4794 return [datetime(1941, 6, 23, 21, tzinfo=self), # Mon Jun 23 21:00:00 1941 UTC
4795 datetime(1941, 6, 23, 22, tzinfo=self)] # Mon Jun 23 22:00:00 1941 UTC
4796
4797 def _loc_fold(self):
4798 return [datetime(1941, 6, 23, 23, tzinfo=self), # Mon Jun 23 23:00:00 1941 MSK / CEST
4799 datetime(1941, 6, 24, 0, tzinfo=self)] # Mon Jun 24 00:00:00 1941 CEST
4800
4801 def utcoffset(self, dt):
4802 fold_start, fold_stop = self._loc_fold()
4803 if dt < fold_start:
4804 return 3 * HOUR
4805 if dt < fold_stop:
4806 return (2 if dt.fold else 3) * HOUR
4807 # if dt >= fold_stop
4808 return 2 * HOUR
4809
4810 def dst(self, dt):
4811 fold_start, fold_stop = self._loc_fold()
4812 if dt < fold_start:
4813 return 0 * HOUR
4814 if dt < fold_stop:
4815 return (1 if dt.fold else 0) * HOUR
4816 # if dt >= fold_stop
4817 return 1 * HOUR
4818
4819 def tzname(self, dt):
4820 fold_start, fold_stop = self._loc_fold()
4821 if dt < fold_start:
4822 return 'MSK'
4823 if dt < fold_stop:
4824 return ('MSK', 'CEST')[dt.fold]
4825 # if dt >= fold_stop
4826 return 'CEST'
4827
4828 def fromutc(self, dt):
4829 assert dt.fold == 0
4830 assert dt.tzinfo is self
4831 if dt.year != 1941:
4832 raise NotImplementedError
4833 fold_start, fold_stop = self._utc_fold()
4834 if dt < fold_start:
4835 return dt + 3 * HOUR
4836 if dt < fold_stop:
4837 return (dt + 2 * HOUR).replace(fold=1)
4838 # if dt >= fold_stop
4839 return dt + 2 * HOUR
4840
4841
4842class TestLocalTimeDisambiguation(unittest.TestCase):
4843
4844 def test_vilnius_1941_fromutc(self):
4845 Vilnius = Europe_Vilnius_1941()
4846
4847 gdt = datetime(1941, 6, 23, 20, 59, 59, tzinfo=timezone.utc)
4848 ldt = gdt.astimezone(Vilnius)
4849 self.assertEqual(ldt.strftime("%c %Z%z"),
4850 'Mon Jun 23 23:59:59 1941 MSK+0300')
4851 self.assertEqual(ldt.fold, 0)
4852 self.assertFalse(ldt.dst())
4853
4854 gdt = datetime(1941, 6, 23, 21, tzinfo=timezone.utc)
4855 ldt = gdt.astimezone(Vilnius)
4856 self.assertEqual(ldt.strftime("%c %Z%z"),
4857 'Mon Jun 23 23:00:00 1941 CEST+0200')
4858 self.assertEqual(ldt.fold, 1)
4859 self.assertTrue(ldt.dst())
4860
4861 gdt = datetime(1941, 6, 23, 22, tzinfo=timezone.utc)
4862 ldt = gdt.astimezone(Vilnius)
4863 self.assertEqual(ldt.strftime("%c %Z%z"),
4864 'Tue Jun 24 00:00:00 1941 CEST+0200')
4865 self.assertEqual(ldt.fold, 0)
4866 self.assertTrue(ldt.dst())
4867
4868 def test_vilnius_1941_toutc(self):
4869 Vilnius = Europe_Vilnius_1941()
4870
4871 ldt = datetime(1941, 6, 23, 22, 59, 59, tzinfo=Vilnius)
4872 gdt = ldt.astimezone(timezone.utc)
4873 self.assertEqual(gdt.strftime("%c %Z"),
4874 'Mon Jun 23 19:59:59 1941 UTC')
4875
4876 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius)
4877 gdt = ldt.astimezone(timezone.utc)
4878 self.assertEqual(gdt.strftime("%c %Z"),
4879 'Mon Jun 23 20:59:59 1941 UTC')
4880
4881 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius, fold=1)
4882 gdt = ldt.astimezone(timezone.utc)
4883 self.assertEqual(gdt.strftime("%c %Z"),
4884 'Mon Jun 23 21:59:59 1941 UTC')
4885
4886 ldt = datetime(1941, 6, 24, 0, tzinfo=Vilnius)
4887 gdt = ldt.astimezone(timezone.utc)
4888 self.assertEqual(gdt.strftime("%c %Z"),
4889 'Mon Jun 23 22:00:00 1941 UTC')
4890
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004891 def test_constructors(self):
4892 t = time(0, fold=1)
4893 dt = datetime(1, 1, 1, fold=1)
4894 self.assertEqual(t.fold, 1)
4895 self.assertEqual(dt.fold, 1)
4896 with self.assertRaises(TypeError):
4897 time(0, 0, 0, 0, None, 0)
4898
4899 def test_member(self):
4900 dt = datetime(1, 1, 1, fold=1)
4901 t = dt.time()
4902 self.assertEqual(t.fold, 1)
4903 t = dt.timetz()
4904 self.assertEqual(t.fold, 1)
4905
4906 def test_replace(self):
4907 t = time(0)
4908 dt = datetime(1, 1, 1)
4909 self.assertEqual(t.replace(fold=1).fold, 1)
4910 self.assertEqual(dt.replace(fold=1).fold, 1)
4911 self.assertEqual(t.replace(fold=0).fold, 0)
4912 self.assertEqual(dt.replace(fold=0).fold, 0)
4913 # Check that replacement of other fields does not change "fold".
4914 t = t.replace(fold=1, tzinfo=Eastern)
4915 dt = dt.replace(fold=1, tzinfo=Eastern)
4916 self.assertEqual(t.replace(tzinfo=None).fold, 1)
4917 self.assertEqual(dt.replace(tzinfo=None).fold, 1)
Serhiy Storchaka314d6fc2017-03-31 22:48:16 +03004918 # Out of bounds.
4919 with self.assertRaises(ValueError):
4920 t.replace(fold=2)
4921 with self.assertRaises(ValueError):
4922 dt.replace(fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004923 # Check that fold is a keyword-only argument
4924 with self.assertRaises(TypeError):
4925 t.replace(1, 1, 1, None, 1)
4926 with self.assertRaises(TypeError):
4927 dt.replace(1, 1, 1, 1, 1, 1, 1, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04004928
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004929 def test_comparison(self):
4930 t = time(0)
4931 dt = datetime(1, 1, 1)
4932 self.assertEqual(t, t.replace(fold=1))
4933 self.assertEqual(dt, dt.replace(fold=1))
4934
4935 def test_hash(self):
4936 t = time(0)
4937 dt = datetime(1, 1, 1)
4938 self.assertEqual(hash(t), hash(t.replace(fold=1)))
4939 self.assertEqual(hash(dt), hash(dt.replace(fold=1)))
4940
4941 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4942 def test_fromtimestamp(self):
4943 s = 1414906200
4944 dt0 = datetime.fromtimestamp(s)
4945 dt1 = datetime.fromtimestamp(s + 3600)
4946 self.assertEqual(dt0.fold, 0)
4947 self.assertEqual(dt1.fold, 1)
4948
4949 @support.run_with_tz('Australia/Lord_Howe')
4950 def test_fromtimestamp_lord_howe(self):
4951 tm = _time.localtime(1.4e9)
4952 if _time.strftime('%Z%z', tm) != 'LHST+1030':
4953 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
4954 # $ TZ=Australia/Lord_Howe date -r 1428158700
4955 # Sun Apr 5 01:45:00 LHDT 2015
4956 # $ TZ=Australia/Lord_Howe date -r 1428160500
4957 # Sun Apr 5 01:45:00 LHST 2015
4958 s = 1428158700
4959 t0 = datetime.fromtimestamp(s)
4960 t1 = datetime.fromtimestamp(s + 1800)
4961 self.assertEqual(t0, t1)
4962 self.assertEqual(t0.fold, 0)
4963 self.assertEqual(t1.fold, 1)
4964
Ammar Askar96d1e692018-07-25 09:54:58 -07004965 def test_fromtimestamp_low_fold_detection(self):
4966 # Ensure that fold detection doesn't cause an
4967 # OSError for really low values, see bpo-29097
4968 self.assertEqual(datetime.fromtimestamp(0).fold, 0)
4969
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004970 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4971 def test_timestamp(self):
4972 dt0 = datetime(2014, 11, 2, 1, 30)
4973 dt1 = dt0.replace(fold=1)
4974 self.assertEqual(dt0.timestamp() + 3600,
4975 dt1.timestamp())
4976
4977 @support.run_with_tz('Australia/Lord_Howe')
4978 def test_timestamp_lord_howe(self):
4979 tm = _time.localtime(1.4e9)
4980 if _time.strftime('%Z%z', tm) != 'LHST+1030':
4981 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
4982 t = datetime(2015, 4, 5, 1, 45)
4983 s0 = t.replace(fold=0).timestamp()
4984 s1 = t.replace(fold=1).timestamp()
4985 self.assertEqual(s0 + 1800, s1)
4986
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004987 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4988 def test_astimezone(self):
4989 dt0 = datetime(2014, 11, 2, 1, 30)
4990 dt1 = dt0.replace(fold=1)
4991 # Convert both naive instances to aware.
4992 adt0 = dt0.astimezone()
4993 adt1 = dt1.astimezone()
4994 # Check that the first instance in DST zone and the second in STD
4995 self.assertEqual(adt0.tzname(), 'EDT')
4996 self.assertEqual(adt1.tzname(), 'EST')
4997 self.assertEqual(adt0 + HOUR, adt1)
4998 # Aware instances with fixed offset tzinfo's always have fold=0
4999 self.assertEqual(adt0.fold, 0)
5000 self.assertEqual(adt1.fold, 0)
5001
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005002 def test_pickle_fold(self):
5003 t = time(fold=1)
5004 dt = datetime(1, 1, 1, fold=1)
5005 for pickler, unpickler, proto in pickle_choices:
5006 for x in [t, dt]:
5007 s = pickler.dumps(x, proto)
5008 y = unpickler.loads(s)
5009 self.assertEqual(x, y)
5010 self.assertEqual((0 if proto < 4 else x.fold), y.fold)
5011
5012 def test_repr(self):
5013 t = time(fold=1)
5014 dt = datetime(1, 1, 1, fold=1)
5015 self.assertEqual(repr(t), 'datetime.time(0, 0, fold=1)')
5016 self.assertEqual(repr(dt),
5017 'datetime.datetime(1, 1, 1, 0, 0, fold=1)')
5018
5019 def test_dst(self):
5020 # Let's first establish that things work in regular times.
5021 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
5022 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
5023 self.assertEqual(dt_summer.dst(), HOUR)
5024 self.assertEqual(dt_winter.dst(), ZERO)
5025 # The disambiguation flag is ignored
5026 self.assertEqual(dt_summer.replace(fold=1).dst(), HOUR)
5027 self.assertEqual(dt_winter.replace(fold=1).dst(), ZERO)
5028
5029 # Pick local time in the fold.
5030 for minute in [0, 30, 59]:
5031 dt = datetime(2002, 10, 27, 1, minute, tzinfo=Eastern2)
5032 # With fold=0 (the default) it is in DST.
5033 self.assertEqual(dt.dst(), HOUR)
5034 # With fold=1 it is in STD.
5035 self.assertEqual(dt.replace(fold=1).dst(), ZERO)
5036
5037 # Pick local time in the gap.
5038 for minute in [0, 30, 59]:
5039 dt = datetime(2002, 4, 7, 2, minute, tzinfo=Eastern2)
5040 # With fold=0 (the default) it is in STD.
5041 self.assertEqual(dt.dst(), ZERO)
5042 # With fold=1 it is in DST.
5043 self.assertEqual(dt.replace(fold=1).dst(), HOUR)
5044
5045
5046 def test_utcoffset(self):
5047 # Let's first establish that things work in regular times.
5048 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
5049 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
5050 self.assertEqual(dt_summer.utcoffset(), -4 * HOUR)
5051 self.assertEqual(dt_winter.utcoffset(), -5 * HOUR)
5052 # The disambiguation flag is ignored
5053 self.assertEqual(dt_summer.replace(fold=1).utcoffset(), -4 * HOUR)
5054 self.assertEqual(dt_winter.replace(fold=1).utcoffset(), -5 * HOUR)
5055
5056 def test_fromutc(self):
5057 # Let's first establish that things work in regular times.
5058 u_summer = datetime(2002, 10, 27, 6, tzinfo=Eastern2) - timedelta.resolution
5059 u_winter = datetime(2002, 10, 27, 7, tzinfo=Eastern2)
5060 t_summer = Eastern2.fromutc(u_summer)
5061 t_winter = Eastern2.fromutc(u_winter)
5062 self.assertEqual(t_summer, u_summer - 4 * HOUR)
5063 self.assertEqual(t_winter, u_winter - 5 * HOUR)
5064 self.assertEqual(t_summer.fold, 0)
5065 self.assertEqual(t_winter.fold, 0)
5066
5067 # What happens in the fall-back fold?
5068 u = datetime(2002, 10, 27, 5, 30, tzinfo=Eastern2)
5069 t0 = Eastern2.fromutc(u)
5070 u += HOUR
5071 t1 = Eastern2.fromutc(u)
5072 self.assertEqual(t0, t1)
5073 self.assertEqual(t0.fold, 0)
5074 self.assertEqual(t1.fold, 1)
5075 # The tricky part is when u is in the local fold:
5076 u = datetime(2002, 10, 27, 1, 30, tzinfo=Eastern2)
5077 t = Eastern2.fromutc(u)
5078 self.assertEqual((t.day, t.hour), (26, 21))
5079 # .. or gets into the local fold after a standard time adjustment
5080 u = datetime(2002, 10, 27, 6, 30, tzinfo=Eastern2)
5081 t = Eastern2.fromutc(u)
5082 self.assertEqual((t.day, t.hour), (27, 1))
5083
5084 # What happens in the spring-forward gap?
5085 u = datetime(2002, 4, 7, 2, 0, tzinfo=Eastern2)
5086 t = Eastern2.fromutc(u)
5087 self.assertEqual((t.day, t.hour), (6, 21))
5088
5089 def test_mixed_compare_regular(self):
5090 t = datetime(2000, 1, 1, tzinfo=Eastern2)
5091 self.assertEqual(t, t.astimezone(timezone.utc))
5092 t = datetime(2000, 6, 1, tzinfo=Eastern2)
5093 self.assertEqual(t, t.astimezone(timezone.utc))
5094
5095 def test_mixed_compare_fold(self):
5096 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
5097 t_fold_utc = t_fold.astimezone(timezone.utc)
5098 self.assertNotEqual(t_fold, t_fold_utc)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04005099 self.assertNotEqual(t_fold_utc, t_fold)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005100
5101 def test_mixed_compare_gap(self):
5102 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
5103 t_gap_utc = t_gap.astimezone(timezone.utc)
5104 self.assertNotEqual(t_gap, t_gap_utc)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04005105 self.assertNotEqual(t_gap_utc, t_gap)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005106
5107 def test_hash_aware(self):
5108 t = datetime(2000, 1, 1, tzinfo=Eastern2)
5109 self.assertEqual(hash(t), hash(t.replace(fold=1)))
5110 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
5111 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
5112 self.assertEqual(hash(t_fold), hash(t_fold.replace(fold=1)))
5113 self.assertEqual(hash(t_gap), hash(t_gap.replace(fold=1)))
5114
5115SEC = timedelta(0, 1)
5116
5117def pairs(iterable):
5118 a, b = itertools.tee(iterable)
5119 next(b, None)
5120 return zip(a, b)
5121
5122class ZoneInfo(tzinfo):
5123 zoneroot = '/usr/share/zoneinfo'
5124 def __init__(self, ut, ti):
5125 """
5126
5127 :param ut: array
5128 Array of transition point timestamps
5129 :param ti: list
5130 A list of (offset, isdst, abbr) tuples
5131 :return: None
5132 """
5133 self.ut = ut
5134 self.ti = ti
5135 self.lt = self.invert(ut, ti)
5136
5137 @staticmethod
5138 def invert(ut, ti):
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04005139 lt = (array('q', ut), array('q', ut))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005140 if ut:
5141 offset = ti[0][0] // SEC
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04005142 lt[0][0] += offset
5143 lt[1][0] += offset
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005144 for i in range(1, len(ut)):
5145 lt[0][i] += ti[i-1][0] // SEC
5146 lt[1][i] += ti[i][0] // SEC
5147 return lt
5148
5149 @classmethod
5150 def fromfile(cls, fileobj):
5151 if fileobj.read(4).decode() != "TZif":
5152 raise ValueError("not a zoneinfo file")
5153 fileobj.seek(32)
5154 counts = array('i')
5155 counts.fromfile(fileobj, 3)
5156 if sys.byteorder != 'big':
5157 counts.byteswap()
5158
5159 ut = array('i')
5160 ut.fromfile(fileobj, counts[0])
5161 if sys.byteorder != 'big':
5162 ut.byteswap()
5163
5164 type_indices = array('B')
5165 type_indices.fromfile(fileobj, counts[0])
5166
5167 ttis = []
5168 for i in range(counts[1]):
5169 ttis.append(struct.unpack(">lbb", fileobj.read(6)))
5170
5171 abbrs = fileobj.read(counts[2])
5172
5173 # Convert ttis
5174 for i, (gmtoff, isdst, abbrind) in enumerate(ttis):
5175 abbr = abbrs[abbrind:abbrs.find(0, abbrind)].decode()
5176 ttis[i] = (timedelta(0, gmtoff), isdst, abbr)
5177
5178 ti = [None] * len(ut)
5179 for i, idx in enumerate(type_indices):
5180 ti[i] = ttis[idx]
5181
5182 self = cls(ut, ti)
5183
5184 return self
5185
5186 @classmethod
5187 def fromname(cls, name):
5188 path = os.path.join(cls.zoneroot, name)
5189 with open(path, 'rb') as f:
5190 return cls.fromfile(f)
5191
5192 EPOCHORDINAL = date(1970, 1, 1).toordinal()
5193
5194 def fromutc(self, dt):
5195 """datetime in UTC -> datetime in local time."""
5196
5197 if not isinstance(dt, datetime):
5198 raise TypeError("fromutc() requires a datetime argument")
5199 if dt.tzinfo is not self:
5200 raise ValueError("dt.tzinfo is not self")
5201
5202 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
5203 + dt.hour * 3600
5204 + dt.minute * 60
5205 + dt.second)
5206
5207 if timestamp < self.ut[1]:
5208 tti = self.ti[0]
5209 fold = 0
5210 else:
5211 idx = bisect.bisect_right(self.ut, timestamp)
5212 assert self.ut[idx-1] <= timestamp
5213 assert idx == len(self.ut) or timestamp < self.ut[idx]
5214 tti_prev, tti = self.ti[idx-2:idx]
5215 # Detect fold
5216 shift = tti_prev[0] - tti[0]
5217 fold = (shift > timedelta(0, timestamp - self.ut[idx-1]))
5218 dt += tti[0]
5219 if fold:
5220 return dt.replace(fold=1)
5221 else:
5222 return dt
5223
5224 def _find_ti(self, dt, i):
5225 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
5226 + dt.hour * 3600
5227 + dt.minute * 60
5228 + dt.second)
5229 lt = self.lt[dt.fold]
5230 idx = bisect.bisect_right(lt, timestamp)
5231
5232 return self.ti[max(0, idx - 1)][i]
5233
5234 def utcoffset(self, dt):
5235 return self._find_ti(dt, 0)
5236
5237 def dst(self, dt):
5238 isdst = self._find_ti(dt, 1)
5239 # XXX: We cannot accurately determine the "save" value,
5240 # so let's return 1h whenever DST is in effect. Since
5241 # we don't use dst() in fromutc(), it is unlikely that
5242 # it will be needed for anything more than bool(dst()).
5243 return ZERO if isdst else HOUR
5244
5245 def tzname(self, dt):
5246 return self._find_ti(dt, 2)
5247
5248 @classmethod
5249 def zonenames(cls, zonedir=None):
5250 if zonedir is None:
5251 zonedir = cls.zoneroot
Alexander Belopolsky1b8f26c2016-08-11 11:01:45 -04005252 zone_tab = os.path.join(zonedir, 'zone.tab')
5253 try:
5254 f = open(zone_tab)
5255 except OSError:
5256 return
5257 with f:
5258 for line in f:
5259 line = line.strip()
5260 if line and not line.startswith('#'):
5261 yield line.split()[2]
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005262
5263 @classmethod
5264 def stats(cls, start_year=1):
5265 count = gap_count = fold_count = zeros_count = 0
5266 min_gap = min_fold = timedelta.max
5267 max_gap = max_fold = ZERO
5268 min_gap_datetime = max_gap_datetime = datetime.min
5269 min_gap_zone = max_gap_zone = None
5270 min_fold_datetime = max_fold_datetime = datetime.min
5271 min_fold_zone = max_fold_zone = None
5272 stats_since = datetime(start_year, 1, 1) # Starting from 1970 eliminates a lot of noise
5273 for zonename in cls.zonenames():
5274 count += 1
5275 tz = cls.fromname(zonename)
5276 for dt, shift in tz.transitions():
5277 if dt < stats_since:
5278 continue
5279 if shift > ZERO:
5280 gap_count += 1
5281 if (shift, dt) > (max_gap, max_gap_datetime):
5282 max_gap = shift
5283 max_gap_zone = zonename
5284 max_gap_datetime = dt
5285 if (shift, datetime.max - dt) < (min_gap, datetime.max - min_gap_datetime):
5286 min_gap = shift
5287 min_gap_zone = zonename
5288 min_gap_datetime = dt
5289 elif shift < ZERO:
5290 fold_count += 1
5291 shift = -shift
5292 if (shift, dt) > (max_fold, max_fold_datetime):
5293 max_fold = shift
5294 max_fold_zone = zonename
5295 max_fold_datetime = dt
5296 if (shift, datetime.max - dt) < (min_fold, datetime.max - min_fold_datetime):
5297 min_fold = shift
5298 min_fold_zone = zonename
5299 min_fold_datetime = dt
5300 else:
5301 zeros_count += 1
5302 trans_counts = (gap_count, fold_count, zeros_count)
5303 print("Number of zones: %5d" % count)
5304 print("Number of transitions: %5d = %d (gaps) + %d (folds) + %d (zeros)" %
5305 ((sum(trans_counts),) + trans_counts))
5306 print("Min gap: %16s at %s in %s" % (min_gap, min_gap_datetime, min_gap_zone))
5307 print("Max gap: %16s at %s in %s" % (max_gap, max_gap_datetime, max_gap_zone))
5308 print("Min fold: %16s at %s in %s" % (min_fold, min_fold_datetime, min_fold_zone))
5309 print("Max fold: %16s at %s in %s" % (max_fold, max_fold_datetime, max_fold_zone))
5310
5311
5312 def transitions(self):
5313 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
5314 shift = ti[0] - prev_ti[0]
5315 yield datetime.utcfromtimestamp(t), shift
5316
5317 def nondst_folds(self):
5318 """Find all folds with the same value of isdst on both sides of the transition."""
5319 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
5320 shift = ti[0] - prev_ti[0]
5321 if shift < ZERO and ti[1] == prev_ti[1]:
5322 yield datetime.utcfromtimestamp(t), -shift, prev_ti[2], ti[2]
5323
5324 @classmethod
5325 def print_all_nondst_folds(cls, same_abbr=False, start_year=1):
5326 count = 0
5327 for zonename in cls.zonenames():
5328 tz = cls.fromname(zonename)
5329 for dt, shift, prev_abbr, abbr in tz.nondst_folds():
5330 if dt.year < start_year or same_abbr and prev_abbr != abbr:
5331 continue
5332 count += 1
5333 print("%3d) %-30s %s %10s %5s -> %s" %
5334 (count, zonename, dt, shift, prev_abbr, abbr))
5335
5336 def folds(self):
5337 for t, shift in self.transitions():
5338 if shift < ZERO:
5339 yield t, -shift
5340
5341 def gaps(self):
5342 for t, shift in self.transitions():
5343 if shift > ZERO:
5344 yield t, shift
5345
5346 def zeros(self):
5347 for t, shift in self.transitions():
5348 if not shift:
5349 yield t
5350
5351
5352class ZoneInfoTest(unittest.TestCase):
5353 zonename = 'America/New_York'
5354
5355 def setUp(self):
5356 if sys.platform == "win32":
5357 self.skipTest("Skipping zoneinfo tests on Windows")
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04005358 try:
5359 self.tz = ZoneInfo.fromname(self.zonename)
5360 except FileNotFoundError as err:
5361 self.skipTest("Skipping %s: %s" % (self.zonename, err))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005362
5363 def assertEquivDatetimes(self, a, b):
5364 self.assertEqual((a.replace(tzinfo=None), a.fold, id(a.tzinfo)),
5365 (b.replace(tzinfo=None), b.fold, id(b.tzinfo)))
5366
5367 def test_folds(self):
5368 tz = self.tz
5369 for dt, shift in tz.folds():
5370 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
5371 udt = dt + x
5372 ldt = tz.fromutc(udt.replace(tzinfo=tz))
5373 self.assertEqual(ldt.fold, 1)
5374 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
5375 self.assertEquivDatetimes(adt, ldt)
5376 utcoffset = ldt.utcoffset()
5377 self.assertEqual(ldt.replace(tzinfo=None), udt + utcoffset)
5378 # Round trip
5379 self.assertEquivDatetimes(ldt.astimezone(timezone.utc),
5380 udt.replace(tzinfo=timezone.utc))
5381
5382
5383 for x in [-timedelta.resolution, shift]:
5384 udt = dt + x
5385 udt = udt.replace(tzinfo=tz)
5386 ldt = tz.fromutc(udt)
5387 self.assertEqual(ldt.fold, 0)
5388
5389 def test_gaps(self):
5390 tz = self.tz
5391 for dt, shift in tz.gaps():
5392 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
5393 udt = dt + x
5394 udt = udt.replace(tzinfo=tz)
5395 ldt = tz.fromutc(udt)
5396 self.assertEqual(ldt.fold, 0)
5397 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
5398 self.assertEquivDatetimes(adt, ldt)
5399 utcoffset = ldt.utcoffset()
5400 self.assertEqual(ldt.replace(tzinfo=None), udt.replace(tzinfo=None) + utcoffset)
5401 # Create a local time inside the gap
5402 ldt = tz.fromutc(dt.replace(tzinfo=tz)) - shift + x
5403 self.assertLess(ldt.replace(fold=1).utcoffset(),
5404 ldt.replace(fold=0).utcoffset(),
5405 "At %s." % ldt)
5406
5407 for x in [-timedelta.resolution, shift]:
5408 udt = dt + x
5409 ldt = tz.fromutc(udt.replace(tzinfo=tz))
5410 self.assertEqual(ldt.fold, 0)
5411
5412 def test_system_transitions(self):
5413 if ('Riyadh8' in self.zonename or
5414 # From tzdata NEWS file:
5415 # The files solar87, solar88, and solar89 are no longer distributed.
5416 # They were a negative experiment - that is, a demonstration that
5417 # tz data can represent solar time only with some difficulty and error.
5418 # Their presence in the distribution caused confusion, as Riyadh
5419 # civil time was generally not solar time in those years.
5420 self.zonename.startswith('right/')):
5421 self.skipTest("Skipping %s" % self.zonename)
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04005422 tz = self.tz
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005423 TZ = os.environ.get('TZ')
5424 os.environ['TZ'] = self.zonename
5425 try:
5426 _time.tzset()
5427 for udt, shift in tz.transitions():
Alexander Belopolsky10c2dd22016-08-12 19:08:15 -04005428 if udt.year >= 2037:
5429 # System support for times around the end of 32-bit time_t
5430 # and later is flaky on many systems.
5431 break
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005432 s0 = (udt - datetime(1970, 1, 1)) // SEC
5433 ss = shift // SEC # shift seconds
5434 for x in [-40 * 3600, -20*3600, -1, 0,
5435 ss - 1, ss + 20 * 3600, ss + 40 * 3600]:
5436 s = s0 + x
5437 sdt = datetime.fromtimestamp(s)
5438 tzdt = datetime.fromtimestamp(s, tz).replace(tzinfo=None)
5439 self.assertEquivDatetimes(sdt, tzdt)
5440 s1 = sdt.timestamp()
5441 self.assertEqual(s, s1)
5442 if ss > 0: # gap
5443 # Create local time inside the gap
5444 dt = datetime.fromtimestamp(s0) - shift / 2
5445 ts0 = dt.timestamp()
5446 ts1 = dt.replace(fold=1).timestamp()
5447 self.assertEqual(ts0, s0 + ss / 2)
5448 self.assertEqual(ts1, s0 - ss / 2)
5449 finally:
5450 if TZ is None:
5451 del os.environ['TZ']
5452 else:
5453 os.environ['TZ'] = TZ
5454 _time.tzset()
5455
5456
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04005457class ZoneInfoCompleteTest(unittest.TestSuite):
5458 def __init__(self):
5459 tests = []
5460 if is_resource_enabled('tzdata'):
5461 for name in ZoneInfo.zonenames():
5462 Test = type('ZoneInfoTest[%s]' % name, (ZoneInfoTest,), {})
5463 Test.zonename = name
5464 for method in dir(Test):
5465 if method.startswith('test_'):
5466 tests.append(Test(method))
5467 super().__init__(tests)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005468
5469# Iran had a sub-minute UTC offset before 1946.
5470class IranTest(ZoneInfoTest):
Alexander Belopolsky07e2a0a2016-07-30 11:41:02 -04005471 zonename = 'Asia/Tehran'
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005472
Paul Ganssle04af5b12018-01-24 17:29:30 -05005473
5474class CapiTest(unittest.TestCase):
5475 def setUp(self):
5476 # Since the C API is not present in the _Pure tests, skip all tests
5477 if self.__class__.__name__.endswith('Pure'):
5478 self.skipTest('Not relevant in pure Python')
5479
5480 # This *must* be called, and it must be called first, so until either
5481 # restriction is loosened, we'll call it as part of test setup
5482 _testcapi.test_datetime_capi()
5483
5484 def test_utc_capi(self):
5485 for use_macro in (True, False):
5486 capi_utc = _testcapi.get_timezone_utc_capi(use_macro)
5487
5488 with self.subTest(use_macro=use_macro):
5489 self.assertIs(capi_utc, timezone.utc)
5490
5491 def test_timezones_capi(self):
5492 est_capi, est_macro, est_macro_nn = _testcapi.make_timezones_capi()
5493
5494 exp_named = timezone(timedelta(hours=-5), "EST")
5495 exp_unnamed = timezone(timedelta(hours=-5))
5496
5497 cases = [
5498 ('est_capi', est_capi, exp_named),
5499 ('est_macro', est_macro, exp_named),
5500 ('est_macro_nn', est_macro_nn, exp_unnamed)
5501 ]
5502
5503 for name, tz_act, tz_exp in cases:
5504 with self.subTest(name=name):
5505 self.assertEqual(tz_act, tz_exp)
5506
5507 dt1 = datetime(2000, 2, 4, tzinfo=tz_act)
5508 dt2 = datetime(2000, 2, 4, tzinfo=tz_exp)
5509
5510 self.assertEqual(dt1, dt2)
5511 self.assertEqual(dt1.tzname(), dt2.tzname())
5512
5513 dt_utc = datetime(2000, 2, 4, 5, tzinfo=timezone.utc)
5514
5515 self.assertEqual(dt1.astimezone(timezone.utc), dt_utc)
5516
Paul Gansslea049f572018-02-22 15:15:32 -05005517 def test_timezones_offset_zero(self):
5518 utc0, utc1, non_utc = _testcapi.get_timezones_offset_zero()
5519
5520 with self.subTest(testname="utc0"):
5521 self.assertIs(utc0, timezone.utc)
5522
5523 with self.subTest(testname="utc1"):
5524 self.assertIs(utc1, timezone.utc)
5525
5526 with self.subTest(testname="non_utc"):
5527 self.assertIsNot(non_utc, timezone.utc)
5528
5529 non_utc_exp = timezone(timedelta(hours=0), "")
5530
5531 self.assertEqual(non_utc, non_utc_exp)
5532
5533 dt1 = datetime(2000, 2, 4, tzinfo=non_utc)
5534 dt2 = datetime(2000, 2, 4, tzinfo=non_utc_exp)
5535
5536 self.assertEqual(dt1, dt2)
5537 self.assertEqual(dt1.tzname(), dt2.tzname())
5538
Paul Ganssle04af5b12018-01-24 17:29:30 -05005539 def test_check_date(self):
5540 class DateSubclass(date):
5541 pass
5542
5543 d = date(2011, 1, 1)
5544 ds = DateSubclass(2011, 1, 1)
5545 dt = datetime(2011, 1, 1)
5546
5547 is_date = _testcapi.datetime_check_date
5548
5549 # Check the ones that should be valid
5550 self.assertTrue(is_date(d))
5551 self.assertTrue(is_date(dt))
5552 self.assertTrue(is_date(ds))
5553 self.assertTrue(is_date(d, True))
5554
5555 # Check that the subclasses do not match exactly
5556 self.assertFalse(is_date(dt, True))
5557 self.assertFalse(is_date(ds, True))
5558
5559 # Check that various other things are not dates at all
5560 args = [tuple(), list(), 1, '2011-01-01',
5561 timedelta(1), timezone.utc, time(12, 00)]
5562 for arg in args:
5563 for exact in (True, False):
5564 with self.subTest(arg=arg, exact=exact):
5565 self.assertFalse(is_date(arg, exact))
5566
5567 def test_check_time(self):
5568 class TimeSubclass(time):
5569 pass
5570
5571 t = time(12, 30)
5572 ts = TimeSubclass(12, 30)
5573
5574 is_time = _testcapi.datetime_check_time
5575
5576 # Check the ones that should be valid
5577 self.assertTrue(is_time(t))
5578 self.assertTrue(is_time(ts))
5579 self.assertTrue(is_time(t, True))
5580
5581 # Check that the subclass does not match exactly
5582 self.assertFalse(is_time(ts, True))
5583
5584 # Check that various other things are not times
5585 args = [tuple(), list(), 1, '2011-01-01',
5586 timedelta(1), timezone.utc, date(2011, 1, 1)]
5587
5588 for arg in args:
5589 for exact in (True, False):
5590 with self.subTest(arg=arg, exact=exact):
5591 self.assertFalse(is_time(arg, exact))
5592
5593 def test_check_datetime(self):
5594 class DateTimeSubclass(datetime):
5595 pass
5596
5597 dt = datetime(2011, 1, 1, 12, 30)
5598 dts = DateTimeSubclass(2011, 1, 1, 12, 30)
5599
5600 is_datetime = _testcapi.datetime_check_datetime
5601
5602 # Check the ones that should be valid
5603 self.assertTrue(is_datetime(dt))
5604 self.assertTrue(is_datetime(dts))
5605 self.assertTrue(is_datetime(dt, True))
5606
5607 # Check that the subclass does not match exactly
5608 self.assertFalse(is_datetime(dts, True))
5609
5610 # Check that various other things are not datetimes
5611 args = [tuple(), list(), 1, '2011-01-01',
5612 timedelta(1), timezone.utc, date(2011, 1, 1)]
5613
5614 for arg in args:
5615 for exact in (True, False):
5616 with self.subTest(arg=arg, exact=exact):
5617 self.assertFalse(is_datetime(arg, exact))
5618
5619 def test_check_delta(self):
5620 class TimeDeltaSubclass(timedelta):
5621 pass
5622
5623 td = timedelta(1)
5624 tds = TimeDeltaSubclass(1)
5625
5626 is_timedelta = _testcapi.datetime_check_delta
5627
5628 # Check the ones that should be valid
5629 self.assertTrue(is_timedelta(td))
5630 self.assertTrue(is_timedelta(tds))
5631 self.assertTrue(is_timedelta(td, True))
5632
5633 # Check that the subclass does not match exactly
5634 self.assertFalse(is_timedelta(tds, True))
5635
5636 # Check that various other things are not timedeltas
5637 args = [tuple(), list(), 1, '2011-01-01',
5638 timezone.utc, date(2011, 1, 1), datetime(2011, 1, 1)]
5639
5640 for arg in args:
5641 for exact in (True, False):
5642 with self.subTest(arg=arg, exact=exact):
5643 self.assertFalse(is_timedelta(arg, exact))
5644
5645 def test_check_tzinfo(self):
5646 class TZInfoSubclass(tzinfo):
5647 pass
5648
5649 tzi = tzinfo()
5650 tzis = TZInfoSubclass()
5651 tz = timezone(timedelta(hours=-5))
5652
5653 is_tzinfo = _testcapi.datetime_check_tzinfo
5654
5655 # Check the ones that should be valid
5656 self.assertTrue(is_tzinfo(tzi))
5657 self.assertTrue(is_tzinfo(tz))
5658 self.assertTrue(is_tzinfo(tzis))
5659 self.assertTrue(is_tzinfo(tzi, True))
5660
5661 # Check that the subclasses do not match exactly
5662 self.assertFalse(is_tzinfo(tz, True))
5663 self.assertFalse(is_tzinfo(tzis, True))
5664
5665 # Check that various other things are not tzinfos
5666 args = [tuple(), list(), 1, '2011-01-01',
5667 date(2011, 1, 1), datetime(2011, 1, 1)]
5668
5669 for arg in args:
5670 for exact in (True, False):
5671 with self.subTest(arg=arg, exact=exact):
5672 self.assertFalse(is_tzinfo(arg, exact))
5673
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04005674def load_tests(loader, standard_tests, pattern):
5675 standard_tests.addTest(ZoneInfoCompleteTest())
5676 return standard_tests
5677
5678
Alexander Belopolskycf86e362010-07-23 19:25:47 +00005679if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -05005680 unittest.main()