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