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