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