blob: 06fe647fb1a4704022e301d098cdcd24ff77a72e [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()
899
900 class Prod:
901 def __radd__(self, other):
902 return Sum()
903
904 class Sum(int):
905 def __divmod__(self, other):
906 # negative remainder
907 return (0, -1)
908
909 timedelta(microseconds=BadInt(1))
910 timedelta(hours=BadInt(1))
911 timedelta(weeks=BadInt(1))
912
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000913
914#############################################################################
915# date tests
916
917class TestDateOnly(unittest.TestCase):
918 # Tests here won't pass if also run on datetime objects, so don't
919 # subclass this to test datetimes too.
920
921 def test_delta_non_days_ignored(self):
922 dt = date(2000, 1, 2)
923 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
924 microseconds=5)
925 days = timedelta(delta.days)
926 self.assertEqual(days, timedelta(1))
927
928 dt2 = dt + delta
929 self.assertEqual(dt2, dt + days)
930
931 dt2 = delta + dt
932 self.assertEqual(dt2, dt + days)
933
934 dt2 = dt - delta
935 self.assertEqual(dt2, dt - days)
936
937 delta = -delta
938 days = timedelta(delta.days)
939 self.assertEqual(days, timedelta(-2))
940
941 dt2 = dt + delta
942 self.assertEqual(dt2, dt + days)
943
944 dt2 = delta + dt
945 self.assertEqual(dt2, dt + days)
946
947 dt2 = dt - delta
948 self.assertEqual(dt2, dt - days)
949
950class SubclassDate(date):
951 sub_var = 1
952
953class TestDate(HarmlessMixedComparison, unittest.TestCase):
954 # Tests here should pass for both dates and datetimes, except for a
955 # few tests that TestDateTime overrides.
956
957 theclass = date
958
959 def test_basic_attributes(self):
960 dt = self.theclass(2002, 3, 1)
961 self.assertEqual(dt.year, 2002)
962 self.assertEqual(dt.month, 3)
963 self.assertEqual(dt.day, 1)
964
965 def test_roundtrip(self):
966 for dt in (self.theclass(1, 2, 3),
967 self.theclass.today()):
968 # Verify dt -> string -> date identity.
969 s = repr(dt)
970 self.assertTrue(s.startswith('datetime.'))
971 s = s[9:]
972 dt2 = eval(s)
973 self.assertEqual(dt, dt2)
974
975 # Verify identity via reconstructing from pieces.
976 dt2 = self.theclass(dt.year, dt.month, dt.day)
977 self.assertEqual(dt, dt2)
978
979 def test_ordinal_conversions(self):
980 # Check some fixed values.
981 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
982 (1, 12, 31, 365),
983 (2, 1, 1, 366),
984 # first example from "Calendrical Calculations"
985 (1945, 11, 12, 710347)]:
986 d = self.theclass(y, m, d)
987 self.assertEqual(n, d.toordinal())
988 fromord = self.theclass.fromordinal(n)
989 self.assertEqual(d, fromord)
990 if hasattr(fromord, "hour"):
991 # if we're checking something fancier than a date, verify
992 # the extra fields have been zeroed out
993 self.assertEqual(fromord.hour, 0)
994 self.assertEqual(fromord.minute, 0)
995 self.assertEqual(fromord.second, 0)
996 self.assertEqual(fromord.microsecond, 0)
997
998 # Check first and last days of year spottily across the whole
999 # range of years supported.
1000 for year in range(MINYEAR, MAXYEAR+1, 7):
1001 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
1002 d = self.theclass(year, 1, 1)
1003 n = d.toordinal()
1004 d2 = self.theclass.fromordinal(n)
1005 self.assertEqual(d, d2)
1006 # Verify that moving back a day gets to the end of year-1.
1007 if year > 1:
1008 d = self.theclass.fromordinal(n-1)
1009 d2 = self.theclass(year-1, 12, 31)
1010 self.assertEqual(d, d2)
1011 self.assertEqual(d2.toordinal(), n-1)
1012
1013 # Test every day in a leap-year and a non-leap year.
1014 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
1015 for year, isleap in (2000, True), (2002, False):
1016 n = self.theclass(year, 1, 1).toordinal()
1017 for month, maxday in zip(range(1, 13), dim):
1018 if month == 2 and isleap:
1019 maxday += 1
1020 for day in range(1, maxday+1):
1021 d = self.theclass(year, month, day)
1022 self.assertEqual(d.toordinal(), n)
1023 self.assertEqual(d, self.theclass.fromordinal(n))
1024 n += 1
1025
1026 def test_extreme_ordinals(self):
1027 a = self.theclass.min
1028 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
1029 aord = a.toordinal()
1030 b = a.fromordinal(aord)
1031 self.assertEqual(a, b)
1032
1033 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
1034
1035 b = a + timedelta(days=1)
1036 self.assertEqual(b.toordinal(), aord + 1)
1037 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
1038
1039 a = self.theclass.max
1040 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
1041 aord = a.toordinal()
1042 b = a.fromordinal(aord)
1043 self.assertEqual(a, b)
1044
1045 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
1046
1047 b = a - timedelta(days=1)
1048 self.assertEqual(b.toordinal(), aord - 1)
1049 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
1050
1051 def test_bad_constructor_arguments(self):
1052 # bad years
1053 self.theclass(MINYEAR, 1, 1) # no exception
1054 self.theclass(MAXYEAR, 1, 1) # no exception
1055 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1056 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1057 # bad months
1058 self.theclass(2000, 1, 1) # no exception
1059 self.theclass(2000, 12, 1) # no exception
1060 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1061 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1062 # bad days
1063 self.theclass(2000, 2, 29) # no exception
1064 self.theclass(2004, 2, 29) # no exception
1065 self.theclass(2400, 2, 29) # no exception
1066 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1067 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1068 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1069 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1070 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1071 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1072
1073 def test_hash_equality(self):
1074 d = self.theclass(2000, 12, 31)
1075 # same thing
1076 e = self.theclass(2000, 12, 31)
1077 self.assertEqual(d, e)
1078 self.assertEqual(hash(d), hash(e))
1079
1080 dic = {d: 1}
1081 dic[e] = 2
1082 self.assertEqual(len(dic), 1)
1083 self.assertEqual(dic[d], 2)
1084 self.assertEqual(dic[e], 2)
1085
1086 d = self.theclass(2001, 1, 1)
1087 # same thing
1088 e = self.theclass(2001, 1, 1)
1089 self.assertEqual(d, e)
1090 self.assertEqual(hash(d), hash(e))
1091
1092 dic = {d: 1}
1093 dic[e] = 2
1094 self.assertEqual(len(dic), 1)
1095 self.assertEqual(dic[d], 2)
1096 self.assertEqual(dic[e], 2)
1097
1098 def test_computations(self):
1099 a = self.theclass(2002, 1, 31)
1100 b = self.theclass(1956, 1, 31)
1101 c = self.theclass(2001,2,1)
1102
1103 diff = a-b
1104 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1105 self.assertEqual(diff.seconds, 0)
1106 self.assertEqual(diff.microseconds, 0)
1107
1108 day = timedelta(1)
1109 week = timedelta(7)
1110 a = self.theclass(2002, 3, 2)
1111 self.assertEqual(a + day, self.theclass(2002, 3, 3))
1112 self.assertEqual(day + a, self.theclass(2002, 3, 3))
1113 self.assertEqual(a - day, self.theclass(2002, 3, 1))
1114 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
1115 self.assertEqual(a + week, self.theclass(2002, 3, 9))
1116 self.assertEqual(a - week, self.theclass(2002, 2, 23))
1117 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
1118 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
1119 self.assertEqual((a + week) - a, week)
1120 self.assertEqual((a + day) - a, day)
1121 self.assertEqual((a - week) - a, -week)
1122 self.assertEqual((a - day) - a, -day)
1123 self.assertEqual(a - (a + week), -week)
1124 self.assertEqual(a - (a + day), -day)
1125 self.assertEqual(a - (a - week), week)
1126 self.assertEqual(a - (a - day), day)
1127 self.assertEqual(c - (c - day), day)
1128
1129 # Add/sub ints or floats should be illegal
1130 for i in 1, 1.0:
1131 self.assertRaises(TypeError, lambda: a+i)
1132 self.assertRaises(TypeError, lambda: a-i)
1133 self.assertRaises(TypeError, lambda: i+a)
1134 self.assertRaises(TypeError, lambda: i-a)
1135
1136 # delta - date is senseless.
1137 self.assertRaises(TypeError, lambda: day - a)
1138 # mixing date and (delta or date) via * or // is senseless
1139 self.assertRaises(TypeError, lambda: day * a)
1140 self.assertRaises(TypeError, lambda: a * day)
1141 self.assertRaises(TypeError, lambda: day // a)
1142 self.assertRaises(TypeError, lambda: a // day)
1143 self.assertRaises(TypeError, lambda: a * a)
1144 self.assertRaises(TypeError, lambda: a // a)
1145 # date + date is senseless
1146 self.assertRaises(TypeError, lambda: a + a)
1147
1148 def test_overflow(self):
1149 tiny = self.theclass.resolution
1150
1151 for delta in [tiny, timedelta(1), timedelta(2)]:
1152 dt = self.theclass.min + delta
1153 dt -= delta # no problem
1154 self.assertRaises(OverflowError, dt.__sub__, delta)
1155 self.assertRaises(OverflowError, dt.__add__, -delta)
1156
1157 dt = self.theclass.max - delta
1158 dt += delta # no problem
1159 self.assertRaises(OverflowError, dt.__add__, delta)
1160 self.assertRaises(OverflowError, dt.__sub__, -delta)
1161
1162 def test_fromtimestamp(self):
1163 import time
1164
1165 # Try an arbitrary fixed value.
1166 year, month, day = 1999, 9, 19
1167 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
1168 d = self.theclass.fromtimestamp(ts)
1169 self.assertEqual(d.year, year)
1170 self.assertEqual(d.month, month)
1171 self.assertEqual(d.day, day)
1172
1173 def test_insane_fromtimestamp(self):
1174 # It's possible that some platform maps time_t to double,
1175 # and that this test will fail there. This test should
1176 # exempt such platforms (provided they return reasonable
1177 # results!).
1178 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001179 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001180 insane)
1181
1182 def test_today(self):
1183 import time
1184
1185 # We claim that today() is like fromtimestamp(time.time()), so
1186 # prove it.
1187 for dummy in range(3):
1188 today = self.theclass.today()
1189 ts = time.time()
1190 todayagain = self.theclass.fromtimestamp(ts)
1191 if today == todayagain:
1192 break
1193 # There are several legit reasons that could fail:
1194 # 1. It recently became midnight, between the today() and the
1195 # time() calls.
1196 # 2. The platform time() has such fine resolution that we'll
1197 # never get the same value twice.
1198 # 3. The platform time() has poor resolution, and we just
1199 # happened to call today() right before a resolution quantum
1200 # boundary.
1201 # 4. The system clock got fiddled between calls.
1202 # In any case, wait a little while and try again.
1203 time.sleep(0.1)
1204
1205 # It worked or it didn't. If it didn't, assume it's reason #2, and
1206 # let the test pass if they're within half a second of each other.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001207 if today != todayagain:
1208 self.assertAlmostEqual(todayagain, today,
1209 delta=timedelta(seconds=0.5))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001210
1211 def test_weekday(self):
1212 for i in range(7):
1213 # March 4, 2002 is a Monday
1214 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
1215 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
1216 # January 2, 1956 is a Monday
1217 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
1218 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
1219
1220 def test_isocalendar(self):
1221 # Check examples from
1222 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1223 for i in range(7):
1224 d = self.theclass(2003, 12, 22+i)
1225 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
1226 d = self.theclass(2003, 12, 29) + timedelta(i)
1227 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
1228 d = self.theclass(2004, 1, 5+i)
1229 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
1230 d = self.theclass(2009, 12, 21+i)
1231 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
1232 d = self.theclass(2009, 12, 28) + timedelta(i)
1233 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
1234 d = self.theclass(2010, 1, 4+i)
1235 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
1236
1237 def test_iso_long_years(self):
1238 # Calculate long ISO years and compare to table from
1239 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1240 ISO_LONG_YEARS_TABLE = """
1241 4 32 60 88
1242 9 37 65 93
1243 15 43 71 99
1244 20 48 76
1245 26 54 82
1246
1247 105 133 161 189
1248 111 139 167 195
1249 116 144 172
1250 122 150 178
1251 128 156 184
1252
1253 201 229 257 285
1254 207 235 263 291
1255 212 240 268 296
1256 218 246 274
1257 224 252 280
1258
1259 303 331 359 387
1260 308 336 364 392
1261 314 342 370 398
1262 320 348 376
1263 325 353 381
1264 """
1265 iso_long_years = sorted(map(int, ISO_LONG_YEARS_TABLE.split()))
1266 L = []
1267 for i in range(400):
1268 d = self.theclass(2000+i, 12, 31)
1269 d1 = self.theclass(1600+i, 12, 31)
1270 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
1271 if d.isocalendar()[1] == 53:
1272 L.append(i)
1273 self.assertEqual(L, iso_long_years)
1274
1275 def test_isoformat(self):
1276 t = self.theclass(2, 3, 2)
1277 self.assertEqual(t.isoformat(), "0002-03-02")
1278
1279 def test_ctime(self):
1280 t = self.theclass(2002, 3, 2)
1281 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
1282
1283 def test_strftime(self):
1284 t = self.theclass(2005, 3, 2)
1285 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
1286 self.assertEqual(t.strftime(""), "") # SF bug #761337
1287 self.assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784
1288
1289 self.assertRaises(TypeError, t.strftime) # needs an arg
1290 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
1291 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
1292
1293 # test that unicode input is allowed (issue 2782)
1294 self.assertEqual(t.strftime("%m"), "03")
1295
1296 # A naive object replaces %z and %Z w/ empty strings.
1297 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1298
1299 #make sure that invalid format specifiers are handled correctly
1300 #self.assertRaises(ValueError, t.strftime, "%e")
1301 #self.assertRaises(ValueError, t.strftime, "%")
1302 #self.assertRaises(ValueError, t.strftime, "%#")
1303
1304 #oh well, some systems just ignore those invalid ones.
Martin Panter46f50722016-05-26 05:35:26 +00001305 #at least, exercise them to make sure that no crashes
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001306 #are generated
1307 for f in ["%e", "%", "%#"]:
1308 try:
1309 t.strftime(f)
1310 except ValueError:
1311 pass
1312
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03001313 # bpo-34482: Check that surrogates don't cause a crash.
1314 try:
1315 t.strftime('%y\ud800%m')
1316 except UnicodeEncodeError:
1317 pass
1318
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001319 #check that this standard extension works
1320 t.strftime("%f")
1321
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001322 def test_format(self):
1323 dt = self.theclass(2007, 9, 10)
1324 self.assertEqual(dt.__format__(''), str(dt))
1325
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02001326 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001327 dt.__format__(123)
1328
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001329 # check that a derived class's __str__() gets called
1330 class A(self.theclass):
1331 def __str__(self):
1332 return 'A'
1333 a = A(2007, 9, 10)
1334 self.assertEqual(a.__format__(''), 'A')
1335
1336 # check that a derived class's strftime gets called
1337 class B(self.theclass):
1338 def strftime(self, format_spec):
1339 return 'B'
1340 b = B(2007, 9, 10)
1341 self.assertEqual(b.__format__(''), str(dt))
1342
1343 for fmt in ["m:%m d:%d y:%y",
1344 "m:%m d:%d y:%y H:%H M:%M S:%S",
1345 "%z %Z",
1346 ]:
1347 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1348 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1349 self.assertEqual(b.__format__(fmt), 'B')
1350
1351 def test_resolution_info(self):
1352 # XXX: Should min and max respect subclassing?
1353 if issubclass(self.theclass, datetime):
1354 expected_class = datetime
1355 else:
1356 expected_class = date
1357 self.assertIsInstance(self.theclass.min, expected_class)
1358 self.assertIsInstance(self.theclass.max, expected_class)
1359 self.assertIsInstance(self.theclass.resolution, timedelta)
1360 self.assertTrue(self.theclass.max > self.theclass.min)
1361
1362 def test_extreme_timedelta(self):
1363 big = self.theclass.max - self.theclass.min
1364 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
1365 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
1366 # n == 315537897599999999 ~= 2**58.13
1367 justasbig = timedelta(0, 0, n)
1368 self.assertEqual(big, justasbig)
1369 self.assertEqual(self.theclass.min + big, self.theclass.max)
1370 self.assertEqual(self.theclass.max - big, self.theclass.min)
1371
1372 def test_timetuple(self):
1373 for i in range(7):
1374 # January 2, 1956 is a Monday (0)
1375 d = self.theclass(1956, 1, 2+i)
1376 t = d.timetuple()
1377 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
1378 # February 1, 1956 is a Wednesday (2)
1379 d = self.theclass(1956, 2, 1+i)
1380 t = d.timetuple()
1381 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
1382 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
1383 # of the year.
1384 d = self.theclass(1956, 3, 1+i)
1385 t = d.timetuple()
1386 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
1387 self.assertEqual(t.tm_year, 1956)
1388 self.assertEqual(t.tm_mon, 3)
1389 self.assertEqual(t.tm_mday, 1+i)
1390 self.assertEqual(t.tm_hour, 0)
1391 self.assertEqual(t.tm_min, 0)
1392 self.assertEqual(t.tm_sec, 0)
1393 self.assertEqual(t.tm_wday, (3+i)%7)
1394 self.assertEqual(t.tm_yday, 61+i)
1395 self.assertEqual(t.tm_isdst, -1)
1396
1397 def test_pickling(self):
1398 args = 6, 7, 23
1399 orig = self.theclass(*args)
1400 for pickler, unpickler, proto in pickle_choices:
1401 green = pickler.dumps(orig, proto)
1402 derived = unpickler.loads(green)
1403 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02001404 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001405
1406 def test_compare(self):
1407 t1 = self.theclass(2, 3, 4)
1408 t2 = self.theclass(2, 3, 4)
1409 self.assertEqual(t1, t2)
1410 self.assertTrue(t1 <= t2)
1411 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001412 self.assertFalse(t1 != t2)
1413 self.assertFalse(t1 < t2)
1414 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001415
1416 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
1417 t2 = self.theclass(*args) # this is larger than t1
1418 self.assertTrue(t1 < t2)
1419 self.assertTrue(t2 > t1)
1420 self.assertTrue(t1 <= t2)
1421 self.assertTrue(t2 >= t1)
1422 self.assertTrue(t1 != t2)
1423 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001424 self.assertFalse(t1 == t2)
1425 self.assertFalse(t2 == t1)
1426 self.assertFalse(t1 > t2)
1427 self.assertFalse(t2 < t1)
1428 self.assertFalse(t1 >= t2)
1429 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001430
1431 for badarg in OTHERSTUFF:
1432 self.assertEqual(t1 == badarg, False)
1433 self.assertEqual(t1 != badarg, True)
1434 self.assertEqual(badarg == t1, False)
1435 self.assertEqual(badarg != t1, True)
1436
1437 self.assertRaises(TypeError, lambda: t1 < badarg)
1438 self.assertRaises(TypeError, lambda: t1 > badarg)
1439 self.assertRaises(TypeError, lambda: t1 >= badarg)
1440 self.assertRaises(TypeError, lambda: badarg <= t1)
1441 self.assertRaises(TypeError, lambda: badarg < t1)
1442 self.assertRaises(TypeError, lambda: badarg > t1)
1443 self.assertRaises(TypeError, lambda: badarg >= t1)
1444
1445 def test_mixed_compare(self):
1446 our = self.theclass(2000, 4, 5)
1447
1448 # Our class can be compared for equality to other classes
1449 self.assertEqual(our == 1, False)
1450 self.assertEqual(1 == our, False)
1451 self.assertEqual(our != 1, True)
1452 self.assertEqual(1 != our, True)
1453
1454 # But the ordering is undefined
1455 self.assertRaises(TypeError, lambda: our < 1)
1456 self.assertRaises(TypeError, lambda: 1 < our)
1457
1458 # Repeat those tests with a different class
1459
1460 class SomeClass:
1461 pass
1462
1463 their = SomeClass()
1464 self.assertEqual(our == their, False)
1465 self.assertEqual(their == our, False)
1466 self.assertEqual(our != their, True)
1467 self.assertEqual(their != our, True)
1468 self.assertRaises(TypeError, lambda: our < their)
1469 self.assertRaises(TypeError, lambda: their < our)
1470
1471 # However, if the other class explicitly defines ordering
1472 # relative to our class, it is allowed to do so
1473
1474 class LargerThanAnything:
1475 def __lt__(self, other):
1476 return False
1477 def __le__(self, other):
1478 return isinstance(other, LargerThanAnything)
1479 def __eq__(self, other):
1480 return isinstance(other, LargerThanAnything)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001481 def __gt__(self, other):
1482 return not isinstance(other, LargerThanAnything)
1483 def __ge__(self, other):
1484 return True
1485
1486 their = LargerThanAnything()
1487 self.assertEqual(our == their, False)
1488 self.assertEqual(their == our, False)
1489 self.assertEqual(our != their, True)
1490 self.assertEqual(their != our, True)
1491 self.assertEqual(our < their, True)
1492 self.assertEqual(their < our, False)
1493
1494 def test_bool(self):
1495 # All dates are considered true.
1496 self.assertTrue(self.theclass.min)
1497 self.assertTrue(self.theclass.max)
1498
Alexander Belopolsky89da3492011-05-02 13:14:24 -04001499 def test_strftime_y2k(self):
1500 for y in (1, 49, 70, 99, 100, 999, 1000, 1970):
Florent Xicluna49ce0682011-11-01 12:56:14 +01001501 d = self.theclass(y, 1, 1)
1502 # Issue 13305: For years < 1000, the value is not always
1503 # padded to 4 digits across platforms. The C standard
1504 # assumes year >= 1900, so it does not specify the number
1505 # of digits.
1506 if d.strftime("%Y") != '%04d' % y:
1507 # Year 42 returns '42', not padded
1508 self.assertEqual(d.strftime("%Y"), '%d' % y)
Victor Stinner15a83e82016-03-12 08:16:48 +01001509 # '0042' is obtained anyway
1510 self.assertEqual(d.strftime("%4Y"), '%04d' % y)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001511
1512 def test_replace(self):
1513 cls = self.theclass
1514 args = [1, 2, 3]
1515 base = cls(*args)
1516 self.assertEqual(base, base.replace())
1517
1518 i = 0
1519 for name, newval in (("year", 2),
1520 ("month", 3),
1521 ("day", 4)):
1522 newargs = args[:]
1523 newargs[i] = newval
1524 expected = cls(*newargs)
1525 got = base.replace(**{name: newval})
1526 self.assertEqual(expected, got)
1527 i += 1
1528
1529 # Out of bounds.
1530 base = cls(2000, 2, 29)
1531 self.assertRaises(ValueError, base.replace, year=2001)
1532
Paul Ganssle191e9932017-11-09 16:34:29 -05001533 def test_subclass_replace(self):
1534 class DateSubclass(self.theclass):
1535 pass
1536
1537 dt = DateSubclass(2012, 1, 1)
1538 self.assertIs(type(dt.replace(year=2013)), DateSubclass)
1539
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001540 def test_subclass_date(self):
1541
1542 class C(self.theclass):
1543 theAnswer = 42
1544
1545 def __new__(cls, *args, **kws):
1546 temp = kws.copy()
1547 extra = temp.pop('extra')
1548 result = self.theclass.__new__(cls, *args, **temp)
1549 result.extra = extra
1550 return result
1551
1552 def newmeth(self, start):
1553 return start + self.year + self.month
1554
1555 args = 2003, 4, 14
1556
1557 dt1 = self.theclass(*args)
1558 dt2 = C(*args, **{'extra': 7})
1559
1560 self.assertEqual(dt2.__class__, C)
1561 self.assertEqual(dt2.theAnswer, 42)
1562 self.assertEqual(dt2.extra, 7)
1563 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1564 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1565
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05001566 def test_subclass_alternate_constructors(self):
1567 # Test that alternate constructors call the constructor
1568 class DateSubclass(self.theclass):
1569 def __new__(cls, *args, **kwargs):
1570 result = self.theclass.__new__(cls, *args, **kwargs)
1571 result.extra = 7
1572
1573 return result
1574
1575 args = (2003, 4, 14)
1576 d_ord = 731319 # Equivalent ordinal date
1577 d_isoformat = '2003-04-14' # Equivalent isoformat()
1578
1579 base_d = DateSubclass(*args)
1580 self.assertIsInstance(base_d, DateSubclass)
1581 self.assertEqual(base_d.extra, 7)
1582
1583 # Timestamp depends on time zone, so we'll calculate the equivalent here
1584 ts = datetime.combine(base_d, time(0)).timestamp()
1585
1586 test_cases = [
1587 ('fromordinal', (d_ord,)),
1588 ('fromtimestamp', (ts,)),
1589 ('fromisoformat', (d_isoformat,)),
1590 ]
1591
1592 for constr_name, constr_args in test_cases:
1593 for base_obj in (DateSubclass, base_d):
1594 # Test both the classmethod and method
1595 with self.subTest(base_obj_type=type(base_obj),
1596 constr_name=constr_name):
1597 constr = getattr(base_obj, constr_name)
1598
1599 dt = constr(*constr_args)
1600
1601 # Test that it creates the right subclass
1602 self.assertIsInstance(dt, DateSubclass)
1603
1604 # Test that it's equal to the base object
1605 self.assertEqual(dt, base_d)
1606
1607 # Test that it called the constructor
1608 self.assertEqual(dt.extra, 7)
1609
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001610 def test_pickling_subclass_date(self):
1611
1612 args = 6, 7, 23
1613 orig = SubclassDate(*args)
1614 for pickler, unpickler, proto in pickle_choices:
1615 green = pickler.dumps(orig, proto)
1616 derived = unpickler.loads(green)
1617 self.assertEqual(orig, derived)
1618
1619 def test_backdoor_resistance(self):
1620 # For fast unpickling, the constructor accepts a pickle byte string.
1621 # This is a low-overhead backdoor. A user can (by intent or
1622 # mistake) pass a string directly, which (if it's the right length)
1623 # will get treated like a pickle, and bypass the normal sanity
1624 # checks in the constructor. This can create insane objects.
1625 # The constructor doesn't want to burn the time to validate all
1626 # fields, but does check the month field. This stops, e.g.,
1627 # datetime.datetime('1995-03-25') from yielding an insane object.
1628 base = b'1995-03-25'
1629 if not issubclass(self.theclass, datetime):
1630 base = base[:4]
1631 for month_byte in b'9', b'\0', b'\r', b'\xff':
1632 self.assertRaises(TypeError, self.theclass,
1633 base[:2] + month_byte + base[3:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001634 if issubclass(self.theclass, datetime):
1635 # Good bytes, but bad tzinfo:
1636 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
1637 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001638
1639 for ord_byte in range(1, 13):
1640 # This shouldn't blow up because of the month byte alone. If
1641 # the implementation changes to do more-careful checking, it may
1642 # blow up because other fields are insane.
1643 self.theclass(base[:2] + bytes([ord_byte]) + base[3:])
1644
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001645 def test_fromisoformat(self):
1646 # Test that isoformat() is reversible
1647 base_dates = [
1648 (1, 1, 1),
1649 (1000, 2, 14),
1650 (1900, 1, 1),
1651 (2000, 2, 29),
1652 (2004, 11, 12),
1653 (2004, 4, 3),
1654 (2017, 5, 30)
1655 ]
1656
1657 for dt_tuple in base_dates:
1658 dt = self.theclass(*dt_tuple)
1659 dt_str = dt.isoformat()
1660 with self.subTest(dt_str=dt_str):
1661 dt_rt = self.theclass.fromisoformat(dt.isoformat())
1662
1663 self.assertEqual(dt, dt_rt)
1664
1665 def test_fromisoformat_subclass(self):
1666 class DateSubclass(self.theclass):
1667 pass
1668
1669 dt = DateSubclass(2014, 12, 14)
1670
1671 dt_rt = DateSubclass.fromisoformat(dt.isoformat())
1672
1673 self.assertIsInstance(dt_rt, DateSubclass)
1674
1675 def test_fromisoformat_fails(self):
1676 # Test that fromisoformat() fails on invalid values
1677 bad_strs = [
1678 '', # Empty string
Paul Ganssle096329f2018-08-23 11:06:20 -04001679 '\ud800', # bpo-34454: Surrogate code point
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001680 '009-03-04', # Not 10 characters
1681 '123456789', # Not a date
1682 '200a-12-04', # Invalid character in year
1683 '2009-1a-04', # Invalid character in month
1684 '2009-12-0a', # Invalid character in day
1685 '2009-01-32', # Invalid day
1686 '2009-02-29', # Invalid leap day
1687 '20090228', # Valid ISO8601 output not from isoformat()
Paul Ganssle096329f2018-08-23 11:06:20 -04001688 '2009\ud80002\ud80028', # Separators are surrogate codepoints
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001689 ]
1690
1691 for bad_str in bad_strs:
1692 with self.assertRaises(ValueError):
1693 self.theclass.fromisoformat(bad_str)
1694
1695 def test_fromisoformat_fails_typeerror(self):
1696 # Test that fromisoformat fails when passed the wrong type
1697 import io
1698
1699 bad_types = [b'2009-03-01', None, io.StringIO('2009-03-01')]
1700 for bad_type in bad_types:
1701 with self.assertRaises(TypeError):
1702 self.theclass.fromisoformat(bad_type)
1703
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001704#############################################################################
1705# datetime tests
1706
1707class SubclassDatetime(datetime):
1708 sub_var = 1
1709
1710class TestDateTime(TestDate):
1711
1712 theclass = datetime
1713
1714 def test_basic_attributes(self):
1715 dt = self.theclass(2002, 3, 1, 12, 0)
1716 self.assertEqual(dt.year, 2002)
1717 self.assertEqual(dt.month, 3)
1718 self.assertEqual(dt.day, 1)
1719 self.assertEqual(dt.hour, 12)
1720 self.assertEqual(dt.minute, 0)
1721 self.assertEqual(dt.second, 0)
1722 self.assertEqual(dt.microsecond, 0)
1723
1724 def test_basic_attributes_nonzero(self):
1725 # Make sure all attributes are non-zero so bugs in
1726 # bit-shifting access show up.
1727 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1728 self.assertEqual(dt.year, 2002)
1729 self.assertEqual(dt.month, 3)
1730 self.assertEqual(dt.day, 1)
1731 self.assertEqual(dt.hour, 12)
1732 self.assertEqual(dt.minute, 59)
1733 self.assertEqual(dt.second, 59)
1734 self.assertEqual(dt.microsecond, 8000)
1735
1736 def test_roundtrip(self):
1737 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1738 self.theclass.now()):
1739 # Verify dt -> string -> datetime identity.
1740 s = repr(dt)
1741 self.assertTrue(s.startswith('datetime.'))
1742 s = s[9:]
1743 dt2 = eval(s)
1744 self.assertEqual(dt, dt2)
1745
1746 # Verify identity via reconstructing from pieces.
1747 dt2 = self.theclass(dt.year, dt.month, dt.day,
1748 dt.hour, dt.minute, dt.second,
1749 dt.microsecond)
1750 self.assertEqual(dt, dt2)
1751
1752 def test_isoformat(self):
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001753 t = self.theclass(1, 2, 3, 4, 5, 1, 123)
1754 self.assertEqual(t.isoformat(), "0001-02-03T04:05:01.000123")
1755 self.assertEqual(t.isoformat('T'), "0001-02-03T04:05:01.000123")
1756 self.assertEqual(t.isoformat(' '), "0001-02-03 04:05:01.000123")
1757 self.assertEqual(t.isoformat('\x00'), "0001-02-03\x0004:05:01.000123")
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03001758 # bpo-34482: Check that surrogates are handled properly.
1759 self.assertEqual(t.isoformat('\ud800'),
1760 "0001-02-03\ud80004:05:01.000123")
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001761 self.assertEqual(t.isoformat(timespec='hours'), "0001-02-03T04")
1762 self.assertEqual(t.isoformat(timespec='minutes'), "0001-02-03T04:05")
1763 self.assertEqual(t.isoformat(timespec='seconds'), "0001-02-03T04:05:01")
1764 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1765 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000123")
1766 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01.000123")
1767 self.assertEqual(t.isoformat(sep=' ', timespec='minutes'), "0001-02-03 04:05")
1768 self.assertRaises(ValueError, t.isoformat, timespec='foo')
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03001769 # bpo-34482: Check that surrogates are handled properly.
1770 self.assertRaises(ValueError, t.isoformat, timespec='\ud800')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001771 # str is ISO format with the separator forced to a blank.
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001772 self.assertEqual(str(t), "0001-02-03 04:05:01.000123")
1773
1774 t = self.theclass(1, 2, 3, 4, 5, 1, 999500, tzinfo=timezone.utc)
1775 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999+00:00")
1776
1777 t = self.theclass(1, 2, 3, 4, 5, 1, 999500)
1778 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999")
1779
1780 t = self.theclass(1, 2, 3, 4, 5, 1)
1781 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01")
1782 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1783 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000000")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001784
1785 t = self.theclass(2, 3, 2)
1786 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1787 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1788 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1789 # str is ISO format with the separator forced to a blank.
1790 self.assertEqual(str(t), "0002-03-02 00:00:00")
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001791 # ISO format with timezone
1792 tz = FixedOffset(timedelta(seconds=16), 'XXX')
1793 t = self.theclass(2, 3, 2, tzinfo=tz)
1794 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00+00:00:16")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001795
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001796 def test_isoformat_timezone(self):
1797 tzoffsets = [
1798 ('05:00', timedelta(hours=5)),
1799 ('02:00', timedelta(hours=2)),
1800 ('06:27', timedelta(hours=6, minutes=27)),
1801 ('12:32:30', timedelta(hours=12, minutes=32, seconds=30)),
1802 ('02:04:09.123456', timedelta(hours=2, minutes=4, seconds=9, microseconds=123456))
1803 ]
1804
1805 tzinfos = [
1806 ('', None),
1807 ('+00:00', timezone.utc),
1808 ('+00:00', timezone(timedelta(0))),
1809 ]
1810
1811 tzinfos += [
1812 (prefix + expected, timezone(sign * td))
1813 for expected, td in tzoffsets
1814 for prefix, sign in [('-', -1), ('+', 1)]
1815 ]
1816
1817 dt_base = self.theclass(2016, 4, 1, 12, 37, 9)
1818 exp_base = '2016-04-01T12:37:09'
1819
1820 for exp_tz, tzi in tzinfos:
1821 dt = dt_base.replace(tzinfo=tzi)
1822 exp = exp_base + exp_tz
1823 with self.subTest(tzi=tzi):
1824 assert dt.isoformat() == exp
1825
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001826 def test_format(self):
1827 dt = self.theclass(2007, 9, 10, 4, 5, 1, 123)
1828 self.assertEqual(dt.__format__(''), str(dt))
1829
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02001830 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001831 dt.__format__(123)
1832
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001833 # check that a derived class's __str__() gets called
1834 class A(self.theclass):
1835 def __str__(self):
1836 return 'A'
1837 a = A(2007, 9, 10, 4, 5, 1, 123)
1838 self.assertEqual(a.__format__(''), 'A')
1839
1840 # check that a derived class's strftime gets called
1841 class B(self.theclass):
1842 def strftime(self, format_spec):
1843 return 'B'
1844 b = B(2007, 9, 10, 4, 5, 1, 123)
1845 self.assertEqual(b.__format__(''), str(dt))
1846
1847 for fmt in ["m:%m d:%d y:%y",
1848 "m:%m d:%d y:%y H:%H M:%M S:%S",
1849 "%z %Z",
1850 ]:
1851 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1852 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1853 self.assertEqual(b.__format__(fmt), 'B')
1854
1855 def test_more_ctime(self):
1856 # Test fields that TestDate doesn't touch.
1857 import time
1858
1859 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1860 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1861 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1862 # out. The difference is that t.ctime() produces " 2" for the day,
1863 # but platform ctime() produces "02" for the day. According to
1864 # C99, t.ctime() is correct here.
1865 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1866
1867 # So test a case where that difference doesn't matter.
1868 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1869 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1870
1871 def test_tz_independent_comparing(self):
1872 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1873 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1874 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1875 self.assertEqual(dt1, dt3)
1876 self.assertTrue(dt2 > dt3)
1877
1878 # Make sure comparison doesn't forget microseconds, and isn't done
1879 # via comparing a float timestamp (an IEEE double doesn't have enough
Leo Ariasc3d95082018-02-03 18:36:10 -06001880 # precision to span microsecond resolution across years 1 through 9999,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001881 # so comparing via timestamp necessarily calls some distinct values
1882 # equal).
1883 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1884 us = timedelta(microseconds=1)
1885 dt2 = dt1 + us
1886 self.assertEqual(dt2 - dt1, us)
1887 self.assertTrue(dt1 < dt2)
1888
1889 def test_strftime_with_bad_tzname_replace(self):
1890 # verify ok if tzinfo.tzname().replace() returns a non-string
1891 class MyTzInfo(FixedOffset):
1892 def tzname(self, dt):
1893 class MyStr(str):
1894 def replace(self, *args):
1895 return None
1896 return MyStr('name')
1897 t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
1898 self.assertRaises(TypeError, t.strftime, '%Z')
1899
1900 def test_bad_constructor_arguments(self):
1901 # bad years
1902 self.theclass(MINYEAR, 1, 1) # no exception
1903 self.theclass(MAXYEAR, 1, 1) # no exception
1904 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1905 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1906 # bad months
1907 self.theclass(2000, 1, 1) # no exception
1908 self.theclass(2000, 12, 1) # no exception
1909 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1910 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1911 # bad days
1912 self.theclass(2000, 2, 29) # no exception
1913 self.theclass(2004, 2, 29) # no exception
1914 self.theclass(2400, 2, 29) # no exception
1915 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1916 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1917 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1918 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1919 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1920 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1921 # bad hours
1922 self.theclass(2000, 1, 31, 0) # no exception
1923 self.theclass(2000, 1, 31, 23) # no exception
1924 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1925 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1926 # bad minutes
1927 self.theclass(2000, 1, 31, 23, 0) # no exception
1928 self.theclass(2000, 1, 31, 23, 59) # no exception
1929 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1930 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1931 # bad seconds
1932 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1933 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1934 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1935 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1936 # bad microseconds
1937 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1938 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1939 self.assertRaises(ValueError, self.theclass,
1940 2000, 1, 31, 23, 59, 59, -1)
1941 self.assertRaises(ValueError, self.theclass,
1942 2000, 1, 31, 23, 59, 59,
1943 1000000)
Alexander Belopolsky47649ab2016-08-08 17:05:40 -04001944 # bad fold
1945 self.assertRaises(ValueError, self.theclass,
1946 2000, 1, 31, fold=-1)
1947 self.assertRaises(ValueError, self.theclass,
1948 2000, 1, 31, fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001949 # Positional fold:
1950 self.assertRaises(TypeError, self.theclass,
1951 2000, 1, 31, 23, 59, 59, 0, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04001952
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001953 def test_hash_equality(self):
1954 d = self.theclass(2000, 12, 31, 23, 30, 17)
1955 e = self.theclass(2000, 12, 31, 23, 30, 17)
1956 self.assertEqual(d, e)
1957 self.assertEqual(hash(d), hash(e))
1958
1959 dic = {d: 1}
1960 dic[e] = 2
1961 self.assertEqual(len(dic), 1)
1962 self.assertEqual(dic[d], 2)
1963 self.assertEqual(dic[e], 2)
1964
1965 d = self.theclass(2001, 1, 1, 0, 5, 17)
1966 e = self.theclass(2001, 1, 1, 0, 5, 17)
1967 self.assertEqual(d, e)
1968 self.assertEqual(hash(d), hash(e))
1969
1970 dic = {d: 1}
1971 dic[e] = 2
1972 self.assertEqual(len(dic), 1)
1973 self.assertEqual(dic[d], 2)
1974 self.assertEqual(dic[e], 2)
1975
1976 def test_computations(self):
1977 a = self.theclass(2002, 1, 31)
1978 b = self.theclass(1956, 1, 31)
1979 diff = a-b
1980 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1981 self.assertEqual(diff.seconds, 0)
1982 self.assertEqual(diff.microseconds, 0)
1983 a = self.theclass(2002, 3, 2, 17, 6)
1984 millisec = timedelta(0, 0, 1000)
1985 hour = timedelta(0, 3600)
1986 day = timedelta(1)
1987 week = timedelta(7)
1988 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1989 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1990 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1991 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1992 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1993 self.assertEqual(a - hour, a + -hour)
1994 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1995 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1996 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1997 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1998 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1999 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
2000 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
2001 self.assertEqual((a + week) - a, week)
2002 self.assertEqual((a + day) - a, day)
2003 self.assertEqual((a + hour) - a, hour)
2004 self.assertEqual((a + millisec) - a, millisec)
2005 self.assertEqual((a - week) - a, -week)
2006 self.assertEqual((a - day) - a, -day)
2007 self.assertEqual((a - hour) - a, -hour)
2008 self.assertEqual((a - millisec) - a, -millisec)
2009 self.assertEqual(a - (a + week), -week)
2010 self.assertEqual(a - (a + day), -day)
2011 self.assertEqual(a - (a + hour), -hour)
2012 self.assertEqual(a - (a + millisec), -millisec)
2013 self.assertEqual(a - (a - week), week)
2014 self.assertEqual(a - (a - day), day)
2015 self.assertEqual(a - (a - hour), hour)
2016 self.assertEqual(a - (a - millisec), millisec)
2017 self.assertEqual(a + (week + day + hour + millisec),
2018 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
2019 self.assertEqual(a + (week + day + hour + millisec),
2020 (((a + week) + day) + hour) + millisec)
2021 self.assertEqual(a - (week + day + hour + millisec),
2022 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
2023 self.assertEqual(a - (week + day + hour + millisec),
2024 (((a - week) - day) - hour) - millisec)
2025 # Add/sub ints or floats should be illegal
2026 for i in 1, 1.0:
2027 self.assertRaises(TypeError, lambda: a+i)
2028 self.assertRaises(TypeError, lambda: a-i)
2029 self.assertRaises(TypeError, lambda: i+a)
2030 self.assertRaises(TypeError, lambda: i-a)
2031
2032 # delta - datetime is senseless.
2033 self.assertRaises(TypeError, lambda: day - a)
2034 # mixing datetime and (delta or datetime) via * or // is senseless
2035 self.assertRaises(TypeError, lambda: day * a)
2036 self.assertRaises(TypeError, lambda: a * day)
2037 self.assertRaises(TypeError, lambda: day // a)
2038 self.assertRaises(TypeError, lambda: a // day)
2039 self.assertRaises(TypeError, lambda: a * a)
2040 self.assertRaises(TypeError, lambda: a // a)
2041 # datetime + datetime is senseless
2042 self.assertRaises(TypeError, lambda: a + a)
2043
2044 def test_pickling(self):
2045 args = 6, 7, 23, 20, 59, 1, 64**2
2046 orig = self.theclass(*args)
2047 for pickler, unpickler, proto in pickle_choices:
2048 green = pickler.dumps(orig, proto)
2049 derived = unpickler.loads(green)
2050 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02002051 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002052
2053 def test_more_pickling(self):
2054 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
Serhiy Storchakabad12572014-12-15 14:03:42 +02002055 for proto in range(pickle.HIGHEST_PROTOCOL + 1):
2056 s = pickle.dumps(a, proto)
2057 b = pickle.loads(s)
2058 self.assertEqual(b.year, 2003)
2059 self.assertEqual(b.month, 2)
2060 self.assertEqual(b.day, 7)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002061
2062 def test_pickling_subclass_datetime(self):
2063 args = 6, 7, 23, 20, 59, 1, 64**2
2064 orig = SubclassDatetime(*args)
2065 for pickler, unpickler, proto in pickle_choices:
2066 green = pickler.dumps(orig, proto)
2067 derived = unpickler.loads(green)
2068 self.assertEqual(orig, derived)
2069
2070 def test_more_compare(self):
2071 # The test_compare() inherited from TestDate covers the error cases.
2072 # We just want to test lexicographic ordering on the members datetime
2073 # has that date lacks.
2074 args = [2000, 11, 29, 20, 58, 16, 999998]
2075 t1 = self.theclass(*args)
2076 t2 = self.theclass(*args)
2077 self.assertEqual(t1, t2)
2078 self.assertTrue(t1 <= t2)
2079 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002080 self.assertFalse(t1 != t2)
2081 self.assertFalse(t1 < t2)
2082 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002083
2084 for i in range(len(args)):
2085 newargs = args[:]
2086 newargs[i] = args[i] + 1
2087 t2 = self.theclass(*newargs) # this is larger than t1
2088 self.assertTrue(t1 < t2)
2089 self.assertTrue(t2 > t1)
2090 self.assertTrue(t1 <= t2)
2091 self.assertTrue(t2 >= t1)
2092 self.assertTrue(t1 != t2)
2093 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002094 self.assertFalse(t1 == t2)
2095 self.assertFalse(t2 == t1)
2096 self.assertFalse(t1 > t2)
2097 self.assertFalse(t2 < t1)
2098 self.assertFalse(t1 >= t2)
2099 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002100
2101
2102 # A helper for timestamp constructor tests.
2103 def verify_field_equality(self, expected, got):
2104 self.assertEqual(expected.tm_year, got.year)
2105 self.assertEqual(expected.tm_mon, got.month)
2106 self.assertEqual(expected.tm_mday, got.day)
2107 self.assertEqual(expected.tm_hour, got.hour)
2108 self.assertEqual(expected.tm_min, got.minute)
2109 self.assertEqual(expected.tm_sec, got.second)
2110
2111 def test_fromtimestamp(self):
2112 import time
2113
2114 ts = time.time()
2115 expected = time.localtime(ts)
2116 got = self.theclass.fromtimestamp(ts)
2117 self.verify_field_equality(expected, got)
2118
2119 def test_utcfromtimestamp(self):
2120 import time
2121
2122 ts = time.time()
2123 expected = time.gmtime(ts)
2124 got = self.theclass.utcfromtimestamp(ts)
2125 self.verify_field_equality(expected, got)
2126
Alexander Belopolskya4415142012-06-08 12:33:09 -04002127 # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in
2128 # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0).
2129 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
2130 def test_timestamp_naive(self):
2131 t = self.theclass(1970, 1, 1)
2132 self.assertEqual(t.timestamp(), 18000.0)
2133 t = self.theclass(1970, 1, 1, 1, 2, 3, 4)
2134 self.assertEqual(t.timestamp(),
2135 18000.0 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002136 # Missing hour
2137 t0 = self.theclass(2012, 3, 11, 2, 30)
2138 t1 = t0.replace(fold=1)
2139 self.assertEqual(self.theclass.fromtimestamp(t1.timestamp()),
2140 t0 - timedelta(hours=1))
2141 self.assertEqual(self.theclass.fromtimestamp(t0.timestamp()),
2142 t1 + timedelta(hours=1))
Alexander Belopolskya4415142012-06-08 12:33:09 -04002143 # Ambiguous hour defaults to DST
2144 t = self.theclass(2012, 11, 4, 1, 30)
2145 self.assertEqual(self.theclass.fromtimestamp(t.timestamp()), t)
2146
2147 # Timestamp may raise an overflow error on some platforms
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002148 # XXX: Do we care to support the first and last year?
2149 for t in [self.theclass(2,1,1), self.theclass(9998,12,12)]:
Alexander Belopolskya4415142012-06-08 12:33:09 -04002150 try:
2151 s = t.timestamp()
2152 except OverflowError:
2153 pass
2154 else:
2155 self.assertEqual(self.theclass.fromtimestamp(s), t)
2156
2157 def test_timestamp_aware(self):
2158 t = self.theclass(1970, 1, 1, tzinfo=timezone.utc)
2159 self.assertEqual(t.timestamp(), 0.0)
2160 t = self.theclass(1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone.utc)
2161 self.assertEqual(t.timestamp(),
2162 3600 + 2*60 + 3 + 4*1e-6)
2163 t = self.theclass(1970, 1, 1, 1, 2, 3, 4,
2164 tzinfo=timezone(timedelta(hours=-5), 'EST'))
2165 self.assertEqual(t.timestamp(),
2166 18000 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002167
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002168 @support.run_with_tz('MSK-03') # Something east of Greenwich
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002169 def test_microsecond_rounding(self):
Victor Stinner5ebfe422015-09-18 14:52:15 +02002170 for fts in [self.theclass.fromtimestamp,
2171 self.theclass.utcfromtimestamp]:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002172 zero = fts(0)
2173 self.assertEqual(zero.second, 0)
2174 self.assertEqual(zero.microsecond, 0)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002175 one = fts(1e-6)
Victor Stinner8050ca92012-03-14 00:17:05 +01002176 try:
2177 minus_one = fts(-1e-6)
2178 except OSError:
2179 # localtime(-1) and gmtime(-1) is not supported on Windows
2180 pass
2181 else:
2182 self.assertEqual(minus_one.second, 59)
2183 self.assertEqual(minus_one.microsecond, 999999)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002184
Victor Stinner8050ca92012-03-14 00:17:05 +01002185 t = fts(-1e-8)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002186 self.assertEqual(t, zero)
Victor Stinner8050ca92012-03-14 00:17:05 +01002187 t = fts(-9e-7)
2188 self.assertEqual(t, minus_one)
2189 t = fts(-1e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002190 self.assertEqual(t, zero)
Victor Stinner8820a352015-09-05 10:50:20 +02002191 t = fts(-1/2**7)
2192 self.assertEqual(t.second, 59)
Victor Stinner7667f582015-09-09 01:02:23 +02002193 self.assertEqual(t.microsecond, 992188)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002194
2195 t = fts(1e-7)
2196 self.assertEqual(t, zero)
2197 t = fts(9e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002198 self.assertEqual(t, one)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002199 t = fts(0.99999949)
2200 self.assertEqual(t.second, 0)
2201 self.assertEqual(t.microsecond, 999999)
2202 t = fts(0.9999999)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002203 self.assertEqual(t.second, 1)
2204 self.assertEqual(t.microsecond, 0)
Victor Stinneradfefa52015-09-04 23:57:25 +02002205 t = fts(1/2**7)
2206 self.assertEqual(t.second, 0)
Victor Stinner7667f582015-09-09 01:02:23 +02002207 self.assertEqual(t.microsecond, 7812)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002208
Victor Stinnerb67f0962017-02-10 10:34:02 +01002209 def test_timestamp_limits(self):
2210 # minimum timestamp
2211 min_dt = self.theclass.min.replace(tzinfo=timezone.utc)
2212 min_ts = min_dt.timestamp()
Victor Stinner6f37e362017-02-10 11:45:14 +01002213 try:
2214 # date 0001-01-01 00:00:00+00:00: timestamp=-62135596800
2215 self.assertEqual(self.theclass.fromtimestamp(min_ts, tz=timezone.utc),
2216 min_dt)
Victor Stinner2a35c932017-02-10 12:37:21 +01002217 except (OverflowError, OSError) as exc:
2218 # the date 0001-01-01 doesn't fit into 32-bit time_t,
2219 # or platform doesn't support such very old date
Victor Stinner6f37e362017-02-10 11:45:14 +01002220 self.skipTest(str(exc))
Victor Stinnerb67f0962017-02-10 10:34:02 +01002221
2222 # maximum timestamp: set seconds to zero to avoid rounding issues
2223 max_dt = self.theclass.max.replace(tzinfo=timezone.utc,
2224 second=0, microsecond=0)
2225 max_ts = max_dt.timestamp()
2226 # date 9999-12-31 23:59:00+00:00: timestamp 253402300740
2227 self.assertEqual(self.theclass.fromtimestamp(max_ts, tz=timezone.utc),
2228 max_dt)
2229
2230 # number of seconds greater than 1 year: make sure that the new date
2231 # is not valid in datetime.datetime limits
2232 delta = 3600 * 24 * 400
2233
2234 # too small
2235 ts = min_ts - delta
2236 # converting a Python int to C time_t can raise a OverflowError,
2237 # especially on 32-bit platforms.
2238 with self.assertRaises((ValueError, OverflowError)):
2239 self.theclass.fromtimestamp(ts)
2240 with self.assertRaises((ValueError, OverflowError)):
2241 self.theclass.utcfromtimestamp(ts)
2242
2243 # too big
2244 ts = max_dt.timestamp() + delta
2245 with self.assertRaises((ValueError, OverflowError)):
2246 self.theclass.fromtimestamp(ts)
2247 with self.assertRaises((ValueError, OverflowError)):
2248 self.theclass.utcfromtimestamp(ts)
2249
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002250 def test_insane_fromtimestamp(self):
2251 # It's possible that some platform maps time_t to double,
2252 # and that this test will fail there. This test should
2253 # exempt such platforms (provided they return reasonable
2254 # results!).
2255 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002256 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002257 insane)
2258
2259 def test_insane_utcfromtimestamp(self):
2260 # It's possible that some platform maps time_t to double,
2261 # and that this test will fail there. This test should
2262 # exempt such platforms (provided they return reasonable
2263 # results!).
2264 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002265 self.assertRaises(OverflowError, self.theclass.utcfromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002266 insane)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002267
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002268 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2269 def test_negative_float_fromtimestamp(self):
2270 # The result is tz-dependent; at least test that this doesn't
2271 # fail (like it did before bug 1646728 was fixed).
2272 self.theclass.fromtimestamp(-1.05)
2273
2274 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2275 def test_negative_float_utcfromtimestamp(self):
2276 d = self.theclass.utcfromtimestamp(-1.05)
2277 self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
2278
2279 def test_utcnow(self):
2280 import time
2281
2282 # Call it a success if utcnow() and utcfromtimestamp() are within
2283 # a second of each other.
2284 tolerance = timedelta(seconds=1)
2285 for dummy in range(3):
2286 from_now = self.theclass.utcnow()
2287 from_timestamp = self.theclass.utcfromtimestamp(time.time())
2288 if abs(from_timestamp - from_now) <= tolerance:
2289 break
2290 # Else try again a few times.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002291 self.assertLessEqual(abs(from_timestamp - from_now), tolerance)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002292
2293 def test_strptime(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002294 string = '2004-12-01 13:02:47.197'
2295 format = '%Y-%m-%d %H:%M:%S.%f'
2296 expected = _strptime._strptime_datetime(self.theclass, string, format)
2297 got = self.theclass.strptime(string, format)
2298 self.assertEqual(expected, got)
2299 self.assertIs(type(expected), self.theclass)
2300 self.assertIs(type(got), self.theclass)
2301
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03002302 # bpo-34482: Check that surrogates are handled properly.
2303 inputs = [
2304 ('2004-12-01\ud80013:02:47.197', '%Y-%m-%d\ud800%H:%M:%S.%f'),
2305 ('2004\ud80012-01 13:02:47.197', '%Y\ud800%m-%d %H:%M:%S.%f'),
2306 ('2004-12-01 13:02\ud80047.197', '%Y-%m-%d %H:%M\ud800%S.%f'),
2307 ]
2308 for string, format in inputs:
2309 with self.subTest(string=string, format=format):
2310 expected = _strptime._strptime_datetime(self.theclass, string,
2311 format)
2312 got = self.theclass.strptime(string, format)
2313 self.assertEqual(expected, got)
2314
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002315 strptime = self.theclass.strptime
2316 self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
2317 self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
Mario Corchero32318932017-10-26 01:35:41 +01002318 self.assertEqual(
2319 strptime("-00:02:01.000003", "%z").utcoffset(),
2320 -timedelta(minutes=2, seconds=1, microseconds=3)
2321 )
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002322 # Only local timezone and UTC are supported
2323 for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
2324 (-_time.timezone, _time.tzname[0])):
2325 if tzseconds < 0:
2326 sign = '-'
2327 seconds = -tzseconds
2328 else:
2329 sign ='+'
2330 seconds = tzseconds
2331 hours, minutes = divmod(seconds//60, 60)
2332 dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
Martin Panterfca22322015-11-16 09:22:19 +00002333 dt = strptime(dtstr, "%z %Z")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002334 self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
2335 self.assertEqual(dt.tzname(), tzname)
2336 # Can produce inconsistent datetime
2337 dtstr, fmt = "+1234 UTC", "%z %Z"
2338 dt = strptime(dtstr, fmt)
2339 self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE)
2340 self.assertEqual(dt.tzname(), 'UTC')
2341 # yet will roundtrip
2342 self.assertEqual(dt.strftime(fmt), dtstr)
2343
2344 # Produce naive datetime if no %z is provided
2345 self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
2346
2347 with self.assertRaises(ValueError): strptime("-2400", "%z")
2348 with self.assertRaises(ValueError): strptime("-000", "%z")
2349
2350 def test_more_timetuple(self):
2351 # This tests fields beyond those tested by the TestDate.test_timetuple.
2352 t = self.theclass(2004, 12, 31, 6, 22, 33)
2353 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
2354 self.assertEqual(t.timetuple(),
2355 (t.year, t.month, t.day,
2356 t.hour, t.minute, t.second,
2357 t.weekday(),
2358 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
2359 -1))
2360 tt = t.timetuple()
2361 self.assertEqual(tt.tm_year, t.year)
2362 self.assertEqual(tt.tm_mon, t.month)
2363 self.assertEqual(tt.tm_mday, t.day)
2364 self.assertEqual(tt.tm_hour, t.hour)
2365 self.assertEqual(tt.tm_min, t.minute)
2366 self.assertEqual(tt.tm_sec, t.second)
2367 self.assertEqual(tt.tm_wday, t.weekday())
2368 self.assertEqual(tt.tm_yday, t.toordinal() -
2369 date(t.year, 1, 1).toordinal() + 1)
2370 self.assertEqual(tt.tm_isdst, -1)
2371
2372 def test_more_strftime(self):
2373 # This tests fields beyond those tested by the TestDate.test_strftime.
2374 t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
2375 self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
2376 "12 31 04 000047 33 22 06 366")
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04002377 for (s, us), z in [((33, 123), "33.000123"), ((33, 0), "33"),]:
2378 tz = timezone(-timedelta(hours=2, seconds=s, microseconds=us))
2379 t = t.replace(tzinfo=tz)
2380 self.assertEqual(t.strftime("%z"), "-0200" + z)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002381
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03002382 # bpo-34482: Check that surrogates don't cause a crash.
2383 try:
2384 t.strftime('%y\ud800%m %H\ud800%M')
2385 except UnicodeEncodeError:
2386 pass
2387
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002388 def test_extract(self):
2389 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2390 self.assertEqual(dt.date(), date(2002, 3, 4))
2391 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2392
2393 def test_combine(self):
2394 d = date(2002, 3, 4)
2395 t = time(18, 45, 3, 1234)
2396 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2397 combine = self.theclass.combine
2398 dt = combine(d, t)
2399 self.assertEqual(dt, expected)
2400
2401 dt = combine(time=t, date=d)
2402 self.assertEqual(dt, expected)
2403
2404 self.assertEqual(d, dt.date())
2405 self.assertEqual(t, dt.time())
2406 self.assertEqual(dt, combine(dt.date(), dt.time()))
2407
2408 self.assertRaises(TypeError, combine) # need an arg
2409 self.assertRaises(TypeError, combine, d) # need two args
2410 self.assertRaises(TypeError, combine, t, d) # args reversed
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002411 self.assertRaises(TypeError, combine, d, t, 1) # wrong tzinfo type
2412 self.assertRaises(TypeError, combine, d, t, 1, 2) # too many args
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002413 self.assertRaises(TypeError, combine, "date", "time") # wrong types
2414 self.assertRaises(TypeError, combine, d, "time") # wrong type
2415 self.assertRaises(TypeError, combine, "date", t) # wrong type
2416
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002417 # tzinfo= argument
2418 dt = combine(d, t, timezone.utc)
2419 self.assertIs(dt.tzinfo, timezone.utc)
2420 dt = combine(d, t, tzinfo=timezone.utc)
2421 self.assertIs(dt.tzinfo, timezone.utc)
2422 t = time()
2423 dt = combine(dt, t)
2424 self.assertEqual(dt.date(), d)
2425 self.assertEqual(dt.time(), t)
2426
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002427 def test_replace(self):
2428 cls = self.theclass
2429 args = [1, 2, 3, 4, 5, 6, 7]
2430 base = cls(*args)
2431 self.assertEqual(base, base.replace())
2432
2433 i = 0
2434 for name, newval in (("year", 2),
2435 ("month", 3),
2436 ("day", 4),
2437 ("hour", 5),
2438 ("minute", 6),
2439 ("second", 7),
2440 ("microsecond", 8)):
2441 newargs = args[:]
2442 newargs[i] = newval
2443 expected = cls(*newargs)
2444 got = base.replace(**{name: newval})
2445 self.assertEqual(expected, got)
2446 i += 1
2447
2448 # Out of bounds.
2449 base = cls(2000, 2, 29)
2450 self.assertRaises(ValueError, base.replace, year=2001)
2451
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002452 @support.run_with_tz('EDT4')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002453 def test_astimezone(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002454 dt = self.theclass.now()
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002455 f = FixedOffset(44, "0044")
2456 dt_utc = dt.replace(tzinfo=timezone(timedelta(hours=-4), 'EDT'))
2457 self.assertEqual(dt.astimezone(), dt_utc) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002458 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
2459 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002460 dt_f = dt.replace(tzinfo=f) + timedelta(hours=4, minutes=44)
2461 self.assertEqual(dt.astimezone(f), dt_f) # naive
2462 self.assertEqual(dt.astimezone(tz=f), dt_f) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002463
2464 class Bogus(tzinfo):
2465 def utcoffset(self, dt): return None
2466 def dst(self, dt): return timedelta(0)
2467 bog = Bogus()
2468 self.assertRaises(ValueError, dt.astimezone, bog) # naive
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002469 self.assertEqual(dt.replace(tzinfo=bog).astimezone(f), dt_f)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002470
2471 class AlsoBogus(tzinfo):
2472 def utcoffset(self, dt): return timedelta(0)
2473 def dst(self, dt): return None
2474 alsobog = AlsoBogus()
2475 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
2476
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002477 class Broken(tzinfo):
2478 def utcoffset(self, dt): return 1
2479 def dst(self, dt): return 1
2480 broken = Broken()
2481 dt_broken = dt.replace(tzinfo=broken)
2482 with self.assertRaises(TypeError):
2483 dt_broken.astimezone()
2484
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002485 def test_subclass_datetime(self):
2486
2487 class C(self.theclass):
2488 theAnswer = 42
2489
2490 def __new__(cls, *args, **kws):
2491 temp = kws.copy()
2492 extra = temp.pop('extra')
2493 result = self.theclass.__new__(cls, *args, **temp)
2494 result.extra = extra
2495 return result
2496
2497 def newmeth(self, start):
2498 return start + self.year + self.month + self.second
2499
2500 args = 2003, 4, 14, 12, 13, 41
2501
2502 dt1 = self.theclass(*args)
2503 dt2 = C(*args, **{'extra': 7})
2504
2505 self.assertEqual(dt2.__class__, C)
2506 self.assertEqual(dt2.theAnswer, 42)
2507 self.assertEqual(dt2.extra, 7)
2508 self.assertEqual(dt1.toordinal(), dt2.toordinal())
2509 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
2510 dt1.second - 7)
2511
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002512 def test_subclass_alternate_constructors_datetime(self):
2513 # Test that alternate constructors call the constructor
2514 class DateTimeSubclass(self.theclass):
2515 def __new__(cls, *args, **kwargs):
2516 result = self.theclass.__new__(cls, *args, **kwargs)
2517 result.extra = 7
2518
2519 return result
2520
2521 args = (2003, 4, 14, 12, 30, 15, 123456)
2522 d_isoformat = '2003-04-14T12:30:15.123456' # Equivalent isoformat()
2523 utc_ts = 1050323415.123456 # UTC timestamp
2524
2525 base_d = DateTimeSubclass(*args)
2526 self.assertIsInstance(base_d, DateTimeSubclass)
2527 self.assertEqual(base_d.extra, 7)
2528
2529 # Timestamp depends on time zone, so we'll calculate the equivalent here
2530 ts = base_d.timestamp()
2531
2532 test_cases = [
2533 ('fromtimestamp', (ts,)),
2534 # See https://bugs.python.org/issue32417
2535 # ('fromtimestamp', (ts, timezone.utc)),
2536 ('utcfromtimestamp', (utc_ts,)),
2537 ('fromisoformat', (d_isoformat,)),
2538 ('strptime', (d_isoformat, '%Y-%m-%dT%H:%M:%S.%f')),
2539 ('combine', (date(*args[0:3]), time(*args[3:]))),
2540 ]
2541
2542 for constr_name, constr_args in test_cases:
2543 for base_obj in (DateTimeSubclass, base_d):
2544 # Test both the classmethod and method
2545 with self.subTest(base_obj_type=type(base_obj),
2546 constr_name=constr_name):
2547 constr = getattr(base_obj, constr_name)
2548
2549 dt = constr(*constr_args)
2550
2551 # Test that it creates the right subclass
2552 self.assertIsInstance(dt, DateTimeSubclass)
2553
2554 # Test that it's equal to the base object
2555 self.assertEqual(dt, base_d.replace(tzinfo=None))
2556
2557 # Test that it called the constructor
2558 self.assertEqual(dt.extra, 7)
2559
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002560 def test_fromisoformat_datetime(self):
2561 # Test that isoformat() is reversible
2562 base_dates = [
2563 (1, 1, 1),
2564 (1900, 1, 1),
2565 (2004, 11, 12),
2566 (2017, 5, 30)
2567 ]
2568
2569 base_times = [
2570 (0, 0, 0, 0),
2571 (0, 0, 0, 241000),
2572 (0, 0, 0, 234567),
2573 (12, 30, 45, 234567)
2574 ]
2575
2576 separators = [' ', 'T']
2577
2578 tzinfos = [None, timezone.utc,
2579 timezone(timedelta(hours=-5)),
2580 timezone(timedelta(hours=2))]
2581
2582 dts = [self.theclass(*date_tuple, *time_tuple, tzinfo=tzi)
2583 for date_tuple in base_dates
2584 for time_tuple in base_times
2585 for tzi in tzinfos]
2586
2587 for dt in dts:
2588 for sep in separators:
2589 dtstr = dt.isoformat(sep=sep)
2590
2591 with self.subTest(dtstr=dtstr):
2592 dt_rt = self.theclass.fromisoformat(dtstr)
2593 self.assertEqual(dt, dt_rt)
2594
2595 def test_fromisoformat_timezone(self):
2596 base_dt = self.theclass(2014, 12, 30, 12, 30, 45, 217456)
2597
2598 tzoffsets = [
2599 timedelta(hours=5), timedelta(hours=2),
2600 timedelta(hours=6, minutes=27),
2601 timedelta(hours=12, minutes=32, seconds=30),
2602 timedelta(hours=2, minutes=4, seconds=9, microseconds=123456)
2603 ]
2604
2605 tzoffsets += [-1 * td for td in tzoffsets]
2606
2607 tzinfos = [None, timezone.utc,
2608 timezone(timedelta(hours=0))]
2609
2610 tzinfos += [timezone(td) for td in tzoffsets]
2611
2612 for tzi in tzinfos:
2613 dt = base_dt.replace(tzinfo=tzi)
2614 dtstr = dt.isoformat()
2615
2616 with self.subTest(tstr=dtstr):
2617 dt_rt = self.theclass.fromisoformat(dtstr)
2618 assert dt == dt_rt, dt_rt
2619
2620 def test_fromisoformat_separators(self):
2621 separators = [
2622 ' ', 'T', '\u007f', # 1-bit widths
2623 '\u0080', 'ʁ', # 2-bit widths
2624 'ᛇ', '時', # 3-bit widths
Paul Ganssle096329f2018-08-23 11:06:20 -04002625 '🐍', # 4-bit widths
2626 '\ud800', # bpo-34454: Surrogate code point
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002627 ]
2628
2629 for sep in separators:
2630 dt = self.theclass(2018, 1, 31, 23, 59, 47, 124789)
2631 dtstr = dt.isoformat(sep=sep)
2632
2633 with self.subTest(dtstr=dtstr):
2634 dt_rt = self.theclass.fromisoformat(dtstr)
2635 self.assertEqual(dt, dt_rt)
2636
2637 def test_fromisoformat_ambiguous(self):
2638 # Test strings like 2018-01-31+12:15 (where +12:15 is not a time zone)
2639 separators = ['+', '-']
2640 for sep in separators:
2641 dt = self.theclass(2018, 1, 31, 12, 15)
2642 dtstr = dt.isoformat(sep=sep)
2643
2644 with self.subTest(dtstr=dtstr):
2645 dt_rt = self.theclass.fromisoformat(dtstr)
2646 self.assertEqual(dt, dt_rt)
2647
2648 def test_fromisoformat_timespecs(self):
2649 datetime_bases = [
2650 (2009, 12, 4, 8, 17, 45, 123456),
2651 (2009, 12, 4, 8, 17, 45, 0)]
2652
2653 tzinfos = [None, timezone.utc,
2654 timezone(timedelta(hours=-5)),
2655 timezone(timedelta(hours=2)),
2656 timezone(timedelta(hours=6, minutes=27))]
2657
2658 timespecs = ['hours', 'minutes', 'seconds',
2659 'milliseconds', 'microseconds']
2660
2661 for ip, ts in enumerate(timespecs):
2662 for tzi in tzinfos:
2663 for dt_tuple in datetime_bases:
2664 if ts == 'milliseconds':
2665 new_microseconds = 1000 * (dt_tuple[6] // 1000)
2666 dt_tuple = dt_tuple[0:6] + (new_microseconds,)
2667
2668 dt = self.theclass(*(dt_tuple[0:(4 + ip)]), tzinfo=tzi)
2669 dtstr = dt.isoformat(timespec=ts)
2670 with self.subTest(dtstr=dtstr):
2671 dt_rt = self.theclass.fromisoformat(dtstr)
2672 self.assertEqual(dt, dt_rt)
2673
2674 def test_fromisoformat_fails_datetime(self):
2675 # Test that fromisoformat() fails on invalid values
2676 bad_strs = [
2677 '', # Empty string
Paul Ganssle096329f2018-08-23 11:06:20 -04002678 '\ud800', # bpo-34454: Surrogate code point
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002679 '2009.04-19T03', # Wrong first separator
2680 '2009-04.19T03', # Wrong second separator
2681 '2009-04-19T0a', # Invalid hours
2682 '2009-04-19T03:1a:45', # Invalid minutes
2683 '2009-04-19T03:15:4a', # Invalid seconds
2684 '2009-04-19T03;15:45', # Bad first time separator
2685 '2009-04-19T03:15;45', # Bad second time separator
2686 '2009-04-19T03:15:4500:00', # Bad time zone separator
2687 '2009-04-19T03:15:45.2345', # Too many digits for milliseconds
2688 '2009-04-19T03:15:45.1234567', # Too many digits for microseconds
2689 '2009-04-19T03:15:45.123456+24:30', # Invalid time zone offset
2690 '2009-04-19T03:15:45.123456-24:30', # Invalid negative offset
2691 '2009-04-10ᛇᛇᛇᛇᛇ12:15', # Too many unicode separators
Paul Ganssle096329f2018-08-23 11:06:20 -04002692 '2009-04\ud80010T12:15', # Surrogate char in date
2693 '2009-04-10T12\ud80015', # Surrogate char in time
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002694 '2009-04-19T1', # Incomplete hours
2695 '2009-04-19T12:3', # Incomplete minutes
2696 '2009-04-19T12:30:4', # Incomplete seconds
2697 '2009-04-19T12:', # Ends with time separator
2698 '2009-04-19T12:30:', # Ends with time separator
2699 '2009-04-19T12:30:45.', # Ends with time separator
2700 '2009-04-19T12:30:45.123456+', # Ends with timzone separator
2701 '2009-04-19T12:30:45.123456-', # Ends with timzone separator
2702 '2009-04-19T12:30:45.123456-05:00a', # Extra text
2703 '2009-04-19T12:30:45.123-05:00a', # Extra text
2704 '2009-04-19T12:30:45-05:00a', # Extra text
2705 ]
2706
2707 for bad_str in bad_strs:
2708 with self.subTest(bad_str=bad_str):
2709 with self.assertRaises(ValueError):
2710 self.theclass.fromisoformat(bad_str)
2711
Paul Ganssle3df85402018-10-22 12:32:52 -04002712 def test_fromisoformat_fails_surrogate(self):
2713 # Test that when fromisoformat() fails with a surrogate character as
2714 # the separator, the error message contains the original string
2715 dtstr = "2018-01-03\ud80001:0113"
2716
2717 with self.assertRaisesRegex(ValueError, re.escape(repr(dtstr))):
2718 self.theclass.fromisoformat(dtstr)
2719
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002720 def test_fromisoformat_utc(self):
2721 dt_str = '2014-04-19T13:21:13+00:00'
2722 dt = self.theclass.fromisoformat(dt_str)
2723
2724 self.assertIs(dt.tzinfo, timezone.utc)
2725
2726 def test_fromisoformat_subclass(self):
2727 class DateTimeSubclass(self.theclass):
2728 pass
2729
2730 dt = DateTimeSubclass(2014, 12, 14, 9, 30, 45, 457390,
2731 tzinfo=timezone(timedelta(hours=10, minutes=45)))
2732
2733 dt_rt = DateTimeSubclass.fromisoformat(dt.isoformat())
2734
2735 self.assertEqual(dt, dt_rt)
2736 self.assertIsInstance(dt_rt, DateTimeSubclass)
2737
2738
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002739class TestSubclassDateTime(TestDateTime):
2740 theclass = SubclassDatetime
2741 # Override tests not designed for subclass
Zachary Ware9fe6d862013-12-08 00:20:35 -06002742 @unittest.skip('not appropriate for subclasses')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002743 def test_roundtrip(self):
2744 pass
2745
2746class SubclassTime(time):
2747 sub_var = 1
2748
2749class TestTime(HarmlessMixedComparison, unittest.TestCase):
2750
2751 theclass = time
2752
2753 def test_basic_attributes(self):
2754 t = self.theclass(12, 0)
2755 self.assertEqual(t.hour, 12)
2756 self.assertEqual(t.minute, 0)
2757 self.assertEqual(t.second, 0)
2758 self.assertEqual(t.microsecond, 0)
2759
2760 def test_basic_attributes_nonzero(self):
2761 # Make sure all attributes are non-zero so bugs in
2762 # bit-shifting access show up.
2763 t = self.theclass(12, 59, 59, 8000)
2764 self.assertEqual(t.hour, 12)
2765 self.assertEqual(t.minute, 59)
2766 self.assertEqual(t.second, 59)
2767 self.assertEqual(t.microsecond, 8000)
2768
2769 def test_roundtrip(self):
2770 t = self.theclass(1, 2, 3, 4)
2771
2772 # Verify t -> string -> time identity.
2773 s = repr(t)
2774 self.assertTrue(s.startswith('datetime.'))
2775 s = s[9:]
2776 t2 = eval(s)
2777 self.assertEqual(t, t2)
2778
2779 # Verify identity via reconstructing from pieces.
2780 t2 = self.theclass(t.hour, t.minute, t.second,
2781 t.microsecond)
2782 self.assertEqual(t, t2)
2783
2784 def test_comparing(self):
2785 args = [1, 2, 3, 4]
2786 t1 = self.theclass(*args)
2787 t2 = self.theclass(*args)
2788 self.assertEqual(t1, t2)
2789 self.assertTrue(t1 <= t2)
2790 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002791 self.assertFalse(t1 != t2)
2792 self.assertFalse(t1 < t2)
2793 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002794
2795 for i in range(len(args)):
2796 newargs = args[:]
2797 newargs[i] = args[i] + 1
2798 t2 = self.theclass(*newargs) # this is larger than t1
2799 self.assertTrue(t1 < t2)
2800 self.assertTrue(t2 > t1)
2801 self.assertTrue(t1 <= t2)
2802 self.assertTrue(t2 >= t1)
2803 self.assertTrue(t1 != t2)
2804 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002805 self.assertFalse(t1 == t2)
2806 self.assertFalse(t2 == t1)
2807 self.assertFalse(t1 > t2)
2808 self.assertFalse(t2 < t1)
2809 self.assertFalse(t1 >= t2)
2810 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002811
2812 for badarg in OTHERSTUFF:
2813 self.assertEqual(t1 == badarg, False)
2814 self.assertEqual(t1 != badarg, True)
2815 self.assertEqual(badarg == t1, False)
2816 self.assertEqual(badarg != t1, True)
2817
2818 self.assertRaises(TypeError, lambda: t1 <= badarg)
2819 self.assertRaises(TypeError, lambda: t1 < badarg)
2820 self.assertRaises(TypeError, lambda: t1 > badarg)
2821 self.assertRaises(TypeError, lambda: t1 >= badarg)
2822 self.assertRaises(TypeError, lambda: badarg <= t1)
2823 self.assertRaises(TypeError, lambda: badarg < t1)
2824 self.assertRaises(TypeError, lambda: badarg > t1)
2825 self.assertRaises(TypeError, lambda: badarg >= t1)
2826
2827 def test_bad_constructor_arguments(self):
2828 # bad hours
2829 self.theclass(0, 0) # no exception
2830 self.theclass(23, 0) # no exception
2831 self.assertRaises(ValueError, self.theclass, -1, 0)
2832 self.assertRaises(ValueError, self.theclass, 24, 0)
2833 # bad minutes
2834 self.theclass(23, 0) # no exception
2835 self.theclass(23, 59) # no exception
2836 self.assertRaises(ValueError, self.theclass, 23, -1)
2837 self.assertRaises(ValueError, self.theclass, 23, 60)
2838 # bad seconds
2839 self.theclass(23, 59, 0) # no exception
2840 self.theclass(23, 59, 59) # no exception
2841 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
2842 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
2843 # bad microseconds
2844 self.theclass(23, 59, 59, 0) # no exception
2845 self.theclass(23, 59, 59, 999999) # no exception
2846 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
2847 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
2848
2849 def test_hash_equality(self):
2850 d = self.theclass(23, 30, 17)
2851 e = self.theclass(23, 30, 17)
2852 self.assertEqual(d, e)
2853 self.assertEqual(hash(d), hash(e))
2854
2855 dic = {d: 1}
2856 dic[e] = 2
2857 self.assertEqual(len(dic), 1)
2858 self.assertEqual(dic[d], 2)
2859 self.assertEqual(dic[e], 2)
2860
2861 d = self.theclass(0, 5, 17)
2862 e = self.theclass(0, 5, 17)
2863 self.assertEqual(d, e)
2864 self.assertEqual(hash(d), hash(e))
2865
2866 dic = {d: 1}
2867 dic[e] = 2
2868 self.assertEqual(len(dic), 1)
2869 self.assertEqual(dic[d], 2)
2870 self.assertEqual(dic[e], 2)
2871
2872 def test_isoformat(self):
2873 t = self.theclass(4, 5, 1, 123)
2874 self.assertEqual(t.isoformat(), "04:05:01.000123")
2875 self.assertEqual(t.isoformat(), str(t))
2876
2877 t = self.theclass()
2878 self.assertEqual(t.isoformat(), "00:00:00")
2879 self.assertEqual(t.isoformat(), str(t))
2880
2881 t = self.theclass(microsecond=1)
2882 self.assertEqual(t.isoformat(), "00:00:00.000001")
2883 self.assertEqual(t.isoformat(), str(t))
2884
2885 t = self.theclass(microsecond=10)
2886 self.assertEqual(t.isoformat(), "00:00:00.000010")
2887 self.assertEqual(t.isoformat(), str(t))
2888
2889 t = self.theclass(microsecond=100)
2890 self.assertEqual(t.isoformat(), "00:00:00.000100")
2891 self.assertEqual(t.isoformat(), str(t))
2892
2893 t = self.theclass(microsecond=1000)
2894 self.assertEqual(t.isoformat(), "00:00:00.001000")
2895 self.assertEqual(t.isoformat(), str(t))
2896
2897 t = self.theclass(microsecond=10000)
2898 self.assertEqual(t.isoformat(), "00:00:00.010000")
2899 self.assertEqual(t.isoformat(), str(t))
2900
2901 t = self.theclass(microsecond=100000)
2902 self.assertEqual(t.isoformat(), "00:00:00.100000")
2903 self.assertEqual(t.isoformat(), str(t))
2904
Alexander Belopolskya2998a62016-03-06 14:58:43 -05002905 t = self.theclass(hour=12, minute=34, second=56, microsecond=123456)
2906 self.assertEqual(t.isoformat(timespec='hours'), "12")
2907 self.assertEqual(t.isoformat(timespec='minutes'), "12:34")
2908 self.assertEqual(t.isoformat(timespec='seconds'), "12:34:56")
2909 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.123")
2910 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.123456")
2911 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56.123456")
2912 self.assertRaises(ValueError, t.isoformat, timespec='monkey')
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03002913 # bpo-34482: Check that surrogates are handled properly.
2914 self.assertRaises(ValueError, t.isoformat, timespec='\ud800')
Alexander Belopolskya2998a62016-03-06 14:58:43 -05002915
2916 t = self.theclass(hour=12, minute=34, second=56, microsecond=999500)
2917 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.999")
2918
2919 t = self.theclass(hour=12, minute=34, second=56, microsecond=0)
2920 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.000")
2921 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.000000")
2922 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56")
2923
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002924 def test_isoformat_timezone(self):
2925 tzoffsets = [
2926 ('05:00', timedelta(hours=5)),
2927 ('02:00', timedelta(hours=2)),
2928 ('06:27', timedelta(hours=6, minutes=27)),
2929 ('12:32:30', timedelta(hours=12, minutes=32, seconds=30)),
2930 ('02:04:09.123456', timedelta(hours=2, minutes=4, seconds=9, microseconds=123456))
2931 ]
2932
2933 tzinfos = [
2934 ('', None),
2935 ('+00:00', timezone.utc),
2936 ('+00:00', timezone(timedelta(0))),
2937 ]
2938
2939 tzinfos += [
2940 (prefix + expected, timezone(sign * td))
2941 for expected, td in tzoffsets
2942 for prefix, sign in [('-', -1), ('+', 1)]
2943 ]
2944
2945 t_base = self.theclass(12, 37, 9)
2946 exp_base = '12:37:09'
2947
2948 for exp_tz, tzi in tzinfos:
2949 t = t_base.replace(tzinfo=tzi)
2950 exp = exp_base + exp_tz
2951 with self.subTest(tzi=tzi):
2952 assert t.isoformat() == exp
2953
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002954 def test_1653736(self):
2955 # verify it doesn't accept extra keyword arguments
2956 t = self.theclass(second=1)
2957 self.assertRaises(TypeError, t.isoformat, foo=3)
2958
2959 def test_strftime(self):
2960 t = self.theclass(1, 2, 3, 4)
2961 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
2962 # A naive object replaces %z and %Z with empty strings.
2963 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
2964
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03002965 # bpo-34482: Check that surrogates don't cause a crash.
2966 try:
2967 t.strftime('%H\ud800%M')
2968 except UnicodeEncodeError:
2969 pass
2970
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002971 def test_format(self):
2972 t = self.theclass(1, 2, 3, 4)
2973 self.assertEqual(t.__format__(''), str(t))
2974
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02002975 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002976 t.__format__(123)
2977
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002978 # check that a derived class's __str__() gets called
2979 class A(self.theclass):
2980 def __str__(self):
2981 return 'A'
2982 a = A(1, 2, 3, 4)
2983 self.assertEqual(a.__format__(''), 'A')
2984
2985 # check that a derived class's strftime gets called
2986 class B(self.theclass):
2987 def strftime(self, format_spec):
2988 return 'B'
2989 b = B(1, 2, 3, 4)
2990 self.assertEqual(b.__format__(''), str(t))
2991
2992 for fmt in ['%H %M %S',
2993 ]:
2994 self.assertEqual(t.__format__(fmt), t.strftime(fmt))
2995 self.assertEqual(a.__format__(fmt), t.strftime(fmt))
2996 self.assertEqual(b.__format__(fmt), 'B')
2997
2998 def test_str(self):
2999 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
3000 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
3001 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
3002 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
3003 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
3004
3005 def test_repr(self):
3006 name = 'datetime.' + self.theclass.__name__
3007 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
3008 "%s(1, 2, 3, 4)" % name)
3009 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
3010 "%s(10, 2, 3, 4000)" % name)
3011 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
3012 "%s(0, 2, 3, 400000)" % name)
3013 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
3014 "%s(12, 2, 3)" % name)
3015 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
3016 "%s(23, 15)" % name)
3017
3018 def test_resolution_info(self):
3019 self.assertIsInstance(self.theclass.min, self.theclass)
3020 self.assertIsInstance(self.theclass.max, self.theclass)
3021 self.assertIsInstance(self.theclass.resolution, timedelta)
3022 self.assertTrue(self.theclass.max > self.theclass.min)
3023
3024 def test_pickling(self):
3025 args = 20, 59, 16, 64**2
3026 orig = self.theclass(*args)
3027 for pickler, unpickler, proto in pickle_choices:
3028 green = pickler.dumps(orig, proto)
3029 derived = unpickler.loads(green)
3030 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003031 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003032
3033 def test_pickling_subclass_time(self):
3034 args = 20, 59, 16, 64**2
3035 orig = SubclassTime(*args)
3036 for pickler, unpickler, proto in pickle_choices:
3037 green = pickler.dumps(orig, proto)
3038 derived = unpickler.loads(green)
3039 self.assertEqual(orig, derived)
3040
3041 def test_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003042 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003043 cls = self.theclass
3044 self.assertTrue(cls(1))
3045 self.assertTrue(cls(0, 1))
3046 self.assertTrue(cls(0, 0, 1))
3047 self.assertTrue(cls(0, 0, 0, 1))
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003048 self.assertTrue(cls(0))
3049 self.assertTrue(cls())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003050
3051 def test_replace(self):
3052 cls = self.theclass
3053 args = [1, 2, 3, 4]
3054 base = cls(*args)
3055 self.assertEqual(base, base.replace())
3056
3057 i = 0
3058 for name, newval in (("hour", 5),
3059 ("minute", 6),
3060 ("second", 7),
3061 ("microsecond", 8)):
3062 newargs = args[:]
3063 newargs[i] = newval
3064 expected = cls(*newargs)
3065 got = base.replace(**{name: newval})
3066 self.assertEqual(expected, got)
3067 i += 1
3068
3069 # Out of bounds.
3070 base = cls(1)
3071 self.assertRaises(ValueError, base.replace, hour=24)
3072 self.assertRaises(ValueError, base.replace, minute=-1)
3073 self.assertRaises(ValueError, base.replace, second=100)
3074 self.assertRaises(ValueError, base.replace, microsecond=1000000)
3075
Paul Ganssle191e9932017-11-09 16:34:29 -05003076 def test_subclass_replace(self):
3077 class TimeSubclass(self.theclass):
3078 pass
3079
3080 ctime = TimeSubclass(12, 30)
3081 self.assertIs(type(ctime.replace(hour=10)), TimeSubclass)
3082
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003083 def test_subclass_time(self):
3084
3085 class C(self.theclass):
3086 theAnswer = 42
3087
3088 def __new__(cls, *args, **kws):
3089 temp = kws.copy()
3090 extra = temp.pop('extra')
3091 result = self.theclass.__new__(cls, *args, **temp)
3092 result.extra = extra
3093 return result
3094
3095 def newmeth(self, start):
3096 return start + self.hour + self.second
3097
3098 args = 4, 5, 6
3099
3100 dt1 = self.theclass(*args)
3101 dt2 = C(*args, **{'extra': 7})
3102
3103 self.assertEqual(dt2.__class__, C)
3104 self.assertEqual(dt2.theAnswer, 42)
3105 self.assertEqual(dt2.extra, 7)
3106 self.assertEqual(dt1.isoformat(), dt2.isoformat())
3107 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
3108
3109 def test_backdoor_resistance(self):
3110 # see TestDate.test_backdoor_resistance().
3111 base = '2:59.0'
3112 for hour_byte in ' ', '9', chr(24), '\xff':
3113 self.assertRaises(TypeError, self.theclass,
3114 hour_byte + base[1:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04003115 # Good bytes, but bad tzinfo:
3116 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
3117 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003118
3119# A mixin for classes with a tzinfo= argument. Subclasses must define
Martin Panter46f50722016-05-26 05:35:26 +00003120# theclass as a class attribute, and theclass(1, 1, 1, tzinfo=whatever)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003121# must be legit (which is true for time and datetime).
3122class TZInfoBase:
3123
3124 def test_argument_passing(self):
3125 cls = self.theclass
3126 # A datetime passes itself on, a time passes None.
3127 class introspective(tzinfo):
3128 def tzname(self, dt): return dt and "real" or "none"
3129 def utcoffset(self, dt):
3130 return timedelta(minutes = dt and 42 or -42)
3131 dst = utcoffset
3132
3133 obj = cls(1, 2, 3, tzinfo=introspective())
3134
3135 expected = cls is time and "none" or "real"
3136 self.assertEqual(obj.tzname(), expected)
3137
3138 expected = timedelta(minutes=(cls is time and -42 or 42))
3139 self.assertEqual(obj.utcoffset(), expected)
3140 self.assertEqual(obj.dst(), expected)
3141
3142 def test_bad_tzinfo_classes(self):
3143 cls = self.theclass
3144 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
3145
3146 class NiceTry(object):
3147 def __init__(self): pass
3148 def utcoffset(self, dt): pass
3149 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
3150
3151 class BetterTry(tzinfo):
3152 def __init__(self): pass
3153 def utcoffset(self, dt): pass
3154 b = BetterTry()
3155 t = cls(1, 1, 1, tzinfo=b)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003156 self.assertIs(t.tzinfo, b)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003157
3158 def test_utc_offset_out_of_bounds(self):
3159 class Edgy(tzinfo):
3160 def __init__(self, offset):
3161 self.offset = timedelta(minutes=offset)
3162 def utcoffset(self, dt):
3163 return self.offset
3164
3165 cls = self.theclass
3166 for offset, legit in ((-1440, False),
3167 (-1439, True),
3168 (1439, True),
3169 (1440, False)):
3170 if cls is time:
3171 t = cls(1, 2, 3, tzinfo=Edgy(offset))
3172 elif cls is datetime:
3173 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
3174 else:
3175 assert 0, "impossible"
3176 if legit:
3177 aofs = abs(offset)
3178 h, m = divmod(aofs, 60)
3179 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
3180 if isinstance(t, datetime):
3181 t = t.timetz()
3182 self.assertEqual(str(t), "01:02:03" + tag)
3183 else:
3184 self.assertRaises(ValueError, str, t)
3185
3186 def test_tzinfo_classes(self):
3187 cls = self.theclass
3188 class C1(tzinfo):
3189 def utcoffset(self, dt): return None
3190 def dst(self, dt): return None
3191 def tzname(self, dt): return None
3192 for t in (cls(1, 1, 1),
3193 cls(1, 1, 1, tzinfo=None),
3194 cls(1, 1, 1, tzinfo=C1())):
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003195 self.assertIsNone(t.utcoffset())
3196 self.assertIsNone(t.dst())
3197 self.assertIsNone(t.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003198
3199 class C3(tzinfo):
3200 def utcoffset(self, dt): return timedelta(minutes=-1439)
3201 def dst(self, dt): return timedelta(minutes=1439)
3202 def tzname(self, dt): return "aname"
3203 t = cls(1, 1, 1, tzinfo=C3())
3204 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
3205 self.assertEqual(t.dst(), timedelta(minutes=1439))
3206 self.assertEqual(t.tzname(), "aname")
3207
3208 # Wrong types.
3209 class C4(tzinfo):
3210 def utcoffset(self, dt): return "aname"
3211 def dst(self, dt): return 7
3212 def tzname(self, dt): return 0
3213 t = cls(1, 1, 1, tzinfo=C4())
3214 self.assertRaises(TypeError, t.utcoffset)
3215 self.assertRaises(TypeError, t.dst)
3216 self.assertRaises(TypeError, t.tzname)
3217
3218 # Offset out of range.
3219 class C6(tzinfo):
3220 def utcoffset(self, dt): return timedelta(hours=-24)
3221 def dst(self, dt): return timedelta(hours=24)
3222 t = cls(1, 1, 1, tzinfo=C6())
3223 self.assertRaises(ValueError, t.utcoffset)
3224 self.assertRaises(ValueError, t.dst)
3225
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04003226 # Not a whole number of seconds.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003227 class C7(tzinfo):
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04003228 def utcoffset(self, dt): return timedelta(microseconds=61)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003229 def dst(self, dt): return timedelta(microseconds=-81)
3230 t = cls(1, 1, 1, tzinfo=C7())
Alexander Belopolsky018d3532017-07-31 10:26:50 -04003231 self.assertEqual(t.utcoffset(), timedelta(microseconds=61))
3232 self.assertEqual(t.dst(), timedelta(microseconds=-81))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003233
3234 def test_aware_compare(self):
3235 cls = self.theclass
3236
3237 # Ensure that utcoffset() gets ignored if the comparands have
3238 # the same tzinfo member.
3239 class OperandDependentOffset(tzinfo):
3240 def utcoffset(self, t):
3241 if t.minute < 10:
3242 # d0 and d1 equal after adjustment
3243 return timedelta(minutes=t.minute)
3244 else:
3245 # d2 off in the weeds
3246 return timedelta(minutes=59)
3247
3248 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
3249 d0 = base.replace(minute=3)
3250 d1 = base.replace(minute=9)
3251 d2 = base.replace(minute=11)
3252 for x in d0, d1, d2:
3253 for y in d0, d1, d2:
3254 for op in lt, le, gt, ge, eq, ne:
3255 got = op(x, y)
3256 expected = op(x.minute, y.minute)
3257 self.assertEqual(got, expected)
3258
3259 # However, if they're different members, uctoffset is not ignored.
3260 # Note that a time can't actually have an operand-depedent offset,
3261 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
3262 # so skip this test for time.
3263 if cls is not time:
3264 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
3265 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
3266 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
3267 for x in d0, d1, d2:
3268 for y in d0, d1, d2:
3269 got = (x > y) - (x < y)
3270 if (x is d0 or x is d1) and (y is d0 or y is d1):
3271 expected = 0
3272 elif x is y is d2:
3273 expected = 0
3274 elif x is d2:
3275 expected = -1
3276 else:
3277 assert y is d2
3278 expected = 1
3279 self.assertEqual(got, expected)
3280
3281
3282# Testing time objects with a non-None tzinfo.
3283class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
3284 theclass = time
3285
3286 def test_empty(self):
3287 t = self.theclass()
3288 self.assertEqual(t.hour, 0)
3289 self.assertEqual(t.minute, 0)
3290 self.assertEqual(t.second, 0)
3291 self.assertEqual(t.microsecond, 0)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003292 self.assertIsNone(t.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003293
3294 def test_zones(self):
3295 est = FixedOffset(-300, "EST", 1)
3296 utc = FixedOffset(0, "UTC", -2)
3297 met = FixedOffset(60, "MET", 3)
3298 t1 = time( 7, 47, tzinfo=est)
3299 t2 = time(12, 47, tzinfo=utc)
3300 t3 = time(13, 47, tzinfo=met)
3301 t4 = time(microsecond=40)
3302 t5 = time(microsecond=40, tzinfo=utc)
3303
3304 self.assertEqual(t1.tzinfo, est)
3305 self.assertEqual(t2.tzinfo, utc)
3306 self.assertEqual(t3.tzinfo, met)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003307 self.assertIsNone(t4.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003308 self.assertEqual(t5.tzinfo, utc)
3309
3310 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
3311 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
3312 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003313 self.assertIsNone(t4.utcoffset())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003314 self.assertRaises(TypeError, t1.utcoffset, "no args")
3315
3316 self.assertEqual(t1.tzname(), "EST")
3317 self.assertEqual(t2.tzname(), "UTC")
3318 self.assertEqual(t3.tzname(), "MET")
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003319 self.assertIsNone(t4.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003320 self.assertRaises(TypeError, t1.tzname, "no args")
3321
3322 self.assertEqual(t1.dst(), timedelta(minutes=1))
3323 self.assertEqual(t2.dst(), timedelta(minutes=-2))
3324 self.assertEqual(t3.dst(), timedelta(minutes=3))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003325 self.assertIsNone(t4.dst())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003326 self.assertRaises(TypeError, t1.dst, "no args")
3327
3328 self.assertEqual(hash(t1), hash(t2))
3329 self.assertEqual(hash(t1), hash(t3))
3330 self.assertEqual(hash(t2), hash(t3))
3331
3332 self.assertEqual(t1, t2)
3333 self.assertEqual(t1, t3)
3334 self.assertEqual(t2, t3)
Alexander Belopolsky08313822012-06-15 20:19:47 -04003335 self.assertNotEqual(t4, t5) # mixed tz-aware & naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003336 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
3337 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
3338
3339 self.assertEqual(str(t1), "07:47:00-05:00")
3340 self.assertEqual(str(t2), "12:47:00+00:00")
3341 self.assertEqual(str(t3), "13:47:00+01:00")
3342 self.assertEqual(str(t4), "00:00:00.000040")
3343 self.assertEqual(str(t5), "00:00:00.000040+00:00")
3344
3345 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
3346 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
3347 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
3348 self.assertEqual(t4.isoformat(), "00:00:00.000040")
3349 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
3350
3351 d = 'datetime.time'
3352 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
3353 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
3354 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
3355 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
3356 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
3357
3358 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
3359 "07:47:00 %Z=EST %z=-0500")
3360 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
3361 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
3362
3363 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
3364 t1 = time(23, 59, tzinfo=yuck)
3365 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
3366 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
3367
3368 # Check that an invalid tzname result raises an exception.
3369 class Badtzname(tzinfo):
Alexander Belopolskye239d232010-12-08 23:31:48 +00003370 tz = 42
3371 def tzname(self, dt): return self.tz
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003372 t = time(2, 3, 4, tzinfo=Badtzname())
3373 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
3374 self.assertRaises(TypeError, t.strftime, "%Z")
3375
Alexander Belopolskye239d232010-12-08 23:31:48 +00003376 # Issue #6697:
Utkarsh Upadhyay287c5592017-07-21 02:14:54 +02003377 if '_Fast' in self.__class__.__name__:
Alexander Belopolskye239d232010-12-08 23:31:48 +00003378 Badtzname.tz = '\ud800'
3379 self.assertRaises(ValueError, t.strftime, "%Z")
3380
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003381 def test_hash_edge_cases(self):
3382 # Offsets that overflow a basic time.
3383 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
3384 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
3385 self.assertEqual(hash(t1), hash(t2))
3386
3387 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
3388 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
3389 self.assertEqual(hash(t1), hash(t2))
3390
3391 def test_pickling(self):
3392 # Try one without a tzinfo.
3393 args = 20, 59, 16, 64**2
3394 orig = self.theclass(*args)
3395 for pickler, unpickler, proto in pickle_choices:
3396 green = pickler.dumps(orig, proto)
3397 derived = unpickler.loads(green)
3398 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003399 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003400
3401 # Try one with a tzinfo.
3402 tinfo = PicklableFixedOffset(-300, 'cookie')
3403 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
3404 for pickler, unpickler, proto in pickle_choices:
3405 green = pickler.dumps(orig, proto)
3406 derived = unpickler.loads(green)
3407 self.assertEqual(orig, derived)
3408 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3409 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3410 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003411 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003412
3413 def test_more_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003414 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003415 cls = self.theclass
3416
3417 t = cls(0, tzinfo=FixedOffset(-300, ""))
3418 self.assertTrue(t)
3419
3420 t = cls(5, tzinfo=FixedOffset(-300, ""))
3421 self.assertTrue(t)
3422
3423 t = cls(5, tzinfo=FixedOffset(300, ""))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003424 self.assertTrue(t)
3425
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003426 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
3427 self.assertTrue(t)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003428
3429 def test_replace(self):
3430 cls = self.theclass
3431 z100 = FixedOffset(100, "+100")
3432 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3433 args = [1, 2, 3, 4, z100]
3434 base = cls(*args)
3435 self.assertEqual(base, base.replace())
3436
3437 i = 0
3438 for name, newval in (("hour", 5),
3439 ("minute", 6),
3440 ("second", 7),
3441 ("microsecond", 8),
3442 ("tzinfo", zm200)):
3443 newargs = args[:]
3444 newargs[i] = newval
3445 expected = cls(*newargs)
3446 got = base.replace(**{name: newval})
3447 self.assertEqual(expected, got)
3448 i += 1
3449
3450 # Ensure we can get rid of a tzinfo.
3451 self.assertEqual(base.tzname(), "+100")
3452 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003453 self.assertIsNone(base2.tzinfo)
3454 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003455
3456 # Ensure we can add one.
3457 base3 = base2.replace(tzinfo=z100)
3458 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003459 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003460
3461 # Out of bounds.
3462 base = cls(1)
3463 self.assertRaises(ValueError, base.replace, hour=24)
3464 self.assertRaises(ValueError, base.replace, minute=-1)
3465 self.assertRaises(ValueError, base.replace, second=100)
3466 self.assertRaises(ValueError, base.replace, microsecond=1000000)
3467
3468 def test_mixed_compare(self):
3469 t1 = time(1, 2, 3)
3470 t2 = time(1, 2, 3)
3471 self.assertEqual(t1, t2)
3472 t2 = t2.replace(tzinfo=None)
3473 self.assertEqual(t1, t2)
3474 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3475 self.assertEqual(t1, t2)
3476 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04003477 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003478
3479 # In time w/ identical tzinfo objects, utcoffset is ignored.
3480 class Varies(tzinfo):
3481 def __init__(self):
3482 self.offset = timedelta(minutes=22)
3483 def utcoffset(self, t):
3484 self.offset += timedelta(minutes=1)
3485 return self.offset
3486
3487 v = Varies()
3488 t1 = t2.replace(tzinfo=v)
3489 t2 = t2.replace(tzinfo=v)
3490 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3491 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3492 self.assertEqual(t1, t2)
3493
3494 # But if they're not identical, it isn't ignored.
3495 t2 = t2.replace(tzinfo=Varies())
3496 self.assertTrue(t1 < t2) # t1's offset counter still going up
3497
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003498 def test_fromisoformat(self):
3499 time_examples = [
3500 (0, 0, 0, 0),
3501 (23, 59, 59, 999999),
3502 ]
3503
3504 hh = (9, 12, 20)
3505 mm = (5, 30)
3506 ss = (4, 45)
3507 usec = (0, 245000, 678901)
3508
3509 time_examples += list(itertools.product(hh, mm, ss, usec))
3510
3511 tzinfos = [None, timezone.utc,
3512 timezone(timedelta(hours=2)),
3513 timezone(timedelta(hours=6, minutes=27))]
3514
3515 for ttup in time_examples:
3516 for tzi in tzinfos:
3517 t = self.theclass(*ttup, tzinfo=tzi)
3518 tstr = t.isoformat()
3519
3520 with self.subTest(tstr=tstr):
3521 t_rt = self.theclass.fromisoformat(tstr)
3522 self.assertEqual(t, t_rt)
3523
3524 def test_fromisoformat_timezone(self):
3525 base_time = self.theclass(12, 30, 45, 217456)
3526
3527 tzoffsets = [
3528 timedelta(hours=5), timedelta(hours=2),
3529 timedelta(hours=6, minutes=27),
3530 timedelta(hours=12, minutes=32, seconds=30),
3531 timedelta(hours=2, minutes=4, seconds=9, microseconds=123456)
3532 ]
3533
3534 tzoffsets += [-1 * td for td in tzoffsets]
3535
3536 tzinfos = [None, timezone.utc,
3537 timezone(timedelta(hours=0))]
3538
3539 tzinfos += [timezone(td) for td in tzoffsets]
3540
3541 for tzi in tzinfos:
3542 t = base_time.replace(tzinfo=tzi)
3543 tstr = t.isoformat()
3544
3545 with self.subTest(tstr=tstr):
3546 t_rt = self.theclass.fromisoformat(tstr)
3547 assert t == t_rt, t_rt
3548
3549 def test_fromisoformat_timespecs(self):
3550 time_bases = [
3551 (8, 17, 45, 123456),
3552 (8, 17, 45, 0)
3553 ]
3554
3555 tzinfos = [None, timezone.utc,
3556 timezone(timedelta(hours=-5)),
3557 timezone(timedelta(hours=2)),
3558 timezone(timedelta(hours=6, minutes=27))]
3559
3560 timespecs = ['hours', 'minutes', 'seconds',
3561 'milliseconds', 'microseconds']
3562
3563 for ip, ts in enumerate(timespecs):
3564 for tzi in tzinfos:
3565 for t_tuple in time_bases:
3566 if ts == 'milliseconds':
3567 new_microseconds = 1000 * (t_tuple[-1] // 1000)
3568 t_tuple = t_tuple[0:-1] + (new_microseconds,)
3569
3570 t = self.theclass(*(t_tuple[0:(1 + ip)]), tzinfo=tzi)
3571 tstr = t.isoformat(timespec=ts)
3572 with self.subTest(tstr=tstr):
3573 t_rt = self.theclass.fromisoformat(tstr)
3574 self.assertEqual(t, t_rt)
3575
3576 def test_fromisoformat_fails(self):
3577 bad_strs = [
3578 '', # Empty string
Paul Ganssle096329f2018-08-23 11:06:20 -04003579 '12\ud80000', # Invalid separator - surrogate char
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003580 '12:', # Ends on a separator
3581 '12:30:', # Ends on a separator
3582 '12:30:15.', # Ends on a separator
3583 '1', # Incomplete hours
3584 '12:3', # Incomplete minutes
3585 '12:30:1', # Incomplete seconds
3586 '1a:30:45.334034', # Invalid character in hours
3587 '12:a0:45.334034', # Invalid character in minutes
3588 '12:30:a5.334034', # Invalid character in seconds
3589 '12:30:45.1234', # Too many digits for milliseconds
3590 '12:30:45.1234567', # Too many digits for microseconds
3591 '12:30:45.123456+24:30', # Invalid time zone offset
3592 '12:30:45.123456-24:30', # Invalid negative offset
3593 '12:30:45', # Uses full-width unicode colons
3594 '12:30:45․123456', # Uses \u2024 in place of decimal point
3595 '12:30:45a', # Extra at tend of basic time
3596 '12:30:45.123a', # Extra at end of millisecond time
3597 '12:30:45.123456a', # Extra at end of microsecond time
3598 '12:30:45.123456+12:00:30a', # Extra at end of full time
3599 ]
3600
3601 for bad_str in bad_strs:
3602 with self.subTest(bad_str=bad_str):
3603 with self.assertRaises(ValueError):
3604 self.theclass.fromisoformat(bad_str)
3605
3606 def test_fromisoformat_fails_typeerror(self):
3607 # Test the fromisoformat fails when passed the wrong type
3608 import io
3609
3610 bad_types = [b'12:30:45', None, io.StringIO('12:30:45')]
3611
3612 for bad_type in bad_types:
3613 with self.assertRaises(TypeError):
3614 self.theclass.fromisoformat(bad_type)
3615
3616 def test_fromisoformat_subclass(self):
3617 class TimeSubclass(self.theclass):
3618 pass
3619
3620 tsc = TimeSubclass(12, 14, 45, 203745, tzinfo=timezone.utc)
3621 tsc_rt = TimeSubclass.fromisoformat(tsc.isoformat())
3622
3623 self.assertEqual(tsc, tsc_rt)
3624 self.assertIsInstance(tsc_rt, TimeSubclass)
3625
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003626 def test_subclass_timetz(self):
3627
3628 class C(self.theclass):
3629 theAnswer = 42
3630
3631 def __new__(cls, *args, **kws):
3632 temp = kws.copy()
3633 extra = temp.pop('extra')
3634 result = self.theclass.__new__(cls, *args, **temp)
3635 result.extra = extra
3636 return result
3637
3638 def newmeth(self, start):
3639 return start + self.hour + self.second
3640
3641 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3642
3643 dt1 = self.theclass(*args)
3644 dt2 = C(*args, **{'extra': 7})
3645
3646 self.assertEqual(dt2.__class__, C)
3647 self.assertEqual(dt2.theAnswer, 42)
3648 self.assertEqual(dt2.extra, 7)
3649 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3650 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
3651
3652
3653# Testing datetime objects with a non-None tzinfo.
3654
3655class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
3656 theclass = datetime
3657
3658 def test_trivial(self):
3659 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
3660 self.assertEqual(dt.year, 1)
3661 self.assertEqual(dt.month, 2)
3662 self.assertEqual(dt.day, 3)
3663 self.assertEqual(dt.hour, 4)
3664 self.assertEqual(dt.minute, 5)
3665 self.assertEqual(dt.second, 6)
3666 self.assertEqual(dt.microsecond, 7)
3667 self.assertEqual(dt.tzinfo, None)
3668
3669 def test_even_more_compare(self):
3670 # The test_compare() and test_more_compare() inherited from TestDate
3671 # and TestDateTime covered non-tzinfo cases.
3672
3673 # Smallest possible after UTC adjustment.
3674 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
3675 # Largest possible after UTC adjustment.
3676 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3677 tzinfo=FixedOffset(-1439, ""))
3678
3679 # Make sure those compare correctly, and w/o overflow.
3680 self.assertTrue(t1 < t2)
3681 self.assertTrue(t1 != t2)
3682 self.assertTrue(t2 > t1)
3683
3684 self.assertEqual(t1, t1)
3685 self.assertEqual(t2, t2)
3686
3687 # Equal afer adjustment.
3688 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
3689 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
3690 self.assertEqual(t1, t2)
3691
3692 # Change t1 not to subtract a minute, and t1 should be larger.
3693 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
3694 self.assertTrue(t1 > t2)
3695
3696 # Change t1 to subtract 2 minutes, and t1 should be smaller.
3697 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
3698 self.assertTrue(t1 < t2)
3699
3700 # Back to the original t1, but make seconds resolve it.
3701 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3702 second=1)
3703 self.assertTrue(t1 > t2)
3704
3705 # Likewise, but make microseconds resolve it.
3706 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3707 microsecond=1)
3708 self.assertTrue(t1 > t2)
3709
Alexander Belopolsky08313822012-06-15 20:19:47 -04003710 # Make t2 naive and it should differ.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003711 t2 = self.theclass.min
Alexander Belopolsky08313822012-06-15 20:19:47 -04003712 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003713 self.assertEqual(t2, t2)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04003714 # and > comparison should fail
3715 with self.assertRaises(TypeError):
3716 t1 > t2
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003717
3718 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
3719 class Naive(tzinfo):
3720 def utcoffset(self, dt): return None
3721 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
Alexander Belopolsky08313822012-06-15 20:19:47 -04003722 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003723 self.assertEqual(t2, t2)
3724
3725 # OTOH, it's OK to compare two of these mixing the two ways of being
3726 # naive.
3727 t1 = self.theclass(5, 6, 7)
3728 self.assertEqual(t1, t2)
3729
3730 # Try a bogus uctoffset.
3731 class Bogus(tzinfo):
3732 def utcoffset(self, dt):
3733 return timedelta(minutes=1440) # out of bounds
3734 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
3735 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
3736 self.assertRaises(ValueError, lambda: t1 == t2)
3737
3738 def test_pickling(self):
3739 # Try one without a tzinfo.
3740 args = 6, 7, 23, 20, 59, 1, 64**2
3741 orig = self.theclass(*args)
3742 for pickler, unpickler, proto in pickle_choices:
3743 green = pickler.dumps(orig, proto)
3744 derived = unpickler.loads(green)
3745 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003746 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003747
3748 # Try one with a tzinfo.
3749 tinfo = PicklableFixedOffset(-300, 'cookie')
3750 orig = self.theclass(*args, **{'tzinfo': tinfo})
3751 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
3752 for pickler, unpickler, proto in pickle_choices:
3753 green = pickler.dumps(orig, proto)
3754 derived = unpickler.loads(green)
3755 self.assertEqual(orig, derived)
3756 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3757 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3758 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003759 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003760
3761 def test_extreme_hashes(self):
3762 # If an attempt is made to hash these via subtracting the offset
3763 # then hashing a datetime object, OverflowError results. The
3764 # Python implementation used to blow up here.
3765 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
3766 hash(t)
3767 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3768 tzinfo=FixedOffset(-1439, ""))
3769 hash(t)
3770
3771 # OTOH, an OOB offset should blow up.
3772 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
3773 self.assertRaises(ValueError, hash, t)
3774
3775 def test_zones(self):
3776 est = FixedOffset(-300, "EST")
3777 utc = FixedOffset(0, "UTC")
3778 met = FixedOffset(60, "MET")
3779 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
3780 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
3781 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
3782 self.assertEqual(t1.tzinfo, est)
3783 self.assertEqual(t2.tzinfo, utc)
3784 self.assertEqual(t3.tzinfo, met)
3785 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
3786 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
3787 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
3788 self.assertEqual(t1.tzname(), "EST")
3789 self.assertEqual(t2.tzname(), "UTC")
3790 self.assertEqual(t3.tzname(), "MET")
3791 self.assertEqual(hash(t1), hash(t2))
3792 self.assertEqual(hash(t1), hash(t3))
3793 self.assertEqual(hash(t2), hash(t3))
3794 self.assertEqual(t1, t2)
3795 self.assertEqual(t1, t3)
3796 self.assertEqual(t2, t3)
3797 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
3798 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
3799 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
3800 d = 'datetime.datetime(2002, 3, 19, '
3801 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
3802 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
3803 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
3804
3805 def test_combine(self):
3806 met = FixedOffset(60, "MET")
3807 d = date(2002, 3, 4)
3808 tz = time(18, 45, 3, 1234, tzinfo=met)
3809 dt = datetime.combine(d, tz)
3810 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
3811 tzinfo=met))
3812
3813 def test_extract(self):
3814 met = FixedOffset(60, "MET")
3815 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
3816 self.assertEqual(dt.date(), date(2002, 3, 4))
3817 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
3818 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
3819
3820 def test_tz_aware_arithmetic(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003821 now = self.theclass.now()
3822 tz55 = FixedOffset(-330, "west 5:30")
3823 timeaware = now.time().replace(tzinfo=tz55)
3824 nowaware = self.theclass.combine(now.date(), timeaware)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003825 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003826 self.assertEqual(nowaware.timetz(), timeaware)
3827
3828 # Can't mix aware and non-aware.
3829 self.assertRaises(TypeError, lambda: now - nowaware)
3830 self.assertRaises(TypeError, lambda: nowaware - now)
3831
3832 # And adding datetime's doesn't make sense, aware or not.
3833 self.assertRaises(TypeError, lambda: now + nowaware)
3834 self.assertRaises(TypeError, lambda: nowaware + now)
3835 self.assertRaises(TypeError, lambda: nowaware + nowaware)
3836
3837 # Subtracting should yield 0.
3838 self.assertEqual(now - now, timedelta(0))
3839 self.assertEqual(nowaware - nowaware, timedelta(0))
3840
3841 # Adding a delta should preserve tzinfo.
3842 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
3843 nowawareplus = nowaware + delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003844 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003845 nowawareplus2 = delta + nowaware
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003846 self.assertIs(nowawareplus2.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003847 self.assertEqual(nowawareplus, nowawareplus2)
3848
3849 # that - delta should be what we started with, and that - what we
3850 # started with should be delta.
3851 diff = nowawareplus - delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003852 self.assertIs(diff.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003853 self.assertEqual(nowaware, diff)
3854 self.assertRaises(TypeError, lambda: delta - nowawareplus)
3855 self.assertEqual(nowawareplus - nowaware, delta)
3856
3857 # Make up a random timezone.
3858 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
3859 # Attach it to nowawareplus.
3860 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003861 self.assertIs(nowawareplus.tzinfo, tzr)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003862 # Make sure the difference takes the timezone adjustments into account.
3863 got = nowaware - nowawareplus
3864 # Expected: (nowaware base - nowaware offset) -
3865 # (nowawareplus base - nowawareplus offset) =
3866 # (nowaware base - nowawareplus base) +
3867 # (nowawareplus offset - nowaware offset) =
3868 # -delta + nowawareplus offset - nowaware offset
3869 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
3870 self.assertEqual(got, expected)
3871
3872 # Try max possible difference.
3873 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
3874 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3875 tzinfo=FixedOffset(-1439, "max"))
3876 maxdiff = max - min
3877 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
3878 timedelta(minutes=2*1439))
3879 # Different tzinfo, but the same offset
3880 tza = timezone(HOUR, 'A')
3881 tzb = timezone(HOUR, 'B')
3882 delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
3883 self.assertEqual(delta, self.theclass.min - self.theclass.max)
3884
3885 def test_tzinfo_now(self):
3886 meth = self.theclass.now
3887 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3888 base = meth()
3889 # Try with and without naming the keyword.
3890 off42 = FixedOffset(42, "42")
3891 another = meth(off42)
3892 again = meth(tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003893 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003894 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3895 # Bad argument with and w/o naming the keyword.
3896 self.assertRaises(TypeError, meth, 16)
3897 self.assertRaises(TypeError, meth, tzinfo=16)
3898 # Bad keyword name.
3899 self.assertRaises(TypeError, meth, tinfo=off42)
3900 # Too many args.
3901 self.assertRaises(TypeError, meth, off42, off42)
3902
3903 # We don't know which time zone we're in, and don't have a tzinfo
3904 # class to represent it, so seeing whether a tz argument actually
3905 # does a conversion is tricky.
3906 utc = FixedOffset(0, "utc", 0)
3907 for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
3908 timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
3909 for dummy in range(3):
3910 now = datetime.now(weirdtz)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003911 self.assertIs(now.tzinfo, weirdtz)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003912 utcnow = datetime.utcnow().replace(tzinfo=utc)
3913 now2 = utcnow.astimezone(weirdtz)
3914 if abs(now - now2) < timedelta(seconds=30):
3915 break
3916 # Else the code is broken, or more than 30 seconds passed between
3917 # calls; assuming the latter, just try again.
3918 else:
3919 # Three strikes and we're out.
3920 self.fail("utcnow(), now(tz), or astimezone() may be broken")
3921
3922 def test_tzinfo_fromtimestamp(self):
3923 import time
3924 meth = self.theclass.fromtimestamp
3925 ts = time.time()
3926 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3927 base = meth(ts)
3928 # Try with and without naming the keyword.
3929 off42 = FixedOffset(42, "42")
3930 another = meth(ts, off42)
3931 again = meth(ts, tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003932 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003933 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3934 # Bad argument with and w/o naming the keyword.
3935 self.assertRaises(TypeError, meth, ts, 16)
3936 self.assertRaises(TypeError, meth, ts, tzinfo=16)
3937 # Bad keyword name.
3938 self.assertRaises(TypeError, meth, ts, tinfo=off42)
3939 # Too many args.
3940 self.assertRaises(TypeError, meth, ts, off42, off42)
3941 # Too few args.
3942 self.assertRaises(TypeError, meth)
3943
3944 # Try to make sure tz= actually does some conversion.
3945 timestamp = 1000000000
3946 utcdatetime = datetime.utcfromtimestamp(timestamp)
3947 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
3948 # But on some flavor of Mac, it's nowhere near that. So we can't have
3949 # any idea here what time that actually is, we can only test that
3950 # relative changes match.
3951 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
3952 tz = FixedOffset(utcoffset, "tz", 0)
3953 expected = utcdatetime + utcoffset
3954 got = datetime.fromtimestamp(timestamp, tz)
3955 self.assertEqual(expected, got.replace(tzinfo=None))
3956
3957 def test_tzinfo_utcnow(self):
3958 meth = self.theclass.utcnow
3959 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3960 base = meth()
3961 # Try with and without naming the keyword; for whatever reason,
3962 # utcnow() doesn't accept a tzinfo argument.
3963 off42 = FixedOffset(42, "42")
3964 self.assertRaises(TypeError, meth, off42)
3965 self.assertRaises(TypeError, meth, tzinfo=off42)
3966
3967 def test_tzinfo_utcfromtimestamp(self):
3968 import time
3969 meth = self.theclass.utcfromtimestamp
3970 ts = time.time()
3971 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3972 base = meth(ts)
3973 # Try with and without naming the keyword; for whatever reason,
3974 # utcfromtimestamp() doesn't accept a tzinfo argument.
3975 off42 = FixedOffset(42, "42")
3976 self.assertRaises(TypeError, meth, ts, off42)
3977 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
3978
3979 def test_tzinfo_timetuple(self):
3980 # TestDateTime tested most of this. datetime adds a twist to the
3981 # DST flag.
3982 class DST(tzinfo):
3983 def __init__(self, dstvalue):
3984 if isinstance(dstvalue, int):
3985 dstvalue = timedelta(minutes=dstvalue)
3986 self.dstvalue = dstvalue
3987 def dst(self, dt):
3988 return self.dstvalue
3989
3990 cls = self.theclass
3991 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
3992 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
3993 t = d.timetuple()
3994 self.assertEqual(1, t.tm_year)
3995 self.assertEqual(1, t.tm_mon)
3996 self.assertEqual(1, t.tm_mday)
3997 self.assertEqual(10, t.tm_hour)
3998 self.assertEqual(20, t.tm_min)
3999 self.assertEqual(30, t.tm_sec)
4000 self.assertEqual(0, t.tm_wday)
4001 self.assertEqual(1, t.tm_yday)
4002 self.assertEqual(flag, t.tm_isdst)
4003
4004 # dst() returns wrong type.
4005 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
4006
4007 # dst() at the edge.
4008 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
4009 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
4010
4011 # dst() out of range.
4012 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
4013 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
4014
4015 def test_utctimetuple(self):
4016 class DST(tzinfo):
4017 def __init__(self, dstvalue=0):
4018 if isinstance(dstvalue, int):
4019 dstvalue = timedelta(minutes=dstvalue)
4020 self.dstvalue = dstvalue
4021 def dst(self, dt):
4022 return self.dstvalue
4023
4024 cls = self.theclass
4025 # This can't work: DST didn't implement utcoffset.
4026 self.assertRaises(NotImplementedError,
4027 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
4028
4029 class UOFS(DST):
4030 def __init__(self, uofs, dofs=None):
4031 DST.__init__(self, dofs)
4032 self.uofs = timedelta(minutes=uofs)
4033 def utcoffset(self, dt):
4034 return self.uofs
4035
4036 for dstvalue in -33, 33, 0, None:
4037 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
4038 t = d.utctimetuple()
4039 self.assertEqual(d.year, t.tm_year)
4040 self.assertEqual(d.month, t.tm_mon)
4041 self.assertEqual(d.day, t.tm_mday)
4042 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
4043 self.assertEqual(13, t.tm_min)
4044 self.assertEqual(d.second, t.tm_sec)
4045 self.assertEqual(d.weekday(), t.tm_wday)
4046 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
4047 t.tm_yday)
4048 # Ensure tm_isdst is 0 regardless of what dst() says: DST
4049 # is never in effect for a UTC time.
4050 self.assertEqual(0, t.tm_isdst)
4051
4052 # For naive datetime, utctimetuple == timetuple except for isdst
4053 d = cls(1, 2, 3, 10, 20, 30, 40)
4054 t = d.utctimetuple()
4055 self.assertEqual(t[:-1], d.timetuple()[:-1])
4056 self.assertEqual(0, t.tm_isdst)
4057 # Same if utcoffset is None
4058 class NOFS(DST):
4059 def utcoffset(self, dt):
4060 return None
4061 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
4062 t = d.utctimetuple()
4063 self.assertEqual(t[:-1], d.timetuple()[:-1])
4064 self.assertEqual(0, t.tm_isdst)
4065 # Check that bad tzinfo is detected
4066 class BOFS(DST):
4067 def utcoffset(self, dt):
4068 return "EST"
4069 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
4070 self.assertRaises(TypeError, d.utctimetuple)
4071
4072 # Check that utctimetuple() is the same as
4073 # astimezone(utc).timetuple()
4074 d = cls(2010, 11, 13, 14, 15, 16, 171819)
4075 for tz in [timezone.min, timezone.utc, timezone.max]:
4076 dtz = d.replace(tzinfo=tz)
4077 self.assertEqual(dtz.utctimetuple()[:-1],
4078 dtz.astimezone(timezone.utc).timetuple()[:-1])
4079 # At the edges, UTC adjustment can produce years out-of-range
4080 # for a datetime object. Ensure that an OverflowError is
4081 # raised.
4082 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
4083 # That goes back 1 minute less than a full day.
4084 self.assertRaises(OverflowError, tiny.utctimetuple)
4085
4086 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
4087 # That goes forward 1 minute less than a full day.
4088 self.assertRaises(OverflowError, huge.utctimetuple)
4089 # More overflow cases
4090 tiny = cls.min.replace(tzinfo=timezone(MINUTE))
4091 self.assertRaises(OverflowError, tiny.utctimetuple)
4092 huge = cls.max.replace(tzinfo=timezone(-MINUTE))
4093 self.assertRaises(OverflowError, huge.utctimetuple)
4094
4095 def test_tzinfo_isoformat(self):
4096 zero = FixedOffset(0, "+00:00")
4097 plus = FixedOffset(220, "+03:40")
4098 minus = FixedOffset(-231, "-03:51")
4099 unknown = FixedOffset(None, "")
4100
4101 cls = self.theclass
4102 datestr = '0001-02-03'
4103 for ofs in None, zero, plus, minus, unknown:
4104 for us in 0, 987001:
4105 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
4106 timestr = '04:05:59' + (us and '.987001' or '')
4107 ofsstr = ofs is not None and d.tzname() or ''
4108 tailstr = timestr + ofsstr
4109 iso = d.isoformat()
4110 self.assertEqual(iso, datestr + 'T' + tailstr)
4111 self.assertEqual(iso, d.isoformat('T'))
4112 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
4113 self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
4114 self.assertEqual(str(d), datestr + ' ' + tailstr)
4115
4116 def test_replace(self):
4117 cls = self.theclass
4118 z100 = FixedOffset(100, "+100")
4119 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
4120 args = [1, 2, 3, 4, 5, 6, 7, z100]
4121 base = cls(*args)
4122 self.assertEqual(base, base.replace())
4123
4124 i = 0
4125 for name, newval in (("year", 2),
4126 ("month", 3),
4127 ("day", 4),
4128 ("hour", 5),
4129 ("minute", 6),
4130 ("second", 7),
4131 ("microsecond", 8),
4132 ("tzinfo", zm200)):
4133 newargs = args[:]
4134 newargs[i] = newval
4135 expected = cls(*newargs)
4136 got = base.replace(**{name: newval})
4137 self.assertEqual(expected, got)
4138 i += 1
4139
4140 # Ensure we can get rid of a tzinfo.
4141 self.assertEqual(base.tzname(), "+100")
4142 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004143 self.assertIsNone(base2.tzinfo)
4144 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004145
4146 # Ensure we can add one.
4147 base3 = base2.replace(tzinfo=z100)
4148 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004149 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004150
4151 # Out of bounds.
4152 base = cls(2000, 2, 29)
4153 self.assertRaises(ValueError, base.replace, year=2001)
4154
4155 def test_more_astimezone(self):
4156 # The inherited test_astimezone covered some trivial and error cases.
4157 fnone = FixedOffset(None, "None")
4158 f44m = FixedOffset(44, "44")
4159 fm5h = FixedOffset(-timedelta(hours=5), "m300")
4160
4161 dt = self.theclass.now(tz=f44m)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004162 self.assertIs(dt.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004163 # Replacing with degenerate tzinfo raises an exception.
4164 self.assertRaises(ValueError, dt.astimezone, fnone)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004165 # Replacing with same tzinfo makes no change.
4166 x = dt.astimezone(dt.tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004167 self.assertIs(x.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004168 self.assertEqual(x.date(), dt.date())
4169 self.assertEqual(x.time(), dt.time())
4170
4171 # Replacing with different tzinfo does adjust.
4172 got = dt.astimezone(fm5h)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004173 self.assertIs(got.tzinfo, fm5h)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004174 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
4175 expected = dt - dt.utcoffset() # in effect, convert to UTC
4176 expected += fm5h.utcoffset(dt) # and from there to local time
4177 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
4178 self.assertEqual(got.date(), expected.date())
4179 self.assertEqual(got.time(), expected.time())
4180 self.assertEqual(got.timetz(), expected.timetz())
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004181 self.assertIs(got.tzinfo, expected.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004182 self.assertEqual(got, expected)
4183
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004184 @support.run_with_tz('UTC')
4185 def test_astimezone_default_utc(self):
4186 dt = self.theclass.now(timezone.utc)
4187 self.assertEqual(dt.astimezone(None), dt)
4188 self.assertEqual(dt.astimezone(), dt)
4189
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004190 # Note that offset in TZ variable has the opposite sign to that
4191 # produced by %z directive.
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004192 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4193 def test_astimezone_default_eastern(self):
4194 dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
4195 local = dt.astimezone()
4196 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004197 self.assertEqual(local.strftime("%z %Z"), "-0500 EST")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004198 dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc)
4199 local = dt.astimezone()
4200 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004201 self.assertEqual(local.strftime("%z %Z"), "-0400 EDT")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004202
Alexander Belopolsky1dcf4f92016-03-25 15:42:59 -04004203 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4204 def test_astimezone_default_near_fold(self):
4205 # Issue #26616.
4206 u = datetime(2015, 11, 1, 5, tzinfo=timezone.utc)
4207 t = u.astimezone()
4208 s = t.astimezone()
4209 self.assertEqual(t.tzinfo, s.tzinfo)
4210
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004211 def test_aware_subtract(self):
4212 cls = self.theclass
4213
4214 # Ensure that utcoffset() is ignored when the operands have the
4215 # same tzinfo member.
4216 class OperandDependentOffset(tzinfo):
4217 def utcoffset(self, t):
4218 if t.minute < 10:
4219 # d0 and d1 equal after adjustment
4220 return timedelta(minutes=t.minute)
4221 else:
4222 # d2 off in the weeds
4223 return timedelta(minutes=59)
4224
4225 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
4226 d0 = base.replace(minute=3)
4227 d1 = base.replace(minute=9)
4228 d2 = base.replace(minute=11)
4229 for x in d0, d1, d2:
4230 for y in d0, d1, d2:
4231 got = x - y
4232 expected = timedelta(minutes=x.minute - y.minute)
4233 self.assertEqual(got, expected)
4234
4235 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
4236 # ignored.
4237 base = cls(8, 9, 10, 11, 12, 13, 14)
4238 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
4239 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
4240 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
4241 for x in d0, d1, d2:
4242 for y in d0, d1, d2:
4243 got = x - y
4244 if (x is d0 or x is d1) and (y is d0 or y is d1):
4245 expected = timedelta(0)
4246 elif x is y is d2:
4247 expected = timedelta(0)
4248 elif x is d2:
4249 expected = timedelta(minutes=(11-59)-0)
4250 else:
4251 assert y is d2
4252 expected = timedelta(minutes=0-(11-59))
4253 self.assertEqual(got, expected)
4254
4255 def test_mixed_compare(self):
4256 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
4257 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
4258 self.assertEqual(t1, t2)
4259 t2 = t2.replace(tzinfo=None)
4260 self.assertEqual(t1, t2)
4261 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
4262 self.assertEqual(t1, t2)
4263 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04004264 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004265
4266 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
4267 class Varies(tzinfo):
4268 def __init__(self):
4269 self.offset = timedelta(minutes=22)
4270 def utcoffset(self, t):
4271 self.offset += timedelta(minutes=1)
4272 return self.offset
4273
4274 v = Varies()
4275 t1 = t2.replace(tzinfo=v)
4276 t2 = t2.replace(tzinfo=v)
4277 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
4278 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
4279 self.assertEqual(t1, t2)
4280
4281 # But if they're not identical, it isn't ignored.
4282 t2 = t2.replace(tzinfo=Varies())
4283 self.assertTrue(t1 < t2) # t1's offset counter still going up
4284
4285 def test_subclass_datetimetz(self):
4286
4287 class C(self.theclass):
4288 theAnswer = 42
4289
4290 def __new__(cls, *args, **kws):
4291 temp = kws.copy()
4292 extra = temp.pop('extra')
4293 result = self.theclass.__new__(cls, *args, **temp)
4294 result.extra = extra
4295 return result
4296
4297 def newmeth(self, start):
4298 return start + self.hour + self.year
4299
4300 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
4301
4302 dt1 = self.theclass(*args)
4303 dt2 = C(*args, **{'extra': 7})
4304
4305 self.assertEqual(dt2.__class__, C)
4306 self.assertEqual(dt2.theAnswer, 42)
4307 self.assertEqual(dt2.extra, 7)
4308 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
4309 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
4310
4311# Pain to set up DST-aware tzinfo classes.
4312
4313def first_sunday_on_or_after(dt):
4314 days_to_go = 6 - dt.weekday()
4315 if days_to_go:
4316 dt += timedelta(days_to_go)
4317 return dt
4318
4319ZERO = timedelta(0)
4320MINUTE = timedelta(minutes=1)
4321HOUR = timedelta(hours=1)
4322DAY = timedelta(days=1)
4323# In the US, DST starts at 2am (standard time) on the first Sunday in April.
4324DSTSTART = datetime(1, 4, 1, 2)
4325# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
4326# which is the first Sunday on or after Oct 25. Because we view 1:MM as
4327# being standard time on that day, there is no spelling in local time of
4328# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
4329DSTEND = datetime(1, 10, 25, 1)
4330
4331class USTimeZone(tzinfo):
4332
4333 def __init__(self, hours, reprname, stdname, dstname):
4334 self.stdoffset = timedelta(hours=hours)
4335 self.reprname = reprname
4336 self.stdname = stdname
4337 self.dstname = dstname
4338
4339 def __repr__(self):
4340 return self.reprname
4341
4342 def tzname(self, dt):
4343 if self.dst(dt):
4344 return self.dstname
4345 else:
4346 return self.stdname
4347
4348 def utcoffset(self, dt):
4349 return self.stdoffset + self.dst(dt)
4350
4351 def dst(self, dt):
4352 if dt is None or dt.tzinfo is None:
4353 # An exception instead may be sensible here, in one or more of
4354 # the cases.
4355 return ZERO
4356 assert dt.tzinfo is self
4357
4358 # Find first Sunday in April.
4359 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
4360 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
4361
4362 # Find last Sunday in October.
4363 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
4364 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
4365
4366 # Can't compare naive to aware objects, so strip the timezone from
4367 # dt first.
4368 if start <= dt.replace(tzinfo=None) < end:
4369 return HOUR
4370 else:
4371 return ZERO
4372
4373Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
4374Central = USTimeZone(-6, "Central", "CST", "CDT")
4375Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
4376Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
4377utc_real = FixedOffset(0, "UTC", 0)
4378# For better test coverage, we want another flavor of UTC that's west of
4379# the Eastern and Pacific timezones.
4380utc_fake = FixedOffset(-12*60, "UTCfake", 0)
4381
4382class TestTimezoneConversions(unittest.TestCase):
4383 # The DST switch times for 2002, in std time.
4384 dston = datetime(2002, 4, 7, 2)
4385 dstoff = datetime(2002, 10, 27, 1)
4386
4387 theclass = datetime
4388
4389 # Check a time that's inside DST.
4390 def checkinside(self, dt, tz, utc, dston, dstoff):
4391 self.assertEqual(dt.dst(), HOUR)
4392
4393 # Conversion to our own timezone is always an identity.
4394 self.assertEqual(dt.astimezone(tz), dt)
4395
4396 asutc = dt.astimezone(utc)
4397 there_and_back = asutc.astimezone(tz)
4398
4399 # Conversion to UTC and back isn't always an identity here,
4400 # because there are redundant spellings (in local time) of
4401 # UTC time when DST begins: the clock jumps from 1:59:59
4402 # to 3:00:00, and a local time of 2:MM:SS doesn't really
4403 # make sense then. The classes above treat 2:MM:SS as
4404 # daylight time then (it's "after 2am"), really an alias
4405 # for 1:MM:SS standard time. The latter form is what
4406 # conversion back from UTC produces.
4407 if dt.date() == dston.date() and dt.hour == 2:
4408 # We're in the redundant hour, and coming back from
4409 # UTC gives the 1:MM:SS standard-time spelling.
4410 self.assertEqual(there_and_back + HOUR, dt)
4411 # Although during was considered to be in daylight
4412 # time, there_and_back is not.
4413 self.assertEqual(there_and_back.dst(), ZERO)
4414 # They're the same times in UTC.
4415 self.assertEqual(there_and_back.astimezone(utc),
4416 dt.astimezone(utc))
4417 else:
4418 # We're not in the redundant hour.
4419 self.assertEqual(dt, there_and_back)
4420
4421 # Because we have a redundant spelling when DST begins, there is
Ezio Melotti3b3499b2011-03-16 11:35:38 +02004422 # (unfortunately) an hour when DST ends that can't be spelled at all in
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004423 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
4424 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
4425 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
4426 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
4427 # expressed in local time. Nevertheless, we want conversion back
4428 # from UTC to mimic the local clock's "repeat an hour" behavior.
4429 nexthour_utc = asutc + HOUR
4430 nexthour_tz = nexthour_utc.astimezone(tz)
4431 if dt.date() == dstoff.date() and dt.hour == 0:
4432 # We're in the hour before the last DST hour. The last DST hour
4433 # is ineffable. We want the conversion back to repeat 1:MM.
4434 self.assertEqual(nexthour_tz, dt.replace(hour=1))
4435 nexthour_utc += HOUR
4436 nexthour_tz = nexthour_utc.astimezone(tz)
4437 self.assertEqual(nexthour_tz, dt.replace(hour=1))
4438 else:
4439 self.assertEqual(nexthour_tz - dt, HOUR)
4440
4441 # Check a time that's outside DST.
4442 def checkoutside(self, dt, tz, utc):
4443 self.assertEqual(dt.dst(), ZERO)
4444
4445 # Conversion to our own timezone is always an identity.
4446 self.assertEqual(dt.astimezone(tz), dt)
4447
4448 # Converting to UTC and back is an identity too.
4449 asutc = dt.astimezone(utc)
4450 there_and_back = asutc.astimezone(tz)
4451 self.assertEqual(dt, there_and_back)
4452
4453 def convert_between_tz_and_utc(self, tz, utc):
4454 dston = self.dston.replace(tzinfo=tz)
4455 # Because 1:MM on the day DST ends is taken as being standard time,
4456 # there is no spelling in tz for the last hour of daylight time.
4457 # For purposes of the test, the last hour of DST is 0:MM, which is
4458 # taken as being daylight time (and 1:MM is taken as being standard
4459 # time).
4460 dstoff = self.dstoff.replace(tzinfo=tz)
4461 for delta in (timedelta(weeks=13),
4462 DAY,
4463 HOUR,
4464 timedelta(minutes=1),
4465 timedelta(microseconds=1)):
4466
4467 self.checkinside(dston, tz, utc, dston, dstoff)
4468 for during in dston + delta, dstoff - delta:
4469 self.checkinside(during, tz, utc, dston, dstoff)
4470
4471 self.checkoutside(dstoff, tz, utc)
4472 for outside in dston - delta, dstoff + delta:
4473 self.checkoutside(outside, tz, utc)
4474
4475 def test_easy(self):
4476 # Despite the name of this test, the endcases are excruciating.
4477 self.convert_between_tz_and_utc(Eastern, utc_real)
4478 self.convert_between_tz_and_utc(Pacific, utc_real)
4479 self.convert_between_tz_and_utc(Eastern, utc_fake)
4480 self.convert_between_tz_and_utc(Pacific, utc_fake)
4481 # The next is really dancing near the edge. It works because
4482 # Pacific and Eastern are far enough apart that their "problem
4483 # hours" don't overlap.
4484 self.convert_between_tz_and_utc(Eastern, Pacific)
4485 self.convert_between_tz_and_utc(Pacific, Eastern)
4486 # OTOH, these fail! Don't enable them. The difficulty is that
4487 # the edge case tests assume that every hour is representable in
4488 # the "utc" class. This is always true for a fixed-offset tzinfo
4489 # class (lke utc_real and utc_fake), but not for Eastern or Central.
4490 # For these adjacent DST-aware time zones, the range of time offsets
4491 # tested ends up creating hours in the one that aren't representable
4492 # in the other. For the same reason, we would see failures in the
4493 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
4494 # offset deltas in convert_between_tz_and_utc().
4495 #
4496 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
4497 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
4498
4499 def test_tricky(self):
4500 # 22:00 on day before daylight starts.
4501 fourback = self.dston - timedelta(hours=4)
4502 ninewest = FixedOffset(-9*60, "-0900", 0)
4503 fourback = fourback.replace(tzinfo=ninewest)
4504 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
4505 # 2", we should get the 3 spelling.
4506 # If we plug 22:00 the day before into Eastern, it "looks like std
4507 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
4508 # to 22:00 lands on 2:00, which makes no sense in local time (the
4509 # local clock jumps from 1 to 3). The point here is to make sure we
4510 # get the 3 spelling.
4511 expected = self.dston.replace(hour=3)
4512 got = fourback.astimezone(Eastern).replace(tzinfo=None)
4513 self.assertEqual(expected, got)
4514
4515 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
4516 # case we want the 1:00 spelling.
4517 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
4518 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
4519 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
4520 # spelling.
4521 expected = self.dston.replace(hour=1)
4522 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
4523 self.assertEqual(expected, got)
4524
4525 # Now on the day DST ends, we want "repeat an hour" behavior.
4526 # UTC 4:MM 5:MM 6:MM 7:MM checking these
4527 # EST 23:MM 0:MM 1:MM 2:MM
4528 # EDT 0:MM 1:MM 2:MM 3:MM
4529 # wall 0:MM 1:MM 1:MM 2:MM against these
4530 for utc in utc_real, utc_fake:
4531 for tz in Eastern, Pacific:
4532 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
4533 # Convert that to UTC.
4534 first_std_hour -= tz.utcoffset(None)
4535 # Adjust for possibly fake UTC.
4536 asutc = first_std_hour + utc.utcoffset(None)
4537 # First UTC hour to convert; this is 4:00 when utc=utc_real &
4538 # tz=Eastern.
4539 asutcbase = asutc.replace(tzinfo=utc)
4540 for tzhour in (0, 1, 1, 2):
4541 expectedbase = self.dstoff.replace(hour=tzhour)
4542 for minute in 0, 30, 59:
4543 expected = expectedbase.replace(minute=minute)
4544 asutc = asutcbase.replace(minute=minute)
4545 astz = asutc.astimezone(tz)
4546 self.assertEqual(astz.replace(tzinfo=None), expected)
4547 asutcbase += HOUR
4548
4549
4550 def test_bogus_dst(self):
4551 class ok(tzinfo):
4552 def utcoffset(self, dt): return HOUR
4553 def dst(self, dt): return HOUR
4554
4555 now = self.theclass.now().replace(tzinfo=utc_real)
4556 # Doesn't blow up.
4557 now.astimezone(ok())
4558
4559 # Does blow up.
4560 class notok(ok):
4561 def dst(self, dt): return None
4562 self.assertRaises(ValueError, now.astimezone, notok())
4563
4564 # Sometimes blow up. In the following, tzinfo.dst()
4565 # implementation may return None or not None depending on
4566 # whether DST is assumed to be in effect. In this situation,
4567 # a ValueError should be raised by astimezone().
4568 class tricky_notok(ok):
4569 def dst(self, dt):
4570 if dt.year == 2000:
4571 return None
4572 else:
4573 return 10*HOUR
4574 dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
4575 self.assertRaises(ValueError, dt.astimezone, tricky_notok())
4576
4577 def test_fromutc(self):
4578 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
4579 now = datetime.utcnow().replace(tzinfo=utc_real)
4580 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
4581 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
4582 enow = Eastern.fromutc(now) # doesn't blow up
4583 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
4584 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
4585 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
4586
4587 # Always converts UTC to standard time.
4588 class FauxUSTimeZone(USTimeZone):
4589 def fromutc(self, dt):
4590 return dt + self.stdoffset
4591 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
4592
4593 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
4594 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
4595 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
4596
4597 # Check around DST start.
4598 start = self.dston.replace(hour=4, tzinfo=Eastern)
4599 fstart = start.replace(tzinfo=FEastern)
4600 for wall in 23, 0, 1, 3, 4, 5:
4601 expected = start.replace(hour=wall)
4602 if wall == 23:
4603 expected -= timedelta(days=1)
4604 got = Eastern.fromutc(start)
4605 self.assertEqual(expected, got)
4606
4607 expected = fstart + FEastern.stdoffset
4608 got = FEastern.fromutc(fstart)
4609 self.assertEqual(expected, got)
4610
4611 # Ensure astimezone() calls fromutc() too.
4612 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
4613 self.assertEqual(expected, got)
4614
4615 start += HOUR
4616 fstart += HOUR
4617
4618 # Check around DST end.
4619 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
4620 fstart = start.replace(tzinfo=FEastern)
4621 for wall in 0, 1, 1, 2, 3, 4:
4622 expected = start.replace(hour=wall)
4623 got = Eastern.fromutc(start)
4624 self.assertEqual(expected, got)
4625
4626 expected = fstart + FEastern.stdoffset
4627 got = FEastern.fromutc(fstart)
4628 self.assertEqual(expected, got)
4629
4630 # Ensure astimezone() calls fromutc() too.
4631 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
4632 self.assertEqual(expected, got)
4633
4634 start += HOUR
4635 fstart += HOUR
4636
4637
4638#############################################################################
4639# oddballs
4640
4641class Oddballs(unittest.TestCase):
4642
4643 def test_bug_1028306(self):
4644 # Trying to compare a date to a datetime should act like a mixed-
4645 # type comparison, despite that datetime is a subclass of date.
4646 as_date = date.today()
4647 as_datetime = datetime.combine(as_date, time())
4648 self.assertTrue(as_date != as_datetime)
4649 self.assertTrue(as_datetime != as_date)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004650 self.assertFalse(as_date == as_datetime)
4651 self.assertFalse(as_datetime == as_date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004652 self.assertRaises(TypeError, lambda: as_date < as_datetime)
4653 self.assertRaises(TypeError, lambda: as_datetime < as_date)
4654 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
4655 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
4656 self.assertRaises(TypeError, lambda: as_date > as_datetime)
4657 self.assertRaises(TypeError, lambda: as_datetime > as_date)
4658 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
4659 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
4660
Raymond Hettinger15f44ab2016-08-30 10:47:49 -07004661 # Nevertheless, comparison should work with the base-class (date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004662 # projection if use of a date method is forced.
4663 self.assertEqual(as_date.__eq__(as_datetime), True)
4664 different_day = (as_date.day + 1) % 20 + 1
4665 as_different = as_datetime.replace(day= different_day)
4666 self.assertEqual(as_date.__eq__(as_different), False)
4667
4668 # And date should compare with other subclasses of date. If a
4669 # subclass wants to stop this, it's up to the subclass to do so.
4670 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
4671 self.assertEqual(as_date, date_sc)
4672 self.assertEqual(date_sc, as_date)
4673
4674 # Ditto for datetimes.
4675 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
4676 as_date.day, 0, 0, 0)
4677 self.assertEqual(as_datetime, datetime_sc)
4678 self.assertEqual(datetime_sc, as_datetime)
4679
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004680 def test_extra_attributes(self):
4681 for x in [date.today(),
4682 time(),
4683 datetime.utcnow(),
4684 timedelta(),
4685 tzinfo(),
4686 timezone(timedelta())]:
4687 with self.assertRaises(AttributeError):
4688 x.abc = 1
4689
4690 def test_check_arg_types(self):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004691 class Number:
4692 def __init__(self, value):
4693 self.value = value
4694 def __int__(self):
4695 return self.value
4696
4697 for xx in [decimal.Decimal(10),
4698 decimal.Decimal('10.9'),
4699 Number(10)]:
4700 self.assertEqual(datetime(10, 10, 10, 10, 10, 10, 10),
4701 datetime(xx, xx, xx, xx, xx, xx, xx))
4702
4703 with self.assertRaisesRegex(TypeError, '^an integer is required '
R David Murray44b548d2016-09-08 13:59:53 -04004704 r'\(got type str\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004705 datetime(10, 10, '10')
4706
4707 f10 = Number(10.9)
4708 with self.assertRaisesRegex(TypeError, '^__int__ returned non-int '
R David Murray44b548d2016-09-08 13:59:53 -04004709 r'\(type float\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004710 datetime(10, 10, f10)
4711
4712 class Float(float):
4713 pass
4714 s10 = Float(10.9)
4715 with self.assertRaisesRegex(TypeError, '^integer argument expected, '
4716 'got float$'):
4717 datetime(10, 10, s10)
4718
4719 with self.assertRaises(TypeError):
4720 datetime(10., 10, 10)
4721 with self.assertRaises(TypeError):
4722 datetime(10, 10., 10)
4723 with self.assertRaises(TypeError):
4724 datetime(10, 10, 10.)
4725 with self.assertRaises(TypeError):
4726 datetime(10, 10, 10, 10.)
4727 with self.assertRaises(TypeError):
4728 datetime(10, 10, 10, 10, 10.)
4729 with self.assertRaises(TypeError):
4730 datetime(10, 10, 10, 10, 10, 10.)
4731 with self.assertRaises(TypeError):
4732 datetime(10, 10, 10, 10, 10, 10, 10.)
4733
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004734#############################################################################
4735# Local Time Disambiguation
4736
4737# An experimental reimplementation of fromutc that respects the "fold" flag.
4738
4739class tzinfo2(tzinfo):
4740
4741 def fromutc(self, dt):
4742 "datetime in UTC -> datetime in local time."
4743
4744 if not isinstance(dt, datetime):
4745 raise TypeError("fromutc() requires a datetime argument")
4746 if dt.tzinfo is not self:
4747 raise ValueError("dt.tzinfo is not self")
4748 # Returned value satisfies
4749 # dt + ldt.utcoffset() = ldt
4750 off0 = dt.replace(fold=0).utcoffset()
4751 off1 = dt.replace(fold=1).utcoffset()
4752 if off0 is None or off1 is None or dt.dst() is None:
4753 raise ValueError
4754 if off0 == off1:
4755 ldt = dt + off0
4756 off1 = ldt.utcoffset()
4757 if off0 == off1:
4758 return ldt
4759 # Now, we discovered both possible offsets, so
4760 # we can just try four possible solutions:
4761 for off in [off0, off1]:
4762 ldt = dt + off
4763 if ldt.utcoffset() == off:
4764 return ldt
4765 ldt = ldt.replace(fold=1)
4766 if ldt.utcoffset() == off:
4767 return ldt
4768
4769 raise ValueError("No suitable local time found")
4770
4771# Reimplementing simplified US timezones to respect the "fold" flag:
4772
4773class USTimeZone2(tzinfo2):
4774
4775 def __init__(self, hours, reprname, stdname, dstname):
4776 self.stdoffset = timedelta(hours=hours)
4777 self.reprname = reprname
4778 self.stdname = stdname
4779 self.dstname = dstname
4780
4781 def __repr__(self):
4782 return self.reprname
4783
4784 def tzname(self, dt):
4785 if self.dst(dt):
4786 return self.dstname
4787 else:
4788 return self.stdname
4789
4790 def utcoffset(self, dt):
4791 return self.stdoffset + self.dst(dt)
4792
4793 def dst(self, dt):
4794 if dt is None or dt.tzinfo is None:
4795 # An exception instead may be sensible here, in one or more of
4796 # the cases.
4797 return ZERO
4798 assert dt.tzinfo is self
4799
4800 # Find first Sunday in April.
4801 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
4802 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
4803
4804 # Find last Sunday in October.
4805 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
4806 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
4807
4808 # Can't compare naive to aware objects, so strip the timezone from
4809 # dt first.
4810 dt = dt.replace(tzinfo=None)
4811 if start + HOUR <= dt < end:
4812 # DST is in effect.
4813 return HOUR
4814 elif end <= dt < end + HOUR:
4815 # Fold (an ambiguous hour): use dt.fold to disambiguate.
4816 return ZERO if dt.fold else HOUR
4817 elif start <= dt < start + HOUR:
4818 # Gap (a non-existent hour): reverse the fold rule.
4819 return HOUR if dt.fold else ZERO
4820 else:
4821 # DST is off.
4822 return ZERO
4823
4824Eastern2 = USTimeZone2(-5, "Eastern2", "EST", "EDT")
4825Central2 = USTimeZone2(-6, "Central2", "CST", "CDT")
4826Mountain2 = USTimeZone2(-7, "Mountain2", "MST", "MDT")
4827Pacific2 = USTimeZone2(-8, "Pacific2", "PST", "PDT")
4828
4829# Europe_Vilnius_1941 tzinfo implementation reproduces the following
4830# 1941 transition from Olson's tzdist:
4831#
4832# Zone NAME GMTOFF RULES FORMAT [UNTIL]
4833# ZoneEurope/Vilnius 1:00 - CET 1940 Aug 3
4834# 3:00 - MSK 1941 Jun 24
4835# 1:00 C-Eur CE%sT 1944 Aug
4836#
4837# $ zdump -v Europe/Vilnius | grep 1941
4838# Europe/Vilnius Mon Jun 23 20:59:59 1941 UTC = Mon Jun 23 23:59:59 1941 MSK isdst=0 gmtoff=10800
4839# Europe/Vilnius Mon Jun 23 21:00:00 1941 UTC = Mon Jun 23 23:00:00 1941 CEST isdst=1 gmtoff=7200
4840
4841class Europe_Vilnius_1941(tzinfo):
4842 def _utc_fold(self):
4843 return [datetime(1941, 6, 23, 21, tzinfo=self), # Mon Jun 23 21:00:00 1941 UTC
4844 datetime(1941, 6, 23, 22, tzinfo=self)] # Mon Jun 23 22:00:00 1941 UTC
4845
4846 def _loc_fold(self):
4847 return [datetime(1941, 6, 23, 23, tzinfo=self), # Mon Jun 23 23:00:00 1941 MSK / CEST
4848 datetime(1941, 6, 24, 0, tzinfo=self)] # Mon Jun 24 00:00:00 1941 CEST
4849
4850 def utcoffset(self, dt):
4851 fold_start, fold_stop = self._loc_fold()
4852 if dt < fold_start:
4853 return 3 * HOUR
4854 if dt < fold_stop:
4855 return (2 if dt.fold else 3) * HOUR
4856 # if dt >= fold_stop
4857 return 2 * HOUR
4858
4859 def dst(self, dt):
4860 fold_start, fold_stop = self._loc_fold()
4861 if dt < fold_start:
4862 return 0 * HOUR
4863 if dt < fold_stop:
4864 return (1 if dt.fold else 0) * HOUR
4865 # if dt >= fold_stop
4866 return 1 * HOUR
4867
4868 def tzname(self, dt):
4869 fold_start, fold_stop = self._loc_fold()
4870 if dt < fold_start:
4871 return 'MSK'
4872 if dt < fold_stop:
4873 return ('MSK', 'CEST')[dt.fold]
4874 # if dt >= fold_stop
4875 return 'CEST'
4876
4877 def fromutc(self, dt):
4878 assert dt.fold == 0
4879 assert dt.tzinfo is self
4880 if dt.year != 1941:
4881 raise NotImplementedError
4882 fold_start, fold_stop = self._utc_fold()
4883 if dt < fold_start:
4884 return dt + 3 * HOUR
4885 if dt < fold_stop:
4886 return (dt + 2 * HOUR).replace(fold=1)
4887 # if dt >= fold_stop
4888 return dt + 2 * HOUR
4889
4890
4891class TestLocalTimeDisambiguation(unittest.TestCase):
4892
4893 def test_vilnius_1941_fromutc(self):
4894 Vilnius = Europe_Vilnius_1941()
4895
4896 gdt = datetime(1941, 6, 23, 20, 59, 59, tzinfo=timezone.utc)
4897 ldt = gdt.astimezone(Vilnius)
4898 self.assertEqual(ldt.strftime("%c %Z%z"),
4899 'Mon Jun 23 23:59:59 1941 MSK+0300')
4900 self.assertEqual(ldt.fold, 0)
4901 self.assertFalse(ldt.dst())
4902
4903 gdt = datetime(1941, 6, 23, 21, tzinfo=timezone.utc)
4904 ldt = gdt.astimezone(Vilnius)
4905 self.assertEqual(ldt.strftime("%c %Z%z"),
4906 'Mon Jun 23 23:00:00 1941 CEST+0200')
4907 self.assertEqual(ldt.fold, 1)
4908 self.assertTrue(ldt.dst())
4909
4910 gdt = datetime(1941, 6, 23, 22, tzinfo=timezone.utc)
4911 ldt = gdt.astimezone(Vilnius)
4912 self.assertEqual(ldt.strftime("%c %Z%z"),
4913 'Tue Jun 24 00:00:00 1941 CEST+0200')
4914 self.assertEqual(ldt.fold, 0)
4915 self.assertTrue(ldt.dst())
4916
4917 def test_vilnius_1941_toutc(self):
4918 Vilnius = Europe_Vilnius_1941()
4919
4920 ldt = datetime(1941, 6, 23, 22, 59, 59, tzinfo=Vilnius)
4921 gdt = ldt.astimezone(timezone.utc)
4922 self.assertEqual(gdt.strftime("%c %Z"),
4923 'Mon Jun 23 19:59:59 1941 UTC')
4924
4925 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius)
4926 gdt = ldt.astimezone(timezone.utc)
4927 self.assertEqual(gdt.strftime("%c %Z"),
4928 'Mon Jun 23 20:59:59 1941 UTC')
4929
4930 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius, fold=1)
4931 gdt = ldt.astimezone(timezone.utc)
4932 self.assertEqual(gdt.strftime("%c %Z"),
4933 'Mon Jun 23 21:59:59 1941 UTC')
4934
4935 ldt = datetime(1941, 6, 24, 0, tzinfo=Vilnius)
4936 gdt = ldt.astimezone(timezone.utc)
4937 self.assertEqual(gdt.strftime("%c %Z"),
4938 'Mon Jun 23 22:00:00 1941 UTC')
4939
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004940 def test_constructors(self):
4941 t = time(0, fold=1)
4942 dt = datetime(1, 1, 1, fold=1)
4943 self.assertEqual(t.fold, 1)
4944 self.assertEqual(dt.fold, 1)
4945 with self.assertRaises(TypeError):
4946 time(0, 0, 0, 0, None, 0)
4947
4948 def test_member(self):
4949 dt = datetime(1, 1, 1, fold=1)
4950 t = dt.time()
4951 self.assertEqual(t.fold, 1)
4952 t = dt.timetz()
4953 self.assertEqual(t.fold, 1)
4954
4955 def test_replace(self):
4956 t = time(0)
4957 dt = datetime(1, 1, 1)
4958 self.assertEqual(t.replace(fold=1).fold, 1)
4959 self.assertEqual(dt.replace(fold=1).fold, 1)
4960 self.assertEqual(t.replace(fold=0).fold, 0)
4961 self.assertEqual(dt.replace(fold=0).fold, 0)
4962 # Check that replacement of other fields does not change "fold".
4963 t = t.replace(fold=1, tzinfo=Eastern)
4964 dt = dt.replace(fold=1, tzinfo=Eastern)
4965 self.assertEqual(t.replace(tzinfo=None).fold, 1)
4966 self.assertEqual(dt.replace(tzinfo=None).fold, 1)
Serhiy Storchaka314d6fc2017-03-31 22:48:16 +03004967 # Out of bounds.
4968 with self.assertRaises(ValueError):
4969 t.replace(fold=2)
4970 with self.assertRaises(ValueError):
4971 dt.replace(fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004972 # Check that fold is a keyword-only argument
4973 with self.assertRaises(TypeError):
4974 t.replace(1, 1, 1, None, 1)
4975 with self.assertRaises(TypeError):
4976 dt.replace(1, 1, 1, 1, 1, 1, 1, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04004977
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004978 def test_comparison(self):
4979 t = time(0)
4980 dt = datetime(1, 1, 1)
4981 self.assertEqual(t, t.replace(fold=1))
4982 self.assertEqual(dt, dt.replace(fold=1))
4983
4984 def test_hash(self):
4985 t = time(0)
4986 dt = datetime(1, 1, 1)
4987 self.assertEqual(hash(t), hash(t.replace(fold=1)))
4988 self.assertEqual(hash(dt), hash(dt.replace(fold=1)))
4989
4990 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4991 def test_fromtimestamp(self):
4992 s = 1414906200
4993 dt0 = datetime.fromtimestamp(s)
4994 dt1 = datetime.fromtimestamp(s + 3600)
4995 self.assertEqual(dt0.fold, 0)
4996 self.assertEqual(dt1.fold, 1)
4997
4998 @support.run_with_tz('Australia/Lord_Howe')
4999 def test_fromtimestamp_lord_howe(self):
5000 tm = _time.localtime(1.4e9)
5001 if _time.strftime('%Z%z', tm) != 'LHST+1030':
5002 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
5003 # $ TZ=Australia/Lord_Howe date -r 1428158700
5004 # Sun Apr 5 01:45:00 LHDT 2015
5005 # $ TZ=Australia/Lord_Howe date -r 1428160500
5006 # Sun Apr 5 01:45:00 LHST 2015
5007 s = 1428158700
5008 t0 = datetime.fromtimestamp(s)
5009 t1 = datetime.fromtimestamp(s + 1800)
5010 self.assertEqual(t0, t1)
5011 self.assertEqual(t0.fold, 0)
5012 self.assertEqual(t1.fold, 1)
5013
Ammar Askar96d1e692018-07-25 09:54:58 -07005014 def test_fromtimestamp_low_fold_detection(self):
5015 # Ensure that fold detection doesn't cause an
5016 # OSError for really low values, see bpo-29097
5017 self.assertEqual(datetime.fromtimestamp(0).fold, 0)
5018
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005019 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5020 def test_timestamp(self):
5021 dt0 = datetime(2014, 11, 2, 1, 30)
5022 dt1 = dt0.replace(fold=1)
5023 self.assertEqual(dt0.timestamp() + 3600,
5024 dt1.timestamp())
5025
5026 @support.run_with_tz('Australia/Lord_Howe')
5027 def test_timestamp_lord_howe(self):
5028 tm = _time.localtime(1.4e9)
5029 if _time.strftime('%Z%z', tm) != 'LHST+1030':
5030 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
5031 t = datetime(2015, 4, 5, 1, 45)
5032 s0 = t.replace(fold=0).timestamp()
5033 s1 = t.replace(fold=1).timestamp()
5034 self.assertEqual(s0 + 1800, s1)
5035
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005036 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5037 def test_astimezone(self):
5038 dt0 = datetime(2014, 11, 2, 1, 30)
5039 dt1 = dt0.replace(fold=1)
5040 # Convert both naive instances to aware.
5041 adt0 = dt0.astimezone()
5042 adt1 = dt1.astimezone()
5043 # Check that the first instance in DST zone and the second in STD
5044 self.assertEqual(adt0.tzname(), 'EDT')
5045 self.assertEqual(adt1.tzname(), 'EST')
5046 self.assertEqual(adt0 + HOUR, adt1)
5047 # Aware instances with fixed offset tzinfo's always have fold=0
5048 self.assertEqual(adt0.fold, 0)
5049 self.assertEqual(adt1.fold, 0)
5050
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005051 def test_pickle_fold(self):
5052 t = time(fold=1)
5053 dt = datetime(1, 1, 1, fold=1)
5054 for pickler, unpickler, proto in pickle_choices:
5055 for x in [t, dt]:
5056 s = pickler.dumps(x, proto)
5057 y = unpickler.loads(s)
5058 self.assertEqual(x, y)
5059 self.assertEqual((0 if proto < 4 else x.fold), y.fold)
5060
5061 def test_repr(self):
5062 t = time(fold=1)
5063 dt = datetime(1, 1, 1, fold=1)
5064 self.assertEqual(repr(t), 'datetime.time(0, 0, fold=1)')
5065 self.assertEqual(repr(dt),
5066 'datetime.datetime(1, 1, 1, 0, 0, fold=1)')
5067
5068 def test_dst(self):
5069 # Let's first establish that things work in regular times.
5070 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
5071 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
5072 self.assertEqual(dt_summer.dst(), HOUR)
5073 self.assertEqual(dt_winter.dst(), ZERO)
5074 # The disambiguation flag is ignored
5075 self.assertEqual(dt_summer.replace(fold=1).dst(), HOUR)
5076 self.assertEqual(dt_winter.replace(fold=1).dst(), ZERO)
5077
5078 # Pick local time in the fold.
5079 for minute in [0, 30, 59]:
5080 dt = datetime(2002, 10, 27, 1, minute, tzinfo=Eastern2)
5081 # With fold=0 (the default) it is in DST.
5082 self.assertEqual(dt.dst(), HOUR)
5083 # With fold=1 it is in STD.
5084 self.assertEqual(dt.replace(fold=1).dst(), ZERO)
5085
5086 # Pick local time in the gap.
5087 for minute in [0, 30, 59]:
5088 dt = datetime(2002, 4, 7, 2, minute, tzinfo=Eastern2)
5089 # With fold=0 (the default) it is in STD.
5090 self.assertEqual(dt.dst(), ZERO)
5091 # With fold=1 it is in DST.
5092 self.assertEqual(dt.replace(fold=1).dst(), HOUR)
5093
5094
5095 def test_utcoffset(self):
5096 # Let's first establish that things work in regular times.
5097 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
5098 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
5099 self.assertEqual(dt_summer.utcoffset(), -4 * HOUR)
5100 self.assertEqual(dt_winter.utcoffset(), -5 * HOUR)
5101 # The disambiguation flag is ignored
5102 self.assertEqual(dt_summer.replace(fold=1).utcoffset(), -4 * HOUR)
5103 self.assertEqual(dt_winter.replace(fold=1).utcoffset(), -5 * HOUR)
5104
5105 def test_fromutc(self):
5106 # Let's first establish that things work in regular times.
5107 u_summer = datetime(2002, 10, 27, 6, tzinfo=Eastern2) - timedelta.resolution
5108 u_winter = datetime(2002, 10, 27, 7, tzinfo=Eastern2)
5109 t_summer = Eastern2.fromutc(u_summer)
5110 t_winter = Eastern2.fromutc(u_winter)
5111 self.assertEqual(t_summer, u_summer - 4 * HOUR)
5112 self.assertEqual(t_winter, u_winter - 5 * HOUR)
5113 self.assertEqual(t_summer.fold, 0)
5114 self.assertEqual(t_winter.fold, 0)
5115
5116 # What happens in the fall-back fold?
5117 u = datetime(2002, 10, 27, 5, 30, tzinfo=Eastern2)
5118 t0 = Eastern2.fromutc(u)
5119 u += HOUR
5120 t1 = Eastern2.fromutc(u)
5121 self.assertEqual(t0, t1)
5122 self.assertEqual(t0.fold, 0)
5123 self.assertEqual(t1.fold, 1)
5124 # The tricky part is when u is in the local fold:
5125 u = datetime(2002, 10, 27, 1, 30, tzinfo=Eastern2)
5126 t = Eastern2.fromutc(u)
5127 self.assertEqual((t.day, t.hour), (26, 21))
5128 # .. or gets into the local fold after a standard time adjustment
5129 u = datetime(2002, 10, 27, 6, 30, tzinfo=Eastern2)
5130 t = Eastern2.fromutc(u)
5131 self.assertEqual((t.day, t.hour), (27, 1))
5132
5133 # What happens in the spring-forward gap?
5134 u = datetime(2002, 4, 7, 2, 0, tzinfo=Eastern2)
5135 t = Eastern2.fromutc(u)
5136 self.assertEqual((t.day, t.hour), (6, 21))
5137
5138 def test_mixed_compare_regular(self):
5139 t = datetime(2000, 1, 1, tzinfo=Eastern2)
5140 self.assertEqual(t, t.astimezone(timezone.utc))
5141 t = datetime(2000, 6, 1, tzinfo=Eastern2)
5142 self.assertEqual(t, t.astimezone(timezone.utc))
5143
5144 def test_mixed_compare_fold(self):
5145 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
5146 t_fold_utc = t_fold.astimezone(timezone.utc)
5147 self.assertNotEqual(t_fold, t_fold_utc)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04005148 self.assertNotEqual(t_fold_utc, t_fold)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005149
5150 def test_mixed_compare_gap(self):
5151 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
5152 t_gap_utc = t_gap.astimezone(timezone.utc)
5153 self.assertNotEqual(t_gap, t_gap_utc)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04005154 self.assertNotEqual(t_gap_utc, t_gap)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005155
5156 def test_hash_aware(self):
5157 t = datetime(2000, 1, 1, tzinfo=Eastern2)
5158 self.assertEqual(hash(t), hash(t.replace(fold=1)))
5159 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
5160 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
5161 self.assertEqual(hash(t_fold), hash(t_fold.replace(fold=1)))
5162 self.assertEqual(hash(t_gap), hash(t_gap.replace(fold=1)))
5163
5164SEC = timedelta(0, 1)
5165
5166def pairs(iterable):
5167 a, b = itertools.tee(iterable)
5168 next(b, None)
5169 return zip(a, b)
5170
5171class ZoneInfo(tzinfo):
5172 zoneroot = '/usr/share/zoneinfo'
5173 def __init__(self, ut, ti):
5174 """
5175
5176 :param ut: array
5177 Array of transition point timestamps
5178 :param ti: list
5179 A list of (offset, isdst, abbr) tuples
5180 :return: None
5181 """
5182 self.ut = ut
5183 self.ti = ti
5184 self.lt = self.invert(ut, ti)
5185
5186 @staticmethod
5187 def invert(ut, ti):
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04005188 lt = (array('q', ut), array('q', ut))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005189 if ut:
5190 offset = ti[0][0] // SEC
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04005191 lt[0][0] += offset
5192 lt[1][0] += offset
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005193 for i in range(1, len(ut)):
5194 lt[0][i] += ti[i-1][0] // SEC
5195 lt[1][i] += ti[i][0] // SEC
5196 return lt
5197
5198 @classmethod
5199 def fromfile(cls, fileobj):
5200 if fileobj.read(4).decode() != "TZif":
5201 raise ValueError("not a zoneinfo file")
5202 fileobj.seek(32)
5203 counts = array('i')
5204 counts.fromfile(fileobj, 3)
5205 if sys.byteorder != 'big':
5206 counts.byteswap()
5207
5208 ut = array('i')
5209 ut.fromfile(fileobj, counts[0])
5210 if sys.byteorder != 'big':
5211 ut.byteswap()
5212
5213 type_indices = array('B')
5214 type_indices.fromfile(fileobj, counts[0])
5215
5216 ttis = []
5217 for i in range(counts[1]):
5218 ttis.append(struct.unpack(">lbb", fileobj.read(6)))
5219
5220 abbrs = fileobj.read(counts[2])
5221
5222 # Convert ttis
5223 for i, (gmtoff, isdst, abbrind) in enumerate(ttis):
5224 abbr = abbrs[abbrind:abbrs.find(0, abbrind)].decode()
5225 ttis[i] = (timedelta(0, gmtoff), isdst, abbr)
5226
5227 ti = [None] * len(ut)
5228 for i, idx in enumerate(type_indices):
5229 ti[i] = ttis[idx]
5230
5231 self = cls(ut, ti)
5232
5233 return self
5234
5235 @classmethod
5236 def fromname(cls, name):
5237 path = os.path.join(cls.zoneroot, name)
5238 with open(path, 'rb') as f:
5239 return cls.fromfile(f)
5240
5241 EPOCHORDINAL = date(1970, 1, 1).toordinal()
5242
5243 def fromutc(self, dt):
5244 """datetime in UTC -> datetime in local time."""
5245
5246 if not isinstance(dt, datetime):
5247 raise TypeError("fromutc() requires a datetime argument")
5248 if dt.tzinfo is not self:
5249 raise ValueError("dt.tzinfo is not self")
5250
5251 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
5252 + dt.hour * 3600
5253 + dt.minute * 60
5254 + dt.second)
5255
5256 if timestamp < self.ut[1]:
5257 tti = self.ti[0]
5258 fold = 0
5259 else:
5260 idx = bisect.bisect_right(self.ut, timestamp)
5261 assert self.ut[idx-1] <= timestamp
5262 assert idx == len(self.ut) or timestamp < self.ut[idx]
5263 tti_prev, tti = self.ti[idx-2:idx]
5264 # Detect fold
5265 shift = tti_prev[0] - tti[0]
5266 fold = (shift > timedelta(0, timestamp - self.ut[idx-1]))
5267 dt += tti[0]
5268 if fold:
5269 return dt.replace(fold=1)
5270 else:
5271 return dt
5272
5273 def _find_ti(self, dt, i):
5274 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
5275 + dt.hour * 3600
5276 + dt.minute * 60
5277 + dt.second)
5278 lt = self.lt[dt.fold]
5279 idx = bisect.bisect_right(lt, timestamp)
5280
5281 return self.ti[max(0, idx - 1)][i]
5282
5283 def utcoffset(self, dt):
5284 return self._find_ti(dt, 0)
5285
5286 def dst(self, dt):
5287 isdst = self._find_ti(dt, 1)
5288 # XXX: We cannot accurately determine the "save" value,
5289 # so let's return 1h whenever DST is in effect. Since
5290 # we don't use dst() in fromutc(), it is unlikely that
5291 # it will be needed for anything more than bool(dst()).
5292 return ZERO if isdst else HOUR
5293
5294 def tzname(self, dt):
5295 return self._find_ti(dt, 2)
5296
5297 @classmethod
5298 def zonenames(cls, zonedir=None):
5299 if zonedir is None:
5300 zonedir = cls.zoneroot
Alexander Belopolsky1b8f26c2016-08-11 11:01:45 -04005301 zone_tab = os.path.join(zonedir, 'zone.tab')
5302 try:
5303 f = open(zone_tab)
5304 except OSError:
5305 return
5306 with f:
5307 for line in f:
5308 line = line.strip()
5309 if line and not line.startswith('#'):
5310 yield line.split()[2]
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005311
5312 @classmethod
5313 def stats(cls, start_year=1):
5314 count = gap_count = fold_count = zeros_count = 0
5315 min_gap = min_fold = timedelta.max
5316 max_gap = max_fold = ZERO
5317 min_gap_datetime = max_gap_datetime = datetime.min
5318 min_gap_zone = max_gap_zone = None
5319 min_fold_datetime = max_fold_datetime = datetime.min
5320 min_fold_zone = max_fold_zone = None
5321 stats_since = datetime(start_year, 1, 1) # Starting from 1970 eliminates a lot of noise
5322 for zonename in cls.zonenames():
5323 count += 1
5324 tz = cls.fromname(zonename)
5325 for dt, shift in tz.transitions():
5326 if dt < stats_since:
5327 continue
5328 if shift > ZERO:
5329 gap_count += 1
5330 if (shift, dt) > (max_gap, max_gap_datetime):
5331 max_gap = shift
5332 max_gap_zone = zonename
5333 max_gap_datetime = dt
5334 if (shift, datetime.max - dt) < (min_gap, datetime.max - min_gap_datetime):
5335 min_gap = shift
5336 min_gap_zone = zonename
5337 min_gap_datetime = dt
5338 elif shift < ZERO:
5339 fold_count += 1
5340 shift = -shift
5341 if (shift, dt) > (max_fold, max_fold_datetime):
5342 max_fold = shift
5343 max_fold_zone = zonename
5344 max_fold_datetime = dt
5345 if (shift, datetime.max - dt) < (min_fold, datetime.max - min_fold_datetime):
5346 min_fold = shift
5347 min_fold_zone = zonename
5348 min_fold_datetime = dt
5349 else:
5350 zeros_count += 1
5351 trans_counts = (gap_count, fold_count, zeros_count)
5352 print("Number of zones: %5d" % count)
5353 print("Number of transitions: %5d = %d (gaps) + %d (folds) + %d (zeros)" %
5354 ((sum(trans_counts),) + trans_counts))
5355 print("Min gap: %16s at %s in %s" % (min_gap, min_gap_datetime, min_gap_zone))
5356 print("Max gap: %16s at %s in %s" % (max_gap, max_gap_datetime, max_gap_zone))
5357 print("Min fold: %16s at %s in %s" % (min_fold, min_fold_datetime, min_fold_zone))
5358 print("Max fold: %16s at %s in %s" % (max_fold, max_fold_datetime, max_fold_zone))
5359
5360
5361 def transitions(self):
5362 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
5363 shift = ti[0] - prev_ti[0]
5364 yield datetime.utcfromtimestamp(t), shift
5365
5366 def nondst_folds(self):
5367 """Find all folds with the same value of isdst on both sides of the transition."""
5368 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
5369 shift = ti[0] - prev_ti[0]
5370 if shift < ZERO and ti[1] == prev_ti[1]:
5371 yield datetime.utcfromtimestamp(t), -shift, prev_ti[2], ti[2]
5372
5373 @classmethod
5374 def print_all_nondst_folds(cls, same_abbr=False, start_year=1):
5375 count = 0
5376 for zonename in cls.zonenames():
5377 tz = cls.fromname(zonename)
5378 for dt, shift, prev_abbr, abbr in tz.nondst_folds():
5379 if dt.year < start_year or same_abbr and prev_abbr != abbr:
5380 continue
5381 count += 1
5382 print("%3d) %-30s %s %10s %5s -> %s" %
5383 (count, zonename, dt, shift, prev_abbr, abbr))
5384
5385 def folds(self):
5386 for t, shift in self.transitions():
5387 if shift < ZERO:
5388 yield t, -shift
5389
5390 def gaps(self):
5391 for t, shift in self.transitions():
5392 if shift > ZERO:
5393 yield t, shift
5394
5395 def zeros(self):
5396 for t, shift in self.transitions():
5397 if not shift:
5398 yield t
5399
5400
5401class ZoneInfoTest(unittest.TestCase):
5402 zonename = 'America/New_York'
5403
5404 def setUp(self):
5405 if sys.platform == "win32":
5406 self.skipTest("Skipping zoneinfo tests on Windows")
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04005407 try:
5408 self.tz = ZoneInfo.fromname(self.zonename)
5409 except FileNotFoundError as err:
5410 self.skipTest("Skipping %s: %s" % (self.zonename, err))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005411
5412 def assertEquivDatetimes(self, a, b):
5413 self.assertEqual((a.replace(tzinfo=None), a.fold, id(a.tzinfo)),
5414 (b.replace(tzinfo=None), b.fold, id(b.tzinfo)))
5415
5416 def test_folds(self):
5417 tz = self.tz
5418 for dt, shift in tz.folds():
5419 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
5420 udt = dt + x
5421 ldt = tz.fromutc(udt.replace(tzinfo=tz))
5422 self.assertEqual(ldt.fold, 1)
5423 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
5424 self.assertEquivDatetimes(adt, ldt)
5425 utcoffset = ldt.utcoffset()
5426 self.assertEqual(ldt.replace(tzinfo=None), udt + utcoffset)
5427 # Round trip
5428 self.assertEquivDatetimes(ldt.astimezone(timezone.utc),
5429 udt.replace(tzinfo=timezone.utc))
5430
5431
5432 for x in [-timedelta.resolution, shift]:
5433 udt = dt + x
5434 udt = udt.replace(tzinfo=tz)
5435 ldt = tz.fromutc(udt)
5436 self.assertEqual(ldt.fold, 0)
5437
5438 def test_gaps(self):
5439 tz = self.tz
5440 for dt, shift in tz.gaps():
5441 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
5442 udt = dt + x
5443 udt = udt.replace(tzinfo=tz)
5444 ldt = tz.fromutc(udt)
5445 self.assertEqual(ldt.fold, 0)
5446 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
5447 self.assertEquivDatetimes(adt, ldt)
5448 utcoffset = ldt.utcoffset()
5449 self.assertEqual(ldt.replace(tzinfo=None), udt.replace(tzinfo=None) + utcoffset)
5450 # Create a local time inside the gap
5451 ldt = tz.fromutc(dt.replace(tzinfo=tz)) - shift + x
5452 self.assertLess(ldt.replace(fold=1).utcoffset(),
5453 ldt.replace(fold=0).utcoffset(),
5454 "At %s." % ldt)
5455
5456 for x in [-timedelta.resolution, shift]:
5457 udt = dt + x
5458 ldt = tz.fromutc(udt.replace(tzinfo=tz))
5459 self.assertEqual(ldt.fold, 0)
5460
5461 def test_system_transitions(self):
5462 if ('Riyadh8' in self.zonename or
5463 # From tzdata NEWS file:
5464 # The files solar87, solar88, and solar89 are no longer distributed.
5465 # They were a negative experiment - that is, a demonstration that
5466 # tz data can represent solar time only with some difficulty and error.
5467 # Their presence in the distribution caused confusion, as Riyadh
5468 # civil time was generally not solar time in those years.
5469 self.zonename.startswith('right/')):
5470 self.skipTest("Skipping %s" % self.zonename)
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04005471 tz = self.tz
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005472 TZ = os.environ.get('TZ')
5473 os.environ['TZ'] = self.zonename
5474 try:
5475 _time.tzset()
5476 for udt, shift in tz.transitions():
Alexander Belopolsky10c2dd22016-08-12 19:08:15 -04005477 if udt.year >= 2037:
5478 # System support for times around the end of 32-bit time_t
5479 # and later is flaky on many systems.
5480 break
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005481 s0 = (udt - datetime(1970, 1, 1)) // SEC
5482 ss = shift // SEC # shift seconds
5483 for x in [-40 * 3600, -20*3600, -1, 0,
5484 ss - 1, ss + 20 * 3600, ss + 40 * 3600]:
5485 s = s0 + x
5486 sdt = datetime.fromtimestamp(s)
5487 tzdt = datetime.fromtimestamp(s, tz).replace(tzinfo=None)
5488 self.assertEquivDatetimes(sdt, tzdt)
5489 s1 = sdt.timestamp()
5490 self.assertEqual(s, s1)
5491 if ss > 0: # gap
5492 # Create local time inside the gap
5493 dt = datetime.fromtimestamp(s0) - shift / 2
5494 ts0 = dt.timestamp()
5495 ts1 = dt.replace(fold=1).timestamp()
5496 self.assertEqual(ts0, s0 + ss / 2)
5497 self.assertEqual(ts1, s0 - ss / 2)
5498 finally:
5499 if TZ is None:
5500 del os.environ['TZ']
5501 else:
5502 os.environ['TZ'] = TZ
5503 _time.tzset()
5504
5505
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04005506class ZoneInfoCompleteTest(unittest.TestSuite):
5507 def __init__(self):
5508 tests = []
5509 if is_resource_enabled('tzdata'):
5510 for name in ZoneInfo.zonenames():
5511 Test = type('ZoneInfoTest[%s]' % name, (ZoneInfoTest,), {})
5512 Test.zonename = name
5513 for method in dir(Test):
5514 if method.startswith('test_'):
5515 tests.append(Test(method))
5516 super().__init__(tests)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005517
5518# Iran had a sub-minute UTC offset before 1946.
5519class IranTest(ZoneInfoTest):
Alexander Belopolsky07e2a0a2016-07-30 11:41:02 -04005520 zonename = 'Asia/Tehran'
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005521
Paul Ganssle04af5b12018-01-24 17:29:30 -05005522
5523class CapiTest(unittest.TestCase):
5524 def setUp(self):
5525 # Since the C API is not present in the _Pure tests, skip all tests
5526 if self.__class__.__name__.endswith('Pure'):
5527 self.skipTest('Not relevant in pure Python')
5528
5529 # This *must* be called, and it must be called first, so until either
5530 # restriction is loosened, we'll call it as part of test setup
5531 _testcapi.test_datetime_capi()
5532
5533 def test_utc_capi(self):
5534 for use_macro in (True, False):
5535 capi_utc = _testcapi.get_timezone_utc_capi(use_macro)
5536
5537 with self.subTest(use_macro=use_macro):
5538 self.assertIs(capi_utc, timezone.utc)
5539
5540 def test_timezones_capi(self):
5541 est_capi, est_macro, est_macro_nn = _testcapi.make_timezones_capi()
5542
5543 exp_named = timezone(timedelta(hours=-5), "EST")
5544 exp_unnamed = timezone(timedelta(hours=-5))
5545
5546 cases = [
5547 ('est_capi', est_capi, exp_named),
5548 ('est_macro', est_macro, exp_named),
5549 ('est_macro_nn', est_macro_nn, exp_unnamed)
5550 ]
5551
5552 for name, tz_act, tz_exp in cases:
5553 with self.subTest(name=name):
5554 self.assertEqual(tz_act, tz_exp)
5555
5556 dt1 = datetime(2000, 2, 4, tzinfo=tz_act)
5557 dt2 = datetime(2000, 2, 4, tzinfo=tz_exp)
5558
5559 self.assertEqual(dt1, dt2)
5560 self.assertEqual(dt1.tzname(), dt2.tzname())
5561
5562 dt_utc = datetime(2000, 2, 4, 5, tzinfo=timezone.utc)
5563
5564 self.assertEqual(dt1.astimezone(timezone.utc), dt_utc)
5565
Paul Gansslea049f572018-02-22 15:15:32 -05005566 def test_timezones_offset_zero(self):
5567 utc0, utc1, non_utc = _testcapi.get_timezones_offset_zero()
5568
5569 with self.subTest(testname="utc0"):
5570 self.assertIs(utc0, timezone.utc)
5571
5572 with self.subTest(testname="utc1"):
5573 self.assertIs(utc1, timezone.utc)
5574
5575 with self.subTest(testname="non_utc"):
5576 self.assertIsNot(non_utc, timezone.utc)
5577
5578 non_utc_exp = timezone(timedelta(hours=0), "")
5579
5580 self.assertEqual(non_utc, non_utc_exp)
5581
5582 dt1 = datetime(2000, 2, 4, tzinfo=non_utc)
5583 dt2 = datetime(2000, 2, 4, tzinfo=non_utc_exp)
5584
5585 self.assertEqual(dt1, dt2)
5586 self.assertEqual(dt1.tzname(), dt2.tzname())
5587
Paul Ganssle04af5b12018-01-24 17:29:30 -05005588 def test_check_date(self):
5589 class DateSubclass(date):
5590 pass
5591
5592 d = date(2011, 1, 1)
5593 ds = DateSubclass(2011, 1, 1)
5594 dt = datetime(2011, 1, 1)
5595
5596 is_date = _testcapi.datetime_check_date
5597
5598 # Check the ones that should be valid
5599 self.assertTrue(is_date(d))
5600 self.assertTrue(is_date(dt))
5601 self.assertTrue(is_date(ds))
5602 self.assertTrue(is_date(d, True))
5603
5604 # Check that the subclasses do not match exactly
5605 self.assertFalse(is_date(dt, True))
5606 self.assertFalse(is_date(ds, True))
5607
5608 # Check that various other things are not dates at all
5609 args = [tuple(), list(), 1, '2011-01-01',
5610 timedelta(1), timezone.utc, time(12, 00)]
5611 for arg in args:
5612 for exact in (True, False):
5613 with self.subTest(arg=arg, exact=exact):
5614 self.assertFalse(is_date(arg, exact))
5615
5616 def test_check_time(self):
5617 class TimeSubclass(time):
5618 pass
5619
5620 t = time(12, 30)
5621 ts = TimeSubclass(12, 30)
5622
5623 is_time = _testcapi.datetime_check_time
5624
5625 # Check the ones that should be valid
5626 self.assertTrue(is_time(t))
5627 self.assertTrue(is_time(ts))
5628 self.assertTrue(is_time(t, True))
5629
5630 # Check that the subclass does not match exactly
5631 self.assertFalse(is_time(ts, True))
5632
5633 # Check that various other things are not times
5634 args = [tuple(), list(), 1, '2011-01-01',
5635 timedelta(1), timezone.utc, date(2011, 1, 1)]
5636
5637 for arg in args:
5638 for exact in (True, False):
5639 with self.subTest(arg=arg, exact=exact):
5640 self.assertFalse(is_time(arg, exact))
5641
5642 def test_check_datetime(self):
5643 class DateTimeSubclass(datetime):
5644 pass
5645
5646 dt = datetime(2011, 1, 1, 12, 30)
5647 dts = DateTimeSubclass(2011, 1, 1, 12, 30)
5648
5649 is_datetime = _testcapi.datetime_check_datetime
5650
5651 # Check the ones that should be valid
5652 self.assertTrue(is_datetime(dt))
5653 self.assertTrue(is_datetime(dts))
5654 self.assertTrue(is_datetime(dt, True))
5655
5656 # Check that the subclass does not match exactly
5657 self.assertFalse(is_datetime(dts, True))
5658
5659 # Check that various other things are not datetimes
5660 args = [tuple(), list(), 1, '2011-01-01',
5661 timedelta(1), timezone.utc, date(2011, 1, 1)]
5662
5663 for arg in args:
5664 for exact in (True, False):
5665 with self.subTest(arg=arg, exact=exact):
5666 self.assertFalse(is_datetime(arg, exact))
5667
5668 def test_check_delta(self):
5669 class TimeDeltaSubclass(timedelta):
5670 pass
5671
5672 td = timedelta(1)
5673 tds = TimeDeltaSubclass(1)
5674
5675 is_timedelta = _testcapi.datetime_check_delta
5676
5677 # Check the ones that should be valid
5678 self.assertTrue(is_timedelta(td))
5679 self.assertTrue(is_timedelta(tds))
5680 self.assertTrue(is_timedelta(td, True))
5681
5682 # Check that the subclass does not match exactly
5683 self.assertFalse(is_timedelta(tds, True))
5684
5685 # Check that various other things are not timedeltas
5686 args = [tuple(), list(), 1, '2011-01-01',
5687 timezone.utc, date(2011, 1, 1), datetime(2011, 1, 1)]
5688
5689 for arg in args:
5690 for exact in (True, False):
5691 with self.subTest(arg=arg, exact=exact):
5692 self.assertFalse(is_timedelta(arg, exact))
5693
5694 def test_check_tzinfo(self):
5695 class TZInfoSubclass(tzinfo):
5696 pass
5697
5698 tzi = tzinfo()
5699 tzis = TZInfoSubclass()
5700 tz = timezone(timedelta(hours=-5))
5701
5702 is_tzinfo = _testcapi.datetime_check_tzinfo
5703
5704 # Check the ones that should be valid
5705 self.assertTrue(is_tzinfo(tzi))
5706 self.assertTrue(is_tzinfo(tz))
5707 self.assertTrue(is_tzinfo(tzis))
5708 self.assertTrue(is_tzinfo(tzi, True))
5709
5710 # Check that the subclasses do not match exactly
5711 self.assertFalse(is_tzinfo(tz, True))
5712 self.assertFalse(is_tzinfo(tzis, True))
5713
5714 # Check that various other things are not tzinfos
5715 args = [tuple(), list(), 1, '2011-01-01',
5716 date(2011, 1, 1), datetime(2011, 1, 1)]
5717
5718 for arg in args:
5719 for exact in (True, False):
5720 with self.subTest(arg=arg, exact=exact):
5721 self.assertFalse(is_tzinfo(arg, exact))
5722
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04005723def load_tests(loader, standard_tests, pattern):
5724 standard_tests.addTest(ZoneInfoCompleteTest())
5725 return standard_tests
5726
5727
Alexander Belopolskycf86e362010-07-23 19:25:47 +00005728if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -05005729 unittest.main()