blob: 4edfb42d35511ef2256d840d3bb84da7c9c61bd7 [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
34# Needed by test_datetime
35import _strptime
36#
37
38
39pickle_choices = [(pickle, pickle, proto)
40 for proto in range(pickle.HIGHEST_PROTOCOL + 1)]
41assert len(pickle_choices) == pickle.HIGHEST_PROTOCOL + 1
42
43# An arbitrary collection of objects of non-datetime types, for testing
44# mixed-type comparisons.
45OTHERSTUFF = (10, 34.5, "abc", {}, [], ())
46
47
48# XXX Copied from test_float.
49INF = float("inf")
50NAN = float("nan")
51
Alexander Belopolskycf86e362010-07-23 19:25:47 +000052
53#############################################################################
54# module tests
55
56class TestModule(unittest.TestCase):
57
58 def test_constants(self):
59 datetime = datetime_module
60 self.assertEqual(datetime.MINYEAR, 1)
61 self.assertEqual(datetime.MAXYEAR, 9999)
62
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -040063 def test_name_cleanup(self):
Utkarsh Upadhyay287c5592017-07-21 02:14:54 +020064 if '_Pure' in self.__class__.__name__:
65 self.skipTest('Only run for Fast C implementation')
66
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -040067 datetime = datetime_module
68 names = set(name for name in dir(datetime)
69 if not name.startswith('__') and not name.endswith('__'))
70 allowed = set(['MAXYEAR', 'MINYEAR', 'date', 'datetime',
71 'datetime_CAPI', 'time', 'timedelta', 'timezone',
72 'tzinfo'])
73 self.assertEqual(names - allowed, set([]))
74
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -050075 def test_divide_and_round(self):
Utkarsh Upadhyay287c5592017-07-21 02:14:54 +020076 if '_Fast' in self.__class__.__name__:
77 self.skipTest('Only run for Pure Python implementation')
78
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -050079 dar = datetime_module._divide_and_round
80
81 self.assertEqual(dar(-10, -3), 3)
82 self.assertEqual(dar(5, -2), -2)
83
84 # four cases: (2 signs of a) x (2 signs of b)
85 self.assertEqual(dar(7, 3), 2)
86 self.assertEqual(dar(-7, 3), -2)
87 self.assertEqual(dar(7, -3), -2)
88 self.assertEqual(dar(-7, -3), 2)
89
90 # ties to even - eight cases:
91 # (2 signs of a) x (2 signs of b) x (even / odd quotient)
92 self.assertEqual(dar(10, 4), 2)
93 self.assertEqual(dar(-10, 4), -2)
94 self.assertEqual(dar(10, -4), -2)
95 self.assertEqual(dar(-10, -4), 2)
96
97 self.assertEqual(dar(6, 4), 2)
98 self.assertEqual(dar(-6, 4), -2)
99 self.assertEqual(dar(6, -4), -2)
100 self.assertEqual(dar(-6, -4), 2)
101
102
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000103#############################################################################
104# tzinfo tests
105
106class FixedOffset(tzinfo):
107
108 def __init__(self, offset, name, dstoffset=42):
109 if isinstance(offset, int):
110 offset = timedelta(minutes=offset)
111 if isinstance(dstoffset, int):
112 dstoffset = timedelta(minutes=dstoffset)
113 self.__offset = offset
114 self.__name = name
115 self.__dstoffset = dstoffset
116 def __repr__(self):
117 return self.__name.lower()
118 def utcoffset(self, dt):
119 return self.__offset
120 def tzname(self, dt):
121 return self.__name
122 def dst(self, dt):
123 return self.__dstoffset
124
125class PicklableFixedOffset(FixedOffset):
126
127 def __init__(self, offset=None, name=None, dstoffset=None):
128 FixedOffset.__init__(self, offset, name, dstoffset)
129
Berker Peksage3385b42016-03-19 13:16:32 +0200130 def __getstate__(self):
131 return self.__dict__
132
Raymond Hettinger5a2146a2014-07-25 14:59:48 -0700133class _TZInfo(tzinfo):
134 def utcoffset(self, datetime_module):
135 return random.random()
136
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000137class TestTZInfo(unittest.TestCase):
138
Raymond Hettinger5a2146a2014-07-25 14:59:48 -0700139 def test_refcnt_crash_bug_22044(self):
140 tz1 = _TZInfo()
141 dt1 = datetime(2014, 7, 21, 11, 32, 3, 0, tz1)
142 with self.assertRaises(TypeError):
143 dt1.utcoffset()
144
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000145 def test_non_abstractness(self):
146 # In order to allow subclasses to get pickled, the C implementation
147 # wasn't able to get away with having __init__ raise
148 # NotImplementedError.
149 useless = tzinfo()
150 dt = datetime.max
151 self.assertRaises(NotImplementedError, useless.tzname, dt)
152 self.assertRaises(NotImplementedError, useless.utcoffset, dt)
153 self.assertRaises(NotImplementedError, useless.dst, dt)
154
155 def test_subclass_must_override(self):
156 class NotEnough(tzinfo):
157 def __init__(self, offset, name):
158 self.__offset = offset
159 self.__name = name
160 self.assertTrue(issubclass(NotEnough, tzinfo))
161 ne = NotEnough(3, "NotByALongShot")
162 self.assertIsInstance(ne, tzinfo)
163
164 dt = datetime.now()
165 self.assertRaises(NotImplementedError, ne.tzname, dt)
166 self.assertRaises(NotImplementedError, ne.utcoffset, dt)
167 self.assertRaises(NotImplementedError, ne.dst, dt)
168
169 def test_normal(self):
170 fo = FixedOffset(3, "Three")
171 self.assertIsInstance(fo, tzinfo)
172 for dt in datetime.now(), None:
173 self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3))
174 self.assertEqual(fo.tzname(dt), "Three")
175 self.assertEqual(fo.dst(dt), timedelta(minutes=42))
176
177 def test_pickling_base(self):
178 # There's no point to pickling tzinfo objects on their own (they
179 # carry no data), but they need to be picklable anyway else
180 # concrete subclasses can't be pickled.
181 orig = tzinfo.__new__(tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200182 self.assertIs(type(orig), tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000183 for pickler, unpickler, proto in pickle_choices:
184 green = pickler.dumps(orig, proto)
185 derived = unpickler.loads(green)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200186 self.assertIs(type(derived), tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000187
188 def test_pickling_subclass(self):
189 # Make sure we can pickle/unpickle an instance of a subclass.
190 offset = timedelta(minutes=-300)
191 for otype, args in [
192 (PicklableFixedOffset, (offset, 'cookie')),
193 (timezone, (offset,)),
194 (timezone, (offset, "EST"))]:
195 orig = otype(*args)
196 oname = orig.tzname(None)
197 self.assertIsInstance(orig, tzinfo)
198 self.assertIs(type(orig), otype)
199 self.assertEqual(orig.utcoffset(None), offset)
200 self.assertEqual(orig.tzname(None), oname)
201 for pickler, unpickler, proto in pickle_choices:
202 green = pickler.dumps(orig, proto)
203 derived = unpickler.loads(green)
204 self.assertIsInstance(derived, tzinfo)
205 self.assertIs(type(derived), otype)
206 self.assertEqual(derived.utcoffset(None), offset)
207 self.assertEqual(derived.tzname(None), oname)
208
Alexander Belopolskyc79447b2015-09-27 21:41:55 -0400209 def test_issue23600(self):
210 DSTDIFF = DSTOFFSET = timedelta(hours=1)
211
212 class UKSummerTime(tzinfo):
213 """Simple time zone which pretends to always be in summer time, since
214 that's what shows the failure.
215 """
216
217 def utcoffset(self, dt):
218 return DSTOFFSET
219
220 def dst(self, dt):
221 return DSTDIFF
222
223 def tzname(self, dt):
224 return 'UKSummerTime'
225
226 tz = UKSummerTime()
227 u = datetime(2014, 4, 26, 12, 1, tzinfo=tz)
228 t = tz.fromutc(u)
229 self.assertEqual(t - t.utcoffset(), u)
230
231
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000232class TestTimeZone(unittest.TestCase):
233
234 def setUp(self):
235 self.ACDT = timezone(timedelta(hours=9.5), 'ACDT')
236 self.EST = timezone(-timedelta(hours=5), 'EST')
237 self.DT = datetime(2010, 1, 1)
238
239 def test_str(self):
240 for tz in [self.ACDT, self.EST, timezone.utc,
241 timezone.min, timezone.max]:
242 self.assertEqual(str(tz), tz.tzname(None))
243
244 def test_repr(self):
245 datetime = datetime_module
246 for tz in [self.ACDT, self.EST, timezone.utc,
247 timezone.min, timezone.max]:
248 # test round-trip
249 tzrep = repr(tz)
250 self.assertEqual(tz, eval(tzrep))
251
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000252 def test_class_members(self):
253 limit = timedelta(hours=23, minutes=59)
254 self.assertEqual(timezone.utc.utcoffset(None), ZERO)
255 self.assertEqual(timezone.min.utcoffset(None), -limit)
256 self.assertEqual(timezone.max.utcoffset(None), limit)
257
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000258 def test_constructor(self):
Alexander Belopolsky1bcbaab2010-10-14 17:03:51 +0000259 self.assertIs(timezone.utc, timezone(timedelta(0)))
260 self.assertIsNot(timezone.utc, timezone(timedelta(0), 'UTC'))
261 self.assertEqual(timezone.utc, timezone(timedelta(0), 'UTC'))
Alexander Belopolsky018d3532017-07-31 10:26:50 -0400262 for subminute in [timedelta(microseconds=1), timedelta(seconds=1)]:
263 tz = timezone(subminute)
264 self.assertNotEqual(tz.utcoffset(None) % timedelta(minutes=1), 0)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000265 # invalid offsets
Alexander Belopolsky018d3532017-07-31 10:26:50 -0400266 for invalid in [timedelta(1, 1), timedelta(1)]:
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000267 self.assertRaises(ValueError, timezone, invalid)
268 self.assertRaises(ValueError, timezone, -invalid)
269
270 with self.assertRaises(TypeError): timezone(None)
271 with self.assertRaises(TypeError): timezone(42)
272 with self.assertRaises(TypeError): timezone(ZERO, None)
273 with self.assertRaises(TypeError): timezone(ZERO, 42)
274 with self.assertRaises(TypeError): timezone(ZERO, 'ABC', 'extra')
275
276 def test_inheritance(self):
277 self.assertIsInstance(timezone.utc, tzinfo)
278 self.assertIsInstance(self.EST, tzinfo)
279
280 def test_utcoffset(self):
281 dummy = self.DT
282 for h in [0, 1.5, 12]:
283 offset = h * HOUR
284 self.assertEqual(offset, timezone(offset).utcoffset(dummy))
285 self.assertEqual(-offset, timezone(-offset).utcoffset(dummy))
286
287 with self.assertRaises(TypeError): self.EST.utcoffset('')
288 with self.assertRaises(TypeError): self.EST.utcoffset(5)
289
290
291 def test_dst(self):
Raymond Hettinger7beae8a2011-01-06 05:34:17 +0000292 self.assertIsNone(timezone.utc.dst(self.DT))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000293
294 with self.assertRaises(TypeError): self.EST.dst('')
295 with self.assertRaises(TypeError): self.EST.dst(5)
296
297 def test_tzname(self):
Alexander Belopolsky7827a5b2015-09-06 13:07:21 -0400298 self.assertEqual('UTC', timezone.utc.tzname(None))
299 self.assertEqual('UTC', timezone(ZERO).tzname(None))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000300 self.assertEqual('UTC-05:00', timezone(-5 * HOUR).tzname(None))
301 self.assertEqual('UTC+09:30', timezone(9.5 * HOUR).tzname(None))
302 self.assertEqual('UTC-00:01', timezone(timedelta(minutes=-1)).tzname(None))
303 self.assertEqual('XYZ', timezone(-5 * HOUR, 'XYZ').tzname(None))
304
Alexander Belopolsky018d3532017-07-31 10:26:50 -0400305 # Sub-minute offsets:
306 self.assertEqual('UTC+01:06:40', timezone(timedelta(0, 4000)).tzname(None))
307 self.assertEqual('UTC-01:06:40',
308 timezone(-timedelta(0, 4000)).tzname(None))
309 self.assertEqual('UTC+01:06:40.000001',
310 timezone(timedelta(0, 4000, 1)).tzname(None))
311 self.assertEqual('UTC-01:06:40.000001',
312 timezone(-timedelta(0, 4000, 1)).tzname(None))
313
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000314 with self.assertRaises(TypeError): self.EST.tzname('')
315 with self.assertRaises(TypeError): self.EST.tzname(5)
316
317 def test_fromutc(self):
318 with self.assertRaises(ValueError):
319 timezone.utc.fromutc(self.DT)
320 with self.assertRaises(TypeError):
321 timezone.utc.fromutc('not datetime')
322 for tz in [self.EST, self.ACDT, Eastern]:
323 utctime = self.DT.replace(tzinfo=tz)
324 local = tz.fromutc(utctime)
325 self.assertEqual(local - utctime, tz.utcoffset(local))
326 self.assertEqual(local,
327 self.DT.replace(tzinfo=timezone.utc))
328
329 def test_comparison(self):
330 self.assertNotEqual(timezone(ZERO), timezone(HOUR))
331 self.assertEqual(timezone(HOUR), timezone(HOUR))
332 self.assertEqual(timezone(-5 * HOUR), timezone(-5 * HOUR, 'EST'))
333 with self.assertRaises(TypeError): timezone(ZERO) < timezone(ZERO)
334 self.assertIn(timezone(ZERO), {timezone(ZERO)})
Georg Brandl0085a242012-09-22 09:23:12 +0200335 self.assertTrue(timezone(ZERO) != None)
336 self.assertFalse(timezone(ZERO) == None)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000337
338 def test_aware_datetime(self):
339 # test that timezone instances can be used by datetime
340 t = datetime(1, 1, 1)
341 for tz in [timezone.min, timezone.max, timezone.utc]:
342 self.assertEqual(tz.tzname(t),
343 t.replace(tzinfo=tz).tzname())
344 self.assertEqual(tz.utcoffset(t),
345 t.replace(tzinfo=tz).utcoffset())
346 self.assertEqual(tz.dst(t),
347 t.replace(tzinfo=tz).dst())
348
Serhiy Storchakae28209f2015-11-16 11:12:58 +0200349 def test_pickle(self):
350 for tz in self.ACDT, self.EST, timezone.min, timezone.max:
351 for pickler, unpickler, proto in pickle_choices:
352 tz_copy = unpickler.loads(pickler.dumps(tz, proto))
353 self.assertEqual(tz_copy, tz)
354 tz = timezone.utc
355 for pickler, unpickler, proto in pickle_choices:
356 tz_copy = unpickler.loads(pickler.dumps(tz, proto))
357 self.assertIs(tz_copy, tz)
358
359 def test_copy(self):
360 for tz in self.ACDT, self.EST, timezone.min, timezone.max:
361 tz_copy = copy.copy(tz)
362 self.assertEqual(tz_copy, tz)
363 tz = timezone.utc
364 tz_copy = copy.copy(tz)
365 self.assertIs(tz_copy, tz)
366
367 def test_deepcopy(self):
368 for tz in self.ACDT, self.EST, timezone.min, timezone.max:
369 tz_copy = copy.deepcopy(tz)
370 self.assertEqual(tz_copy, tz)
371 tz = timezone.utc
372 tz_copy = copy.deepcopy(tz)
373 self.assertIs(tz_copy, tz)
374
375
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000376#############################################################################
Ezio Melotti85a86292013-08-17 16:57:41 +0300377# Base class for testing a particular aspect of timedelta, time, date and
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000378# datetime comparisons.
379
380class HarmlessMixedComparison:
381 # Test that __eq__ and __ne__ don't complain for mixed-type comparisons.
382
383 # Subclasses must define 'theclass', and theclass(1, 1, 1) must be a
384 # legit constructor.
385
386 def test_harmless_mixed_comparison(self):
387 me = self.theclass(1, 1, 1)
388
389 self.assertFalse(me == ())
390 self.assertTrue(me != ())
391 self.assertFalse(() == me)
392 self.assertTrue(() != me)
393
394 self.assertIn(me, [1, 20, [], me])
395 self.assertIn([], [me, 1, 20, []])
396
397 def test_harmful_mixed_comparison(self):
398 me = self.theclass(1, 1, 1)
399
400 self.assertRaises(TypeError, lambda: me < ())
401 self.assertRaises(TypeError, lambda: me <= ())
402 self.assertRaises(TypeError, lambda: me > ())
403 self.assertRaises(TypeError, lambda: me >= ())
404
405 self.assertRaises(TypeError, lambda: () < me)
406 self.assertRaises(TypeError, lambda: () <= me)
407 self.assertRaises(TypeError, lambda: () > me)
408 self.assertRaises(TypeError, lambda: () >= me)
409
410#############################################################################
411# timedelta tests
412
413class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
414
415 theclass = timedelta
416
417 def test_constructor(self):
418 eq = self.assertEqual
419 td = timedelta
420
421 # Check keyword args to constructor
422 eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0,
423 milliseconds=0, microseconds=0))
424 eq(td(1), td(days=1))
425 eq(td(0, 1), td(seconds=1))
426 eq(td(0, 0, 1), td(microseconds=1))
427 eq(td(weeks=1), td(days=7))
428 eq(td(days=1), td(hours=24))
429 eq(td(hours=1), td(minutes=60))
430 eq(td(minutes=1), td(seconds=60))
431 eq(td(seconds=1), td(milliseconds=1000))
432 eq(td(milliseconds=1), td(microseconds=1000))
433
434 # Check float args to constructor
435 eq(td(weeks=1.0/7), td(days=1))
436 eq(td(days=1.0/24), td(hours=1))
437 eq(td(hours=1.0/60), td(minutes=1))
438 eq(td(minutes=1.0/60), td(seconds=1))
439 eq(td(seconds=0.001), td(milliseconds=1))
440 eq(td(milliseconds=0.001), td(microseconds=1))
441
442 def test_computations(self):
443 eq = self.assertEqual
444 td = timedelta
445
446 a = td(7) # One week
447 b = td(0, 60) # One minute
448 c = td(0, 0, 1000) # One millisecond
449 eq(a+b+c, td(7, 60, 1000))
450 eq(a-b, td(6, 24*3600 - 60))
451 eq(b.__rsub__(a), td(6, 24*3600 - 60))
452 eq(-a, td(-7))
453 eq(+a, td(7))
454 eq(-b, td(-1, 24*3600 - 60))
455 eq(-c, td(-1, 24*3600 - 1, 999000))
456 eq(abs(a), a)
457 eq(abs(-a), a)
458 eq(td(6, 24*3600), a)
459 eq(td(0, 0, 60*1000000), b)
460 eq(a*10, td(70))
461 eq(a*10, 10*a)
462 eq(a*10, 10*a)
463 eq(b*10, td(0, 600))
464 eq(10*b, td(0, 600))
465 eq(b*10, td(0, 600))
466 eq(c*10, td(0, 0, 10000))
467 eq(10*c, td(0, 0, 10000))
468 eq(c*10, td(0, 0, 10000))
469 eq(a*-1, -a)
470 eq(b*-2, -b-b)
471 eq(c*-2, -c+-c)
472 eq(b*(60*24), (b*60)*24)
473 eq(b*(60*24), (60*b)*24)
474 eq(c*1000, td(0, 1))
475 eq(1000*c, td(0, 1))
476 eq(a//7, td(1))
477 eq(b//10, td(0, 6))
478 eq(c//1000, td(0, 0, 1))
479 eq(a//10, td(0, 7*24*360))
480 eq(a//3600000, td(0, 0, 7*24*1000))
481 eq(a/0.5, td(14))
482 eq(b/0.5, td(0, 120))
483 eq(a/7, td(1))
484 eq(b/10, td(0, 6))
485 eq(c/1000, td(0, 0, 1))
486 eq(a/10, td(0, 7*24*360))
487 eq(a/3600000, td(0, 0, 7*24*1000))
488
489 # Multiplication by float
490 us = td(microseconds=1)
491 eq((3*us) * 0.5, 2*us)
492 eq((5*us) * 0.5, 2*us)
493 eq(0.5 * (3*us), 2*us)
494 eq(0.5 * (5*us), 2*us)
495 eq((-3*us) * 0.5, -2*us)
496 eq((-5*us) * 0.5, -2*us)
497
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -0500498 # Issue #23521
499 eq(td(seconds=1) * 0.123456, td(microseconds=123456))
500 eq(td(seconds=1) * 0.6112295, td(microseconds=611229))
501
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000502 # Division by int and float
503 eq((3*us) / 2, 2*us)
504 eq((5*us) / 2, 2*us)
505 eq((-3*us) / 2.0, -2*us)
506 eq((-5*us) / 2.0, -2*us)
507 eq((3*us) / -2, -2*us)
508 eq((5*us) / -2, -2*us)
509 eq((3*us) / -2.0, -2*us)
510 eq((5*us) / -2.0, -2*us)
511 for i in range(-10, 10):
512 eq((i*us/3)//us, round(i/3))
513 for i in range(-10, 10):
514 eq((i*us/-3)//us, round(i/-3))
515
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -0500516 # Issue #23521
517 eq(td(seconds=1) / (1 / 0.6112295), td(microseconds=611229))
518
Alexander Belopolskyb6f5ec72011-04-05 20:07:38 -0400519 # Issue #11576
520 eq(td(999999999, 86399, 999999) - td(999999999, 86399, 999998),
521 td(0, 0, 1))
522 eq(td(999999999, 1, 1) - td(999999999, 1, 0),
523 td(0, 0, 1))
524
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000525 def test_disallowed_computations(self):
526 a = timedelta(42)
527
528 # Add/sub ints or floats should be illegal
529 for i in 1, 1.0:
530 self.assertRaises(TypeError, lambda: a+i)
531 self.assertRaises(TypeError, lambda: a-i)
532 self.assertRaises(TypeError, lambda: i+a)
533 self.assertRaises(TypeError, lambda: i-a)
534
535 # Division of int by timedelta doesn't make sense.
536 # Division by zero doesn't make sense.
537 zero = 0
538 self.assertRaises(TypeError, lambda: zero // a)
539 self.assertRaises(ZeroDivisionError, lambda: a // zero)
540 self.assertRaises(ZeroDivisionError, lambda: a / zero)
541 self.assertRaises(ZeroDivisionError, lambda: a / 0.0)
542 self.assertRaises(TypeError, lambda: a / '')
543
Eric Smith3ab08ca2010-12-04 15:17:38 +0000544 @support.requires_IEEE_754
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000545 def test_disallowed_special(self):
546 a = timedelta(42)
547 self.assertRaises(ValueError, a.__mul__, NAN)
548 self.assertRaises(ValueError, a.__truediv__, NAN)
549
550 def test_basic_attributes(self):
551 days, seconds, us = 1, 7, 31
552 td = timedelta(days, seconds, us)
553 self.assertEqual(td.days, days)
554 self.assertEqual(td.seconds, seconds)
555 self.assertEqual(td.microseconds, us)
556
557 def test_total_seconds(self):
558 td = timedelta(days=365)
559 self.assertEqual(td.total_seconds(), 31536000.0)
560 for total_seconds in [123456.789012, -123456.789012, 0.123456, 0, 1e6]:
561 td = timedelta(seconds=total_seconds)
562 self.assertEqual(td.total_seconds(), total_seconds)
563 # Issue8644: Test that td.total_seconds() has the same
564 # accuracy as td / timedelta(seconds=1).
565 for ms in [-1, -2, -123]:
566 td = timedelta(microseconds=ms)
567 self.assertEqual(td.total_seconds(), td / timedelta(seconds=1))
568
569 def test_carries(self):
570 t1 = timedelta(days=100,
571 weeks=-7,
572 hours=-24*(100-49),
573 minutes=-3,
574 seconds=12,
575 microseconds=(3*60 - 12) * 1e6 + 1)
576 t2 = timedelta(microseconds=1)
577 self.assertEqual(t1, t2)
578
579 def test_hash_equality(self):
580 t1 = timedelta(days=100,
581 weeks=-7,
582 hours=-24*(100-49),
583 minutes=-3,
584 seconds=12,
585 microseconds=(3*60 - 12) * 1000000)
586 t2 = timedelta()
587 self.assertEqual(hash(t1), hash(t2))
588
589 t1 += timedelta(weeks=7)
590 t2 += timedelta(days=7*7)
591 self.assertEqual(t1, t2)
592 self.assertEqual(hash(t1), hash(t2))
593
594 d = {t1: 1}
595 d[t2] = 2
596 self.assertEqual(len(d), 1)
597 self.assertEqual(d[t1], 2)
598
599 def test_pickling(self):
600 args = 12, 34, 56
601 orig = timedelta(*args)
602 for pickler, unpickler, proto in pickle_choices:
603 green = pickler.dumps(orig, proto)
604 derived = unpickler.loads(green)
605 self.assertEqual(orig, derived)
606
607 def test_compare(self):
608 t1 = timedelta(2, 3, 4)
609 t2 = timedelta(2, 3, 4)
610 self.assertEqual(t1, t2)
611 self.assertTrue(t1 <= t2)
612 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200613 self.assertFalse(t1 != t2)
614 self.assertFalse(t1 < t2)
615 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000616
617 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
618 t2 = timedelta(*args) # this is larger than t1
619 self.assertTrue(t1 < t2)
620 self.assertTrue(t2 > t1)
621 self.assertTrue(t1 <= t2)
622 self.assertTrue(t2 >= t1)
623 self.assertTrue(t1 != t2)
624 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200625 self.assertFalse(t1 == t2)
626 self.assertFalse(t2 == t1)
627 self.assertFalse(t1 > t2)
628 self.assertFalse(t2 < t1)
629 self.assertFalse(t1 >= t2)
630 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000631
632 for badarg in OTHERSTUFF:
633 self.assertEqual(t1 == badarg, False)
634 self.assertEqual(t1 != badarg, True)
635 self.assertEqual(badarg == t1, False)
636 self.assertEqual(badarg != t1, True)
637
638 self.assertRaises(TypeError, lambda: t1 <= badarg)
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: badarg <= t1)
643 self.assertRaises(TypeError, lambda: badarg < t1)
644 self.assertRaises(TypeError, lambda: badarg > t1)
645 self.assertRaises(TypeError, lambda: badarg >= t1)
646
647 def test_str(self):
648 td = timedelta
649 eq = self.assertEqual
650
651 eq(str(td(1)), "1 day, 0:00:00")
652 eq(str(td(-1)), "-1 day, 0:00:00")
653 eq(str(td(2)), "2 days, 0:00:00")
654 eq(str(td(-2)), "-2 days, 0:00:00")
655
656 eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
657 eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
658 eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
659 "-210 days, 23:12:34")
660
661 eq(str(td(milliseconds=1)), "0:00:00.001000")
662 eq(str(td(microseconds=3)), "0:00:00.000003")
663
664 eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
665 microseconds=999999)),
666 "999999999 days, 23:59:59.999999")
667
668 def test_repr(self):
669 name = 'datetime.' + self.theclass.__name__
670 self.assertEqual(repr(self.theclass(1)),
Utkarsh Upadhyaycc5a65c2017-07-25 23:51:33 +0200671 "%s(days=1)" % name)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000672 self.assertEqual(repr(self.theclass(10, 2)),
Utkarsh Upadhyaycc5a65c2017-07-25 23:51:33 +0200673 "%s(days=10, seconds=2)" % name)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000674 self.assertEqual(repr(self.theclass(-10, 2, 400000)),
Utkarsh Upadhyaycc5a65c2017-07-25 23:51:33 +0200675 "%s(days=-10, seconds=2, microseconds=400000)" % name)
676 self.assertEqual(repr(self.theclass(seconds=60)),
677 "%s(seconds=60)" % name)
678 self.assertEqual(repr(self.theclass()),
679 "%s(0)" % name)
680 self.assertEqual(repr(self.theclass(microseconds=100)),
681 "%s(microseconds=100)" % name)
682 self.assertEqual(repr(self.theclass(days=1, microseconds=100)),
683 "%s(days=1, microseconds=100)" % name)
684 self.assertEqual(repr(self.theclass(seconds=1, microseconds=100)),
685 "%s(seconds=1, microseconds=100)" % name)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000686
687 def test_roundtrip(self):
688 for td in (timedelta(days=999999999, hours=23, minutes=59,
689 seconds=59, microseconds=999999),
690 timedelta(days=-999999999),
691 timedelta(days=-999999999, seconds=1),
692 timedelta(days=1, seconds=2, microseconds=3)):
693
694 # Verify td -> string -> td identity.
695 s = repr(td)
696 self.assertTrue(s.startswith('datetime.'))
697 s = s[9:]
698 td2 = eval(s)
699 self.assertEqual(td, td2)
700
701 # Verify identity via reconstructing from pieces.
702 td2 = timedelta(td.days, td.seconds, td.microseconds)
703 self.assertEqual(td, td2)
704
705 def test_resolution_info(self):
706 self.assertIsInstance(timedelta.min, timedelta)
707 self.assertIsInstance(timedelta.max, timedelta)
708 self.assertIsInstance(timedelta.resolution, timedelta)
709 self.assertTrue(timedelta.max > timedelta.min)
710 self.assertEqual(timedelta.min, timedelta(-999999999))
711 self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
712 self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
713
714 def test_overflow(self):
715 tiny = timedelta.resolution
716
717 td = timedelta.min + tiny
718 td -= tiny # no problem
719 self.assertRaises(OverflowError, td.__sub__, tiny)
720 self.assertRaises(OverflowError, td.__add__, -tiny)
721
722 td = timedelta.max - tiny
723 td += tiny # no problem
724 self.assertRaises(OverflowError, td.__add__, tiny)
725 self.assertRaises(OverflowError, td.__sub__, -tiny)
726
727 self.assertRaises(OverflowError, lambda: -timedelta.max)
728
729 day = timedelta(1)
730 self.assertRaises(OverflowError, day.__mul__, 10**9)
731 self.assertRaises(OverflowError, day.__mul__, 1e9)
732 self.assertRaises(OverflowError, day.__truediv__, 1e-20)
733 self.assertRaises(OverflowError, day.__truediv__, 1e-10)
734 self.assertRaises(OverflowError, day.__truediv__, 9e-10)
735
Eric Smith3ab08ca2010-12-04 15:17:38 +0000736 @support.requires_IEEE_754
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000737 def _test_overflow_special(self):
738 day = timedelta(1)
739 self.assertRaises(OverflowError, day.__mul__, INF)
740 self.assertRaises(OverflowError, day.__mul__, -INF)
741
742 def test_microsecond_rounding(self):
743 td = timedelta
744 eq = self.assertEqual
745
746 # Single-field rounding.
747 eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
748 eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
Victor Stinner69cc4872015-09-08 23:58:54 +0200749 eq(td(milliseconds=0.5/1000), td(microseconds=0))
750 eq(td(milliseconds=-0.5/1000), td(microseconds=-0))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000751 eq(td(milliseconds=0.6/1000), td(microseconds=1))
752 eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
Victor Stinner69cc4872015-09-08 23:58:54 +0200753 eq(td(milliseconds=1.5/1000), td(microseconds=2))
754 eq(td(milliseconds=-1.5/1000), td(microseconds=-2))
755 eq(td(seconds=0.5/10**6), td(microseconds=0))
756 eq(td(seconds=-0.5/10**6), td(microseconds=-0))
757 eq(td(seconds=1/2**7), td(microseconds=7812))
758 eq(td(seconds=-1/2**7), td(microseconds=-7812))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000759
760 # Rounding due to contributions from more than one field.
761 us_per_hour = 3600e6
762 us_per_day = us_per_hour * 24
763 eq(td(days=.4/us_per_day), td(0))
764 eq(td(hours=.2/us_per_hour), td(0))
Victor Stinnercd5d7652015-09-09 01:09:21 +0200765 eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000766
767 eq(td(days=-.4/us_per_day), td(0))
768 eq(td(hours=-.2/us_per_hour), td(0))
769 eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
770
Victor Stinner69cc4872015-09-08 23:58:54 +0200771 # Test for a patch in Issue 8860
772 eq(td(microseconds=0.5), 0.5*td(microseconds=1.0))
773 eq(td(microseconds=0.5)//td.resolution, 0.5*td.resolution//td.resolution)
774
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000775 def test_massive_normalization(self):
776 td = timedelta(microseconds=-1)
777 self.assertEqual((td.days, td.seconds, td.microseconds),
778 (-1, 24*3600-1, 999999))
779
780 def test_bool(self):
781 self.assertTrue(timedelta(1))
782 self.assertTrue(timedelta(0, 1))
783 self.assertTrue(timedelta(0, 0, 1))
784 self.assertTrue(timedelta(microseconds=1))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200785 self.assertFalse(timedelta(0))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000786
787 def test_subclass_timedelta(self):
788
789 class T(timedelta):
790 @staticmethod
791 def from_td(td):
792 return T(td.days, td.seconds, td.microseconds)
793
794 def as_hours(self):
795 sum = (self.days * 24 +
796 self.seconds / 3600.0 +
797 self.microseconds / 3600e6)
798 return round(sum)
799
800 t1 = T(days=1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200801 self.assertIs(type(t1), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000802 self.assertEqual(t1.as_hours(), 24)
803
804 t2 = T(days=-1, seconds=-3600)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200805 self.assertIs(type(t2), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000806 self.assertEqual(t2.as_hours(), -25)
807
808 t3 = t1 + t2
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200809 self.assertIs(type(t3), timedelta)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000810 t4 = T.from_td(t3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200811 self.assertIs(type(t4), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000812 self.assertEqual(t3.days, t4.days)
813 self.assertEqual(t3.seconds, t4.seconds)
814 self.assertEqual(t3.microseconds, t4.microseconds)
815 self.assertEqual(str(t3), str(t4))
816 self.assertEqual(t4.as_hours(), -1)
817
818 def test_division(self):
819 t = timedelta(hours=1, minutes=24, seconds=19)
820 second = timedelta(seconds=1)
821 self.assertEqual(t / second, 5059.0)
822 self.assertEqual(t // second, 5059)
823
824 t = timedelta(minutes=2, seconds=30)
825 minute = timedelta(minutes=1)
826 self.assertEqual(t / minute, 2.5)
827 self.assertEqual(t // minute, 2)
828
829 zerotd = timedelta(0)
830 self.assertRaises(ZeroDivisionError, truediv, t, zerotd)
831 self.assertRaises(ZeroDivisionError, floordiv, t, zerotd)
832
833 # self.assertRaises(TypeError, truediv, t, 2)
834 # note: floor division of a timedelta by an integer *is*
835 # currently permitted.
836
837 def test_remainder(self):
838 t = timedelta(minutes=2, seconds=30)
839 minute = timedelta(minutes=1)
840 r = t % minute
841 self.assertEqual(r, timedelta(seconds=30))
842
843 t = timedelta(minutes=-2, seconds=30)
844 r = t % minute
845 self.assertEqual(r, timedelta(seconds=30))
846
847 zerotd = timedelta(0)
848 self.assertRaises(ZeroDivisionError, mod, t, zerotd)
849
850 self.assertRaises(TypeError, mod, t, 10)
851
852 def test_divmod(self):
853 t = timedelta(minutes=2, seconds=30)
854 minute = timedelta(minutes=1)
855 q, r = divmod(t, minute)
856 self.assertEqual(q, 2)
857 self.assertEqual(r, timedelta(seconds=30))
858
859 t = timedelta(minutes=-2, seconds=30)
860 q, r = divmod(t, minute)
861 self.assertEqual(q, -2)
862 self.assertEqual(r, timedelta(seconds=30))
863
864 zerotd = timedelta(0)
865 self.assertRaises(ZeroDivisionError, divmod, t, zerotd)
866
867 self.assertRaises(TypeError, divmod, t, 10)
868
Oren Milman865e4b42017-09-19 15:58:11 +0300869 def test_issue31293(self):
870 # The interpreter shouldn't crash in case a timedelta is divided or
871 # multiplied by a float with a bad as_integer_ratio() method.
872 def get_bad_float(bad_ratio):
873 class BadFloat(float):
874 def as_integer_ratio(self):
875 return bad_ratio
876 return BadFloat()
877
878 with self.assertRaises(TypeError):
879 timedelta() / get_bad_float(1 << 1000)
880 with self.assertRaises(TypeError):
881 timedelta() * get_bad_float(1 << 1000)
882
883 for bad_ratio in [(), (42, ), (1, 2, 3)]:
884 with self.assertRaises(ValueError):
885 timedelta() / get_bad_float(bad_ratio)
886 with self.assertRaises(ValueError):
887 timedelta() * get_bad_float(bad_ratio)
888
Serhiy Storchaka4ffd4652017-10-23 17:12:28 +0300889 def test_issue31752(self):
890 # The interpreter shouldn't crash because divmod() returns negative
891 # remainder.
892 class BadInt(int):
893 def __mul__(self, other):
894 return Prod()
895
896 class Prod:
897 def __radd__(self, other):
898 return Sum()
899
900 class Sum(int):
901 def __divmod__(self, other):
902 # negative remainder
903 return (0, -1)
904
905 timedelta(microseconds=BadInt(1))
906 timedelta(hours=BadInt(1))
907 timedelta(weeks=BadInt(1))
908
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000909
910#############################################################################
911# date tests
912
913class TestDateOnly(unittest.TestCase):
914 # Tests here won't pass if also run on datetime objects, so don't
915 # subclass this to test datetimes too.
916
917 def test_delta_non_days_ignored(self):
918 dt = date(2000, 1, 2)
919 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
920 microseconds=5)
921 days = timedelta(delta.days)
922 self.assertEqual(days, timedelta(1))
923
924 dt2 = dt + delta
925 self.assertEqual(dt2, dt + days)
926
927 dt2 = delta + dt
928 self.assertEqual(dt2, dt + days)
929
930 dt2 = dt - delta
931 self.assertEqual(dt2, dt - days)
932
933 delta = -delta
934 days = timedelta(delta.days)
935 self.assertEqual(days, timedelta(-2))
936
937 dt2 = dt + delta
938 self.assertEqual(dt2, dt + days)
939
940 dt2 = delta + dt
941 self.assertEqual(dt2, dt + days)
942
943 dt2 = dt - delta
944 self.assertEqual(dt2, dt - days)
945
946class SubclassDate(date):
947 sub_var = 1
948
949class TestDate(HarmlessMixedComparison, unittest.TestCase):
950 # Tests here should pass for both dates and datetimes, except for a
951 # few tests that TestDateTime overrides.
952
953 theclass = date
954
955 def test_basic_attributes(self):
956 dt = self.theclass(2002, 3, 1)
957 self.assertEqual(dt.year, 2002)
958 self.assertEqual(dt.month, 3)
959 self.assertEqual(dt.day, 1)
960
961 def test_roundtrip(self):
962 for dt in (self.theclass(1, 2, 3),
963 self.theclass.today()):
964 # Verify dt -> string -> date identity.
965 s = repr(dt)
966 self.assertTrue(s.startswith('datetime.'))
967 s = s[9:]
968 dt2 = eval(s)
969 self.assertEqual(dt, dt2)
970
971 # Verify identity via reconstructing from pieces.
972 dt2 = self.theclass(dt.year, dt.month, dt.day)
973 self.assertEqual(dt, dt2)
974
975 def test_ordinal_conversions(self):
976 # Check some fixed values.
977 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
978 (1, 12, 31, 365),
979 (2, 1, 1, 366),
980 # first example from "Calendrical Calculations"
981 (1945, 11, 12, 710347)]:
982 d = self.theclass(y, m, d)
983 self.assertEqual(n, d.toordinal())
984 fromord = self.theclass.fromordinal(n)
985 self.assertEqual(d, fromord)
986 if hasattr(fromord, "hour"):
987 # if we're checking something fancier than a date, verify
988 # the extra fields have been zeroed out
989 self.assertEqual(fromord.hour, 0)
990 self.assertEqual(fromord.minute, 0)
991 self.assertEqual(fromord.second, 0)
992 self.assertEqual(fromord.microsecond, 0)
993
994 # Check first and last days of year spottily across the whole
995 # range of years supported.
996 for year in range(MINYEAR, MAXYEAR+1, 7):
997 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
998 d = self.theclass(year, 1, 1)
999 n = d.toordinal()
1000 d2 = self.theclass.fromordinal(n)
1001 self.assertEqual(d, d2)
1002 # Verify that moving back a day gets to the end of year-1.
1003 if year > 1:
1004 d = self.theclass.fromordinal(n-1)
1005 d2 = self.theclass(year-1, 12, 31)
1006 self.assertEqual(d, d2)
1007 self.assertEqual(d2.toordinal(), n-1)
1008
1009 # Test every day in a leap-year and a non-leap year.
1010 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
1011 for year, isleap in (2000, True), (2002, False):
1012 n = self.theclass(year, 1, 1).toordinal()
1013 for month, maxday in zip(range(1, 13), dim):
1014 if month == 2 and isleap:
1015 maxday += 1
1016 for day in range(1, maxday+1):
1017 d = self.theclass(year, month, day)
1018 self.assertEqual(d.toordinal(), n)
1019 self.assertEqual(d, self.theclass.fromordinal(n))
1020 n += 1
1021
1022 def test_extreme_ordinals(self):
1023 a = self.theclass.min
1024 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
1025 aord = a.toordinal()
1026 b = a.fromordinal(aord)
1027 self.assertEqual(a, b)
1028
1029 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
1030
1031 b = a + timedelta(days=1)
1032 self.assertEqual(b.toordinal(), aord + 1)
1033 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
1034
1035 a = self.theclass.max
1036 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
1037 aord = a.toordinal()
1038 b = a.fromordinal(aord)
1039 self.assertEqual(a, b)
1040
1041 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
1042
1043 b = a - timedelta(days=1)
1044 self.assertEqual(b.toordinal(), aord - 1)
1045 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
1046
1047 def test_bad_constructor_arguments(self):
1048 # bad years
1049 self.theclass(MINYEAR, 1, 1) # no exception
1050 self.theclass(MAXYEAR, 1, 1) # no exception
1051 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1052 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1053 # bad months
1054 self.theclass(2000, 1, 1) # no exception
1055 self.theclass(2000, 12, 1) # no exception
1056 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1057 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1058 # bad days
1059 self.theclass(2000, 2, 29) # no exception
1060 self.theclass(2004, 2, 29) # no exception
1061 self.theclass(2400, 2, 29) # no exception
1062 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1063 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1064 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1065 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1066 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1067 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1068
1069 def test_hash_equality(self):
1070 d = self.theclass(2000, 12, 31)
1071 # same thing
1072 e = self.theclass(2000, 12, 31)
1073 self.assertEqual(d, e)
1074 self.assertEqual(hash(d), hash(e))
1075
1076 dic = {d: 1}
1077 dic[e] = 2
1078 self.assertEqual(len(dic), 1)
1079 self.assertEqual(dic[d], 2)
1080 self.assertEqual(dic[e], 2)
1081
1082 d = self.theclass(2001, 1, 1)
1083 # same thing
1084 e = self.theclass(2001, 1, 1)
1085 self.assertEqual(d, e)
1086 self.assertEqual(hash(d), hash(e))
1087
1088 dic = {d: 1}
1089 dic[e] = 2
1090 self.assertEqual(len(dic), 1)
1091 self.assertEqual(dic[d], 2)
1092 self.assertEqual(dic[e], 2)
1093
1094 def test_computations(self):
1095 a = self.theclass(2002, 1, 31)
1096 b = self.theclass(1956, 1, 31)
1097 c = self.theclass(2001,2,1)
1098
1099 diff = a-b
1100 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1101 self.assertEqual(diff.seconds, 0)
1102 self.assertEqual(diff.microseconds, 0)
1103
1104 day = timedelta(1)
1105 week = timedelta(7)
1106 a = self.theclass(2002, 3, 2)
1107 self.assertEqual(a + day, self.theclass(2002, 3, 3))
1108 self.assertEqual(day + a, self.theclass(2002, 3, 3))
1109 self.assertEqual(a - day, self.theclass(2002, 3, 1))
1110 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
1111 self.assertEqual(a + week, self.theclass(2002, 3, 9))
1112 self.assertEqual(a - week, self.theclass(2002, 2, 23))
1113 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
1114 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
1115 self.assertEqual((a + week) - a, week)
1116 self.assertEqual((a + day) - a, day)
1117 self.assertEqual((a - week) - a, -week)
1118 self.assertEqual((a - day) - a, -day)
1119 self.assertEqual(a - (a + week), -week)
1120 self.assertEqual(a - (a + day), -day)
1121 self.assertEqual(a - (a - week), week)
1122 self.assertEqual(a - (a - day), day)
1123 self.assertEqual(c - (c - day), day)
1124
1125 # Add/sub ints or floats should be illegal
1126 for i in 1, 1.0:
1127 self.assertRaises(TypeError, lambda: a+i)
1128 self.assertRaises(TypeError, lambda: a-i)
1129 self.assertRaises(TypeError, lambda: i+a)
1130 self.assertRaises(TypeError, lambda: i-a)
1131
1132 # delta - date is senseless.
1133 self.assertRaises(TypeError, lambda: day - a)
1134 # mixing date and (delta or date) via * or // is senseless
1135 self.assertRaises(TypeError, lambda: day * a)
1136 self.assertRaises(TypeError, lambda: a * day)
1137 self.assertRaises(TypeError, lambda: day // a)
1138 self.assertRaises(TypeError, lambda: a // day)
1139 self.assertRaises(TypeError, lambda: a * a)
1140 self.assertRaises(TypeError, lambda: a // a)
1141 # date + date is senseless
1142 self.assertRaises(TypeError, lambda: a + a)
1143
1144 def test_overflow(self):
1145 tiny = self.theclass.resolution
1146
1147 for delta in [tiny, timedelta(1), timedelta(2)]:
1148 dt = self.theclass.min + delta
1149 dt -= delta # no problem
1150 self.assertRaises(OverflowError, dt.__sub__, delta)
1151 self.assertRaises(OverflowError, dt.__add__, -delta)
1152
1153 dt = self.theclass.max - delta
1154 dt += delta # no problem
1155 self.assertRaises(OverflowError, dt.__add__, delta)
1156 self.assertRaises(OverflowError, dt.__sub__, -delta)
1157
1158 def test_fromtimestamp(self):
1159 import time
1160
1161 # Try an arbitrary fixed value.
1162 year, month, day = 1999, 9, 19
1163 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
1164 d = self.theclass.fromtimestamp(ts)
1165 self.assertEqual(d.year, year)
1166 self.assertEqual(d.month, month)
1167 self.assertEqual(d.day, day)
1168
1169 def test_insane_fromtimestamp(self):
1170 # It's possible that some platform maps time_t to double,
1171 # and that this test will fail there. This test should
1172 # exempt such platforms (provided they return reasonable
1173 # results!).
1174 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001175 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001176 insane)
1177
1178 def test_today(self):
1179 import time
1180
1181 # We claim that today() is like fromtimestamp(time.time()), so
1182 # prove it.
1183 for dummy in range(3):
1184 today = self.theclass.today()
1185 ts = time.time()
1186 todayagain = self.theclass.fromtimestamp(ts)
1187 if today == todayagain:
1188 break
1189 # There are several legit reasons that could fail:
1190 # 1. It recently became midnight, between the today() and the
1191 # time() calls.
1192 # 2. The platform time() has such fine resolution that we'll
1193 # never get the same value twice.
1194 # 3. The platform time() has poor resolution, and we just
1195 # happened to call today() right before a resolution quantum
1196 # boundary.
1197 # 4. The system clock got fiddled between calls.
1198 # In any case, wait a little while and try again.
1199 time.sleep(0.1)
1200
1201 # It worked or it didn't. If it didn't, assume it's reason #2, and
1202 # let the test pass if they're within half a second of each other.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001203 if today != todayagain:
1204 self.assertAlmostEqual(todayagain, today,
1205 delta=timedelta(seconds=0.5))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001206
1207 def test_weekday(self):
1208 for i in range(7):
1209 # March 4, 2002 is a Monday
1210 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
1211 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
1212 # January 2, 1956 is a Monday
1213 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
1214 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
1215
1216 def test_isocalendar(self):
1217 # Check examples from
1218 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1219 for i in range(7):
1220 d = self.theclass(2003, 12, 22+i)
1221 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
1222 d = self.theclass(2003, 12, 29) + timedelta(i)
1223 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
1224 d = self.theclass(2004, 1, 5+i)
1225 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
1226 d = self.theclass(2009, 12, 21+i)
1227 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
1228 d = self.theclass(2009, 12, 28) + timedelta(i)
1229 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
1230 d = self.theclass(2010, 1, 4+i)
1231 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
1232
1233 def test_iso_long_years(self):
1234 # Calculate long ISO years and compare to table from
1235 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1236 ISO_LONG_YEARS_TABLE = """
1237 4 32 60 88
1238 9 37 65 93
1239 15 43 71 99
1240 20 48 76
1241 26 54 82
1242
1243 105 133 161 189
1244 111 139 167 195
1245 116 144 172
1246 122 150 178
1247 128 156 184
1248
1249 201 229 257 285
1250 207 235 263 291
1251 212 240 268 296
1252 218 246 274
1253 224 252 280
1254
1255 303 331 359 387
1256 308 336 364 392
1257 314 342 370 398
1258 320 348 376
1259 325 353 381
1260 """
1261 iso_long_years = sorted(map(int, ISO_LONG_YEARS_TABLE.split()))
1262 L = []
1263 for i in range(400):
1264 d = self.theclass(2000+i, 12, 31)
1265 d1 = self.theclass(1600+i, 12, 31)
1266 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
1267 if d.isocalendar()[1] == 53:
1268 L.append(i)
1269 self.assertEqual(L, iso_long_years)
1270
1271 def test_isoformat(self):
1272 t = self.theclass(2, 3, 2)
1273 self.assertEqual(t.isoformat(), "0002-03-02")
1274
1275 def test_ctime(self):
1276 t = self.theclass(2002, 3, 2)
1277 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
1278
1279 def test_strftime(self):
1280 t = self.theclass(2005, 3, 2)
1281 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
1282 self.assertEqual(t.strftime(""), "") # SF bug #761337
1283 self.assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784
1284
1285 self.assertRaises(TypeError, t.strftime) # needs an arg
1286 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
1287 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
1288
1289 # test that unicode input is allowed (issue 2782)
1290 self.assertEqual(t.strftime("%m"), "03")
1291
1292 # A naive object replaces %z and %Z w/ empty strings.
1293 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1294
1295 #make sure that invalid format specifiers are handled correctly
1296 #self.assertRaises(ValueError, t.strftime, "%e")
1297 #self.assertRaises(ValueError, t.strftime, "%")
1298 #self.assertRaises(ValueError, t.strftime, "%#")
1299
1300 #oh well, some systems just ignore those invalid ones.
Martin Panter46f50722016-05-26 05:35:26 +00001301 #at least, exercise them to make sure that no crashes
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001302 #are generated
1303 for f in ["%e", "%", "%#"]:
1304 try:
1305 t.strftime(f)
1306 except ValueError:
1307 pass
1308
1309 #check that this standard extension works
1310 t.strftime("%f")
1311
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001312 def test_format(self):
1313 dt = self.theclass(2007, 9, 10)
1314 self.assertEqual(dt.__format__(''), str(dt))
1315
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02001316 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001317 dt.__format__(123)
1318
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001319 # check that a derived class's __str__() gets called
1320 class A(self.theclass):
1321 def __str__(self):
1322 return 'A'
1323 a = A(2007, 9, 10)
1324 self.assertEqual(a.__format__(''), 'A')
1325
1326 # check that a derived class's strftime gets called
1327 class B(self.theclass):
1328 def strftime(self, format_spec):
1329 return 'B'
1330 b = B(2007, 9, 10)
1331 self.assertEqual(b.__format__(''), str(dt))
1332
1333 for fmt in ["m:%m d:%d y:%y",
1334 "m:%m d:%d y:%y H:%H M:%M S:%S",
1335 "%z %Z",
1336 ]:
1337 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1338 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1339 self.assertEqual(b.__format__(fmt), 'B')
1340
1341 def test_resolution_info(self):
1342 # XXX: Should min and max respect subclassing?
1343 if issubclass(self.theclass, datetime):
1344 expected_class = datetime
1345 else:
1346 expected_class = date
1347 self.assertIsInstance(self.theclass.min, expected_class)
1348 self.assertIsInstance(self.theclass.max, expected_class)
1349 self.assertIsInstance(self.theclass.resolution, timedelta)
1350 self.assertTrue(self.theclass.max > self.theclass.min)
1351
1352 def test_extreme_timedelta(self):
1353 big = self.theclass.max - self.theclass.min
1354 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
1355 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
1356 # n == 315537897599999999 ~= 2**58.13
1357 justasbig = timedelta(0, 0, n)
1358 self.assertEqual(big, justasbig)
1359 self.assertEqual(self.theclass.min + big, self.theclass.max)
1360 self.assertEqual(self.theclass.max - big, self.theclass.min)
1361
1362 def test_timetuple(self):
1363 for i in range(7):
1364 # January 2, 1956 is a Monday (0)
1365 d = self.theclass(1956, 1, 2+i)
1366 t = d.timetuple()
1367 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
1368 # February 1, 1956 is a Wednesday (2)
1369 d = self.theclass(1956, 2, 1+i)
1370 t = d.timetuple()
1371 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
1372 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
1373 # of the year.
1374 d = self.theclass(1956, 3, 1+i)
1375 t = d.timetuple()
1376 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
1377 self.assertEqual(t.tm_year, 1956)
1378 self.assertEqual(t.tm_mon, 3)
1379 self.assertEqual(t.tm_mday, 1+i)
1380 self.assertEqual(t.tm_hour, 0)
1381 self.assertEqual(t.tm_min, 0)
1382 self.assertEqual(t.tm_sec, 0)
1383 self.assertEqual(t.tm_wday, (3+i)%7)
1384 self.assertEqual(t.tm_yday, 61+i)
1385 self.assertEqual(t.tm_isdst, -1)
1386
1387 def test_pickling(self):
1388 args = 6, 7, 23
1389 orig = self.theclass(*args)
1390 for pickler, unpickler, proto in pickle_choices:
1391 green = pickler.dumps(orig, proto)
1392 derived = unpickler.loads(green)
1393 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02001394 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001395
1396 def test_compare(self):
1397 t1 = self.theclass(2, 3, 4)
1398 t2 = self.theclass(2, 3, 4)
1399 self.assertEqual(t1, t2)
1400 self.assertTrue(t1 <= t2)
1401 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001402 self.assertFalse(t1 != t2)
1403 self.assertFalse(t1 < t2)
1404 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001405
1406 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
1407 t2 = self.theclass(*args) # this is larger than t1
1408 self.assertTrue(t1 < t2)
1409 self.assertTrue(t2 > t1)
1410 self.assertTrue(t1 <= t2)
1411 self.assertTrue(t2 >= t1)
1412 self.assertTrue(t1 != t2)
1413 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001414 self.assertFalse(t1 == t2)
1415 self.assertFalse(t2 == t1)
1416 self.assertFalse(t1 > t2)
1417 self.assertFalse(t2 < t1)
1418 self.assertFalse(t1 >= t2)
1419 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001420
1421 for badarg in OTHERSTUFF:
1422 self.assertEqual(t1 == badarg, False)
1423 self.assertEqual(t1 != badarg, True)
1424 self.assertEqual(badarg == t1, False)
1425 self.assertEqual(badarg != t1, True)
1426
1427 self.assertRaises(TypeError, lambda: t1 < badarg)
1428 self.assertRaises(TypeError, lambda: t1 > badarg)
1429 self.assertRaises(TypeError, lambda: t1 >= badarg)
1430 self.assertRaises(TypeError, lambda: badarg <= t1)
1431 self.assertRaises(TypeError, lambda: badarg < t1)
1432 self.assertRaises(TypeError, lambda: badarg > t1)
1433 self.assertRaises(TypeError, lambda: badarg >= t1)
1434
1435 def test_mixed_compare(self):
1436 our = self.theclass(2000, 4, 5)
1437
1438 # Our class can be compared for equality to other classes
1439 self.assertEqual(our == 1, False)
1440 self.assertEqual(1 == our, False)
1441 self.assertEqual(our != 1, True)
1442 self.assertEqual(1 != our, True)
1443
1444 # But the ordering is undefined
1445 self.assertRaises(TypeError, lambda: our < 1)
1446 self.assertRaises(TypeError, lambda: 1 < our)
1447
1448 # Repeat those tests with a different class
1449
1450 class SomeClass:
1451 pass
1452
1453 their = SomeClass()
1454 self.assertEqual(our == their, False)
1455 self.assertEqual(their == our, False)
1456 self.assertEqual(our != their, True)
1457 self.assertEqual(their != our, True)
1458 self.assertRaises(TypeError, lambda: our < their)
1459 self.assertRaises(TypeError, lambda: their < our)
1460
1461 # However, if the other class explicitly defines ordering
1462 # relative to our class, it is allowed to do so
1463
1464 class LargerThanAnything:
1465 def __lt__(self, other):
1466 return False
1467 def __le__(self, other):
1468 return isinstance(other, LargerThanAnything)
1469 def __eq__(self, other):
1470 return isinstance(other, LargerThanAnything)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001471 def __gt__(self, other):
1472 return not isinstance(other, LargerThanAnything)
1473 def __ge__(self, other):
1474 return True
1475
1476 their = LargerThanAnything()
1477 self.assertEqual(our == their, False)
1478 self.assertEqual(their == our, False)
1479 self.assertEqual(our != their, True)
1480 self.assertEqual(their != our, True)
1481 self.assertEqual(our < their, True)
1482 self.assertEqual(their < our, False)
1483
1484 def test_bool(self):
1485 # All dates are considered true.
1486 self.assertTrue(self.theclass.min)
1487 self.assertTrue(self.theclass.max)
1488
Alexander Belopolsky89da3492011-05-02 13:14:24 -04001489 def test_strftime_y2k(self):
1490 for y in (1, 49, 70, 99, 100, 999, 1000, 1970):
Florent Xicluna49ce0682011-11-01 12:56:14 +01001491 d = self.theclass(y, 1, 1)
1492 # Issue 13305: For years < 1000, the value is not always
1493 # padded to 4 digits across platforms. The C standard
1494 # assumes year >= 1900, so it does not specify the number
1495 # of digits.
1496 if d.strftime("%Y") != '%04d' % y:
1497 # Year 42 returns '42', not padded
1498 self.assertEqual(d.strftime("%Y"), '%d' % y)
Victor Stinner15a83e82016-03-12 08:16:48 +01001499 # '0042' is obtained anyway
1500 self.assertEqual(d.strftime("%4Y"), '%04d' % y)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001501
1502 def test_replace(self):
1503 cls = self.theclass
1504 args = [1, 2, 3]
1505 base = cls(*args)
1506 self.assertEqual(base, base.replace())
1507
1508 i = 0
1509 for name, newval in (("year", 2),
1510 ("month", 3),
1511 ("day", 4)):
1512 newargs = args[:]
1513 newargs[i] = newval
1514 expected = cls(*newargs)
1515 got = base.replace(**{name: newval})
1516 self.assertEqual(expected, got)
1517 i += 1
1518
1519 # Out of bounds.
1520 base = cls(2000, 2, 29)
1521 self.assertRaises(ValueError, base.replace, year=2001)
1522
1523 def test_subclass_date(self):
1524
1525 class C(self.theclass):
1526 theAnswer = 42
1527
1528 def __new__(cls, *args, **kws):
1529 temp = kws.copy()
1530 extra = temp.pop('extra')
1531 result = self.theclass.__new__(cls, *args, **temp)
1532 result.extra = extra
1533 return result
1534
1535 def newmeth(self, start):
1536 return start + self.year + self.month
1537
1538 args = 2003, 4, 14
1539
1540 dt1 = self.theclass(*args)
1541 dt2 = C(*args, **{'extra': 7})
1542
1543 self.assertEqual(dt2.__class__, C)
1544 self.assertEqual(dt2.theAnswer, 42)
1545 self.assertEqual(dt2.extra, 7)
1546 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1547 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1548
1549 def test_pickling_subclass_date(self):
1550
1551 args = 6, 7, 23
1552 orig = SubclassDate(*args)
1553 for pickler, unpickler, proto in pickle_choices:
1554 green = pickler.dumps(orig, proto)
1555 derived = unpickler.loads(green)
1556 self.assertEqual(orig, derived)
1557
1558 def test_backdoor_resistance(self):
1559 # For fast unpickling, the constructor accepts a pickle byte string.
1560 # This is a low-overhead backdoor. A user can (by intent or
1561 # mistake) pass a string directly, which (if it's the right length)
1562 # will get treated like a pickle, and bypass the normal sanity
1563 # checks in the constructor. This can create insane objects.
1564 # The constructor doesn't want to burn the time to validate all
1565 # fields, but does check the month field. This stops, e.g.,
1566 # datetime.datetime('1995-03-25') from yielding an insane object.
1567 base = b'1995-03-25'
1568 if not issubclass(self.theclass, datetime):
1569 base = base[:4]
1570 for month_byte in b'9', b'\0', b'\r', b'\xff':
1571 self.assertRaises(TypeError, self.theclass,
1572 base[:2] + month_byte + base[3:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001573 if issubclass(self.theclass, datetime):
1574 # Good bytes, but bad tzinfo:
1575 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
1576 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001577
1578 for ord_byte in range(1, 13):
1579 # This shouldn't blow up because of the month byte alone. If
1580 # the implementation changes to do more-careful checking, it may
1581 # blow up because other fields are insane.
1582 self.theclass(base[:2] + bytes([ord_byte]) + base[3:])
1583
1584#############################################################################
1585# datetime tests
1586
1587class SubclassDatetime(datetime):
1588 sub_var = 1
1589
1590class TestDateTime(TestDate):
1591
1592 theclass = datetime
1593
1594 def test_basic_attributes(self):
1595 dt = self.theclass(2002, 3, 1, 12, 0)
1596 self.assertEqual(dt.year, 2002)
1597 self.assertEqual(dt.month, 3)
1598 self.assertEqual(dt.day, 1)
1599 self.assertEqual(dt.hour, 12)
1600 self.assertEqual(dt.minute, 0)
1601 self.assertEqual(dt.second, 0)
1602 self.assertEqual(dt.microsecond, 0)
1603
1604 def test_basic_attributes_nonzero(self):
1605 # Make sure all attributes are non-zero so bugs in
1606 # bit-shifting access show up.
1607 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1608 self.assertEqual(dt.year, 2002)
1609 self.assertEqual(dt.month, 3)
1610 self.assertEqual(dt.day, 1)
1611 self.assertEqual(dt.hour, 12)
1612 self.assertEqual(dt.minute, 59)
1613 self.assertEqual(dt.second, 59)
1614 self.assertEqual(dt.microsecond, 8000)
1615
1616 def test_roundtrip(self):
1617 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1618 self.theclass.now()):
1619 # Verify dt -> string -> datetime identity.
1620 s = repr(dt)
1621 self.assertTrue(s.startswith('datetime.'))
1622 s = s[9:]
1623 dt2 = eval(s)
1624 self.assertEqual(dt, dt2)
1625
1626 # Verify identity via reconstructing from pieces.
1627 dt2 = self.theclass(dt.year, dt.month, dt.day,
1628 dt.hour, dt.minute, dt.second,
1629 dt.microsecond)
1630 self.assertEqual(dt, dt2)
1631
1632 def test_isoformat(self):
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001633 t = self.theclass(1, 2, 3, 4, 5, 1, 123)
1634 self.assertEqual(t.isoformat(), "0001-02-03T04:05:01.000123")
1635 self.assertEqual(t.isoformat('T'), "0001-02-03T04:05:01.000123")
1636 self.assertEqual(t.isoformat(' '), "0001-02-03 04:05:01.000123")
1637 self.assertEqual(t.isoformat('\x00'), "0001-02-03\x0004:05:01.000123")
1638 self.assertEqual(t.isoformat(timespec='hours'), "0001-02-03T04")
1639 self.assertEqual(t.isoformat(timespec='minutes'), "0001-02-03T04:05")
1640 self.assertEqual(t.isoformat(timespec='seconds'), "0001-02-03T04:05:01")
1641 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1642 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000123")
1643 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01.000123")
1644 self.assertEqual(t.isoformat(sep=' ', timespec='minutes'), "0001-02-03 04:05")
1645 self.assertRaises(ValueError, t.isoformat, timespec='foo')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001646 # str is ISO format with the separator forced to a blank.
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001647 self.assertEqual(str(t), "0001-02-03 04:05:01.000123")
1648
1649 t = self.theclass(1, 2, 3, 4, 5, 1, 999500, tzinfo=timezone.utc)
1650 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999+00:00")
1651
1652 t = self.theclass(1, 2, 3, 4, 5, 1, 999500)
1653 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999")
1654
1655 t = self.theclass(1, 2, 3, 4, 5, 1)
1656 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01")
1657 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1658 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000000")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001659
1660 t = self.theclass(2, 3, 2)
1661 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1662 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1663 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1664 # str is ISO format with the separator forced to a blank.
1665 self.assertEqual(str(t), "0002-03-02 00:00:00")
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001666 # ISO format with timezone
1667 tz = FixedOffset(timedelta(seconds=16), 'XXX')
1668 t = self.theclass(2, 3, 2, tzinfo=tz)
1669 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00+00:00:16")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001670
1671 def test_format(self):
1672 dt = self.theclass(2007, 9, 10, 4, 5, 1, 123)
1673 self.assertEqual(dt.__format__(''), str(dt))
1674
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02001675 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001676 dt.__format__(123)
1677
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001678 # check that a derived class's __str__() gets called
1679 class A(self.theclass):
1680 def __str__(self):
1681 return 'A'
1682 a = A(2007, 9, 10, 4, 5, 1, 123)
1683 self.assertEqual(a.__format__(''), 'A')
1684
1685 # check that a derived class's strftime gets called
1686 class B(self.theclass):
1687 def strftime(self, format_spec):
1688 return 'B'
1689 b = B(2007, 9, 10, 4, 5, 1, 123)
1690 self.assertEqual(b.__format__(''), str(dt))
1691
1692 for fmt in ["m:%m d:%d y:%y",
1693 "m:%m d:%d y:%y H:%H M:%M S:%S",
1694 "%z %Z",
1695 ]:
1696 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1697 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1698 self.assertEqual(b.__format__(fmt), 'B')
1699
1700 def test_more_ctime(self):
1701 # Test fields that TestDate doesn't touch.
1702 import time
1703
1704 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1705 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1706 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1707 # out. The difference is that t.ctime() produces " 2" for the day,
1708 # but platform ctime() produces "02" for the day. According to
1709 # C99, t.ctime() is correct here.
1710 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1711
1712 # So test a case where that difference doesn't matter.
1713 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1714 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1715
1716 def test_tz_independent_comparing(self):
1717 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1718 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1719 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1720 self.assertEqual(dt1, dt3)
1721 self.assertTrue(dt2 > dt3)
1722
1723 # Make sure comparison doesn't forget microseconds, and isn't done
1724 # via comparing a float timestamp (an IEEE double doesn't have enough
1725 # precision to span microsecond resolution across years 1 thru 9999,
1726 # so comparing via timestamp necessarily calls some distinct values
1727 # equal).
1728 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1729 us = timedelta(microseconds=1)
1730 dt2 = dt1 + us
1731 self.assertEqual(dt2 - dt1, us)
1732 self.assertTrue(dt1 < dt2)
1733
1734 def test_strftime_with_bad_tzname_replace(self):
1735 # verify ok if tzinfo.tzname().replace() returns a non-string
1736 class MyTzInfo(FixedOffset):
1737 def tzname(self, dt):
1738 class MyStr(str):
1739 def replace(self, *args):
1740 return None
1741 return MyStr('name')
1742 t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
1743 self.assertRaises(TypeError, t.strftime, '%Z')
1744
1745 def test_bad_constructor_arguments(self):
1746 # bad years
1747 self.theclass(MINYEAR, 1, 1) # no exception
1748 self.theclass(MAXYEAR, 1, 1) # no exception
1749 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1750 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1751 # bad months
1752 self.theclass(2000, 1, 1) # no exception
1753 self.theclass(2000, 12, 1) # no exception
1754 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1755 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1756 # bad days
1757 self.theclass(2000, 2, 29) # no exception
1758 self.theclass(2004, 2, 29) # no exception
1759 self.theclass(2400, 2, 29) # no exception
1760 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1761 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1762 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1763 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1764 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1765 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1766 # bad hours
1767 self.theclass(2000, 1, 31, 0) # no exception
1768 self.theclass(2000, 1, 31, 23) # no exception
1769 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1770 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1771 # bad minutes
1772 self.theclass(2000, 1, 31, 23, 0) # no exception
1773 self.theclass(2000, 1, 31, 23, 59) # no exception
1774 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1775 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1776 # bad seconds
1777 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1778 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1779 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1780 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1781 # bad microseconds
1782 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1783 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1784 self.assertRaises(ValueError, self.theclass,
1785 2000, 1, 31, 23, 59, 59, -1)
1786 self.assertRaises(ValueError, self.theclass,
1787 2000, 1, 31, 23, 59, 59,
1788 1000000)
Alexander Belopolsky47649ab2016-08-08 17:05:40 -04001789 # bad fold
1790 self.assertRaises(ValueError, self.theclass,
1791 2000, 1, 31, fold=-1)
1792 self.assertRaises(ValueError, self.theclass,
1793 2000, 1, 31, fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001794 # Positional fold:
1795 self.assertRaises(TypeError, self.theclass,
1796 2000, 1, 31, 23, 59, 59, 0, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04001797
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001798 def test_hash_equality(self):
1799 d = self.theclass(2000, 12, 31, 23, 30, 17)
1800 e = self.theclass(2000, 12, 31, 23, 30, 17)
1801 self.assertEqual(d, e)
1802 self.assertEqual(hash(d), hash(e))
1803
1804 dic = {d: 1}
1805 dic[e] = 2
1806 self.assertEqual(len(dic), 1)
1807 self.assertEqual(dic[d], 2)
1808 self.assertEqual(dic[e], 2)
1809
1810 d = self.theclass(2001, 1, 1, 0, 5, 17)
1811 e = self.theclass(2001, 1, 1, 0, 5, 17)
1812 self.assertEqual(d, e)
1813 self.assertEqual(hash(d), hash(e))
1814
1815 dic = {d: 1}
1816 dic[e] = 2
1817 self.assertEqual(len(dic), 1)
1818 self.assertEqual(dic[d], 2)
1819 self.assertEqual(dic[e], 2)
1820
1821 def test_computations(self):
1822 a = self.theclass(2002, 1, 31)
1823 b = self.theclass(1956, 1, 31)
1824 diff = a-b
1825 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1826 self.assertEqual(diff.seconds, 0)
1827 self.assertEqual(diff.microseconds, 0)
1828 a = self.theclass(2002, 3, 2, 17, 6)
1829 millisec = timedelta(0, 0, 1000)
1830 hour = timedelta(0, 3600)
1831 day = timedelta(1)
1832 week = timedelta(7)
1833 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1834 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1835 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1836 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1837 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1838 self.assertEqual(a - hour, a + -hour)
1839 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1840 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1841 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1842 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1843 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1844 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1845 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1846 self.assertEqual((a + week) - a, week)
1847 self.assertEqual((a + day) - a, day)
1848 self.assertEqual((a + hour) - a, hour)
1849 self.assertEqual((a + millisec) - a, millisec)
1850 self.assertEqual((a - week) - a, -week)
1851 self.assertEqual((a - day) - a, -day)
1852 self.assertEqual((a - hour) - a, -hour)
1853 self.assertEqual((a - millisec) - a, -millisec)
1854 self.assertEqual(a - (a + week), -week)
1855 self.assertEqual(a - (a + day), -day)
1856 self.assertEqual(a - (a + hour), -hour)
1857 self.assertEqual(a - (a + millisec), -millisec)
1858 self.assertEqual(a - (a - week), week)
1859 self.assertEqual(a - (a - day), day)
1860 self.assertEqual(a - (a - hour), hour)
1861 self.assertEqual(a - (a - millisec), millisec)
1862 self.assertEqual(a + (week + day + hour + millisec),
1863 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1864 self.assertEqual(a + (week + day + hour + millisec),
1865 (((a + week) + day) + hour) + millisec)
1866 self.assertEqual(a - (week + day + hour + millisec),
1867 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1868 self.assertEqual(a - (week + day + hour + millisec),
1869 (((a - week) - day) - hour) - millisec)
1870 # Add/sub ints or floats should be illegal
1871 for i in 1, 1.0:
1872 self.assertRaises(TypeError, lambda: a+i)
1873 self.assertRaises(TypeError, lambda: a-i)
1874 self.assertRaises(TypeError, lambda: i+a)
1875 self.assertRaises(TypeError, lambda: i-a)
1876
1877 # delta - datetime is senseless.
1878 self.assertRaises(TypeError, lambda: day - a)
1879 # mixing datetime and (delta or datetime) via * or // is senseless
1880 self.assertRaises(TypeError, lambda: day * a)
1881 self.assertRaises(TypeError, lambda: a * day)
1882 self.assertRaises(TypeError, lambda: day // a)
1883 self.assertRaises(TypeError, lambda: a // day)
1884 self.assertRaises(TypeError, lambda: a * a)
1885 self.assertRaises(TypeError, lambda: a // a)
1886 # datetime + datetime is senseless
1887 self.assertRaises(TypeError, lambda: a + a)
1888
1889 def test_pickling(self):
1890 args = 6, 7, 23, 20, 59, 1, 64**2
1891 orig = self.theclass(*args)
1892 for pickler, unpickler, proto in pickle_choices:
1893 green = pickler.dumps(orig, proto)
1894 derived = unpickler.loads(green)
1895 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02001896 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001897
1898 def test_more_pickling(self):
1899 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
Serhiy Storchakabad12572014-12-15 14:03:42 +02001900 for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1901 s = pickle.dumps(a, proto)
1902 b = pickle.loads(s)
1903 self.assertEqual(b.year, 2003)
1904 self.assertEqual(b.month, 2)
1905 self.assertEqual(b.day, 7)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001906
1907 def test_pickling_subclass_datetime(self):
1908 args = 6, 7, 23, 20, 59, 1, 64**2
1909 orig = SubclassDatetime(*args)
1910 for pickler, unpickler, proto in pickle_choices:
1911 green = pickler.dumps(orig, proto)
1912 derived = unpickler.loads(green)
1913 self.assertEqual(orig, derived)
1914
1915 def test_more_compare(self):
1916 # The test_compare() inherited from TestDate covers the error cases.
1917 # We just want to test lexicographic ordering on the members datetime
1918 # has that date lacks.
1919 args = [2000, 11, 29, 20, 58, 16, 999998]
1920 t1 = self.theclass(*args)
1921 t2 = self.theclass(*args)
1922 self.assertEqual(t1, t2)
1923 self.assertTrue(t1 <= t2)
1924 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001925 self.assertFalse(t1 != t2)
1926 self.assertFalse(t1 < t2)
1927 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001928
1929 for i in range(len(args)):
1930 newargs = args[:]
1931 newargs[i] = args[i] + 1
1932 t2 = self.theclass(*newargs) # this is larger than t1
1933 self.assertTrue(t1 < t2)
1934 self.assertTrue(t2 > t1)
1935 self.assertTrue(t1 <= t2)
1936 self.assertTrue(t2 >= t1)
1937 self.assertTrue(t1 != t2)
1938 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001939 self.assertFalse(t1 == t2)
1940 self.assertFalse(t2 == t1)
1941 self.assertFalse(t1 > t2)
1942 self.assertFalse(t2 < t1)
1943 self.assertFalse(t1 >= t2)
1944 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001945
1946
1947 # A helper for timestamp constructor tests.
1948 def verify_field_equality(self, expected, got):
1949 self.assertEqual(expected.tm_year, got.year)
1950 self.assertEqual(expected.tm_mon, got.month)
1951 self.assertEqual(expected.tm_mday, got.day)
1952 self.assertEqual(expected.tm_hour, got.hour)
1953 self.assertEqual(expected.tm_min, got.minute)
1954 self.assertEqual(expected.tm_sec, got.second)
1955
1956 def test_fromtimestamp(self):
1957 import time
1958
1959 ts = time.time()
1960 expected = time.localtime(ts)
1961 got = self.theclass.fromtimestamp(ts)
1962 self.verify_field_equality(expected, got)
1963
1964 def test_utcfromtimestamp(self):
1965 import time
1966
1967 ts = time.time()
1968 expected = time.gmtime(ts)
1969 got = self.theclass.utcfromtimestamp(ts)
1970 self.verify_field_equality(expected, got)
1971
Alexander Belopolskya4415142012-06-08 12:33:09 -04001972 # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in
1973 # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0).
1974 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
1975 def test_timestamp_naive(self):
1976 t = self.theclass(1970, 1, 1)
1977 self.assertEqual(t.timestamp(), 18000.0)
1978 t = self.theclass(1970, 1, 1, 1, 2, 3, 4)
1979 self.assertEqual(t.timestamp(),
1980 18000.0 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001981 # Missing hour
1982 t0 = self.theclass(2012, 3, 11, 2, 30)
1983 t1 = t0.replace(fold=1)
1984 self.assertEqual(self.theclass.fromtimestamp(t1.timestamp()),
1985 t0 - timedelta(hours=1))
1986 self.assertEqual(self.theclass.fromtimestamp(t0.timestamp()),
1987 t1 + timedelta(hours=1))
Alexander Belopolskya4415142012-06-08 12:33:09 -04001988 # Ambiguous hour defaults to DST
1989 t = self.theclass(2012, 11, 4, 1, 30)
1990 self.assertEqual(self.theclass.fromtimestamp(t.timestamp()), t)
1991
1992 # Timestamp may raise an overflow error on some platforms
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001993 # XXX: Do we care to support the first and last year?
1994 for t in [self.theclass(2,1,1), self.theclass(9998,12,12)]:
Alexander Belopolskya4415142012-06-08 12:33:09 -04001995 try:
1996 s = t.timestamp()
1997 except OverflowError:
1998 pass
1999 else:
2000 self.assertEqual(self.theclass.fromtimestamp(s), t)
2001
2002 def test_timestamp_aware(self):
2003 t = self.theclass(1970, 1, 1, tzinfo=timezone.utc)
2004 self.assertEqual(t.timestamp(), 0.0)
2005 t = self.theclass(1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone.utc)
2006 self.assertEqual(t.timestamp(),
2007 3600 + 2*60 + 3 + 4*1e-6)
2008 t = self.theclass(1970, 1, 1, 1, 2, 3, 4,
2009 tzinfo=timezone(timedelta(hours=-5), 'EST'))
2010 self.assertEqual(t.timestamp(),
2011 18000 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002012
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002013 @support.run_with_tz('MSK-03') # Something east of Greenwich
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002014 def test_microsecond_rounding(self):
Victor Stinner5ebfe422015-09-18 14:52:15 +02002015 for fts in [self.theclass.fromtimestamp,
2016 self.theclass.utcfromtimestamp]:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002017 zero = fts(0)
2018 self.assertEqual(zero.second, 0)
2019 self.assertEqual(zero.microsecond, 0)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002020 one = fts(1e-6)
Victor Stinner8050ca92012-03-14 00:17:05 +01002021 try:
2022 minus_one = fts(-1e-6)
2023 except OSError:
2024 # localtime(-1) and gmtime(-1) is not supported on Windows
2025 pass
2026 else:
2027 self.assertEqual(minus_one.second, 59)
2028 self.assertEqual(minus_one.microsecond, 999999)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002029
Victor Stinner8050ca92012-03-14 00:17:05 +01002030 t = fts(-1e-8)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002031 self.assertEqual(t, zero)
Victor Stinner8050ca92012-03-14 00:17:05 +01002032 t = fts(-9e-7)
2033 self.assertEqual(t, minus_one)
2034 t = fts(-1e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002035 self.assertEqual(t, zero)
Victor Stinner8820a352015-09-05 10:50:20 +02002036 t = fts(-1/2**7)
2037 self.assertEqual(t.second, 59)
Victor Stinner7667f582015-09-09 01:02:23 +02002038 self.assertEqual(t.microsecond, 992188)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002039
2040 t = fts(1e-7)
2041 self.assertEqual(t, zero)
2042 t = fts(9e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002043 self.assertEqual(t, one)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002044 t = fts(0.99999949)
2045 self.assertEqual(t.second, 0)
2046 self.assertEqual(t.microsecond, 999999)
2047 t = fts(0.9999999)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002048 self.assertEqual(t.second, 1)
2049 self.assertEqual(t.microsecond, 0)
Victor Stinneradfefa52015-09-04 23:57:25 +02002050 t = fts(1/2**7)
2051 self.assertEqual(t.second, 0)
Victor Stinner7667f582015-09-09 01:02:23 +02002052 self.assertEqual(t.microsecond, 7812)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002053
Victor Stinnerb67f0962017-02-10 10:34:02 +01002054 def test_timestamp_limits(self):
2055 # minimum timestamp
2056 min_dt = self.theclass.min.replace(tzinfo=timezone.utc)
2057 min_ts = min_dt.timestamp()
Victor Stinner6f37e362017-02-10 11:45:14 +01002058 try:
2059 # date 0001-01-01 00:00:00+00:00: timestamp=-62135596800
2060 self.assertEqual(self.theclass.fromtimestamp(min_ts, tz=timezone.utc),
2061 min_dt)
Victor Stinner2a35c932017-02-10 12:37:21 +01002062 except (OverflowError, OSError) as exc:
2063 # the date 0001-01-01 doesn't fit into 32-bit time_t,
2064 # or platform doesn't support such very old date
Victor Stinner6f37e362017-02-10 11:45:14 +01002065 self.skipTest(str(exc))
Victor Stinnerb67f0962017-02-10 10:34:02 +01002066
2067 # maximum timestamp: set seconds to zero to avoid rounding issues
2068 max_dt = self.theclass.max.replace(tzinfo=timezone.utc,
2069 second=0, microsecond=0)
2070 max_ts = max_dt.timestamp()
2071 # date 9999-12-31 23:59:00+00:00: timestamp 253402300740
2072 self.assertEqual(self.theclass.fromtimestamp(max_ts, tz=timezone.utc),
2073 max_dt)
2074
2075 # number of seconds greater than 1 year: make sure that the new date
2076 # is not valid in datetime.datetime limits
2077 delta = 3600 * 24 * 400
2078
2079 # too small
2080 ts = min_ts - delta
2081 # converting a Python int to C time_t can raise a OverflowError,
2082 # especially on 32-bit platforms.
2083 with self.assertRaises((ValueError, OverflowError)):
2084 self.theclass.fromtimestamp(ts)
2085 with self.assertRaises((ValueError, OverflowError)):
2086 self.theclass.utcfromtimestamp(ts)
2087
2088 # too big
2089 ts = max_dt.timestamp() + delta
2090 with self.assertRaises((ValueError, OverflowError)):
2091 self.theclass.fromtimestamp(ts)
2092 with self.assertRaises((ValueError, OverflowError)):
2093 self.theclass.utcfromtimestamp(ts)
2094
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002095 def test_insane_fromtimestamp(self):
2096 # It's possible that some platform maps time_t to double,
2097 # and that this test will fail there. This test should
2098 # exempt such platforms (provided they return reasonable
2099 # results!).
2100 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002101 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002102 insane)
2103
2104 def test_insane_utcfromtimestamp(self):
2105 # It's possible that some platform maps time_t to double,
2106 # and that this test will fail there. This test should
2107 # exempt such platforms (provided they return reasonable
2108 # results!).
2109 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002110 self.assertRaises(OverflowError, self.theclass.utcfromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002111 insane)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002112
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002113 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2114 def test_negative_float_fromtimestamp(self):
2115 # The result is tz-dependent; at least test that this doesn't
2116 # fail (like it did before bug 1646728 was fixed).
2117 self.theclass.fromtimestamp(-1.05)
2118
2119 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2120 def test_negative_float_utcfromtimestamp(self):
2121 d = self.theclass.utcfromtimestamp(-1.05)
2122 self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
2123
2124 def test_utcnow(self):
2125 import time
2126
2127 # Call it a success if utcnow() and utcfromtimestamp() are within
2128 # a second of each other.
2129 tolerance = timedelta(seconds=1)
2130 for dummy in range(3):
2131 from_now = self.theclass.utcnow()
2132 from_timestamp = self.theclass.utcfromtimestamp(time.time())
2133 if abs(from_timestamp - from_now) <= tolerance:
2134 break
2135 # Else try again a few times.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002136 self.assertLessEqual(abs(from_timestamp - from_now), tolerance)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002137
2138 def test_strptime(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002139 string = '2004-12-01 13:02:47.197'
2140 format = '%Y-%m-%d %H:%M:%S.%f'
2141 expected = _strptime._strptime_datetime(self.theclass, string, format)
2142 got = self.theclass.strptime(string, format)
2143 self.assertEqual(expected, got)
2144 self.assertIs(type(expected), self.theclass)
2145 self.assertIs(type(got), self.theclass)
2146
2147 strptime = self.theclass.strptime
2148 self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
2149 self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
2150 # Only local timezone and UTC are supported
2151 for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
2152 (-_time.timezone, _time.tzname[0])):
2153 if tzseconds < 0:
2154 sign = '-'
2155 seconds = -tzseconds
2156 else:
2157 sign ='+'
2158 seconds = tzseconds
2159 hours, minutes = divmod(seconds//60, 60)
2160 dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
Martin Panterfca22322015-11-16 09:22:19 +00002161 dt = strptime(dtstr, "%z %Z")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002162 self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
2163 self.assertEqual(dt.tzname(), tzname)
2164 # Can produce inconsistent datetime
2165 dtstr, fmt = "+1234 UTC", "%z %Z"
2166 dt = strptime(dtstr, fmt)
2167 self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE)
2168 self.assertEqual(dt.tzname(), 'UTC')
2169 # yet will roundtrip
2170 self.assertEqual(dt.strftime(fmt), dtstr)
2171
2172 # Produce naive datetime if no %z is provided
2173 self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
2174
2175 with self.assertRaises(ValueError): strptime("-2400", "%z")
2176 with self.assertRaises(ValueError): strptime("-000", "%z")
2177
2178 def test_more_timetuple(self):
2179 # This tests fields beyond those tested by the TestDate.test_timetuple.
2180 t = self.theclass(2004, 12, 31, 6, 22, 33)
2181 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
2182 self.assertEqual(t.timetuple(),
2183 (t.year, t.month, t.day,
2184 t.hour, t.minute, t.second,
2185 t.weekday(),
2186 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
2187 -1))
2188 tt = t.timetuple()
2189 self.assertEqual(tt.tm_year, t.year)
2190 self.assertEqual(tt.tm_mon, t.month)
2191 self.assertEqual(tt.tm_mday, t.day)
2192 self.assertEqual(tt.tm_hour, t.hour)
2193 self.assertEqual(tt.tm_min, t.minute)
2194 self.assertEqual(tt.tm_sec, t.second)
2195 self.assertEqual(tt.tm_wday, t.weekday())
2196 self.assertEqual(tt.tm_yday, t.toordinal() -
2197 date(t.year, 1, 1).toordinal() + 1)
2198 self.assertEqual(tt.tm_isdst, -1)
2199
2200 def test_more_strftime(self):
2201 # This tests fields beyond those tested by the TestDate.test_strftime.
2202 t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
2203 self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
2204 "12 31 04 000047 33 22 06 366")
Alexander Belopolsky018d3532017-07-31 10:26:50 -04002205 tz = timezone(-timedelta(hours=2, seconds=33, microseconds=123))
2206 t = t.replace(tzinfo=tz)
2207 self.assertEqual(t.strftime("%z"), "-020033.000123")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002208
2209 def test_extract(self):
2210 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2211 self.assertEqual(dt.date(), date(2002, 3, 4))
2212 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2213
2214 def test_combine(self):
2215 d = date(2002, 3, 4)
2216 t = time(18, 45, 3, 1234)
2217 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2218 combine = self.theclass.combine
2219 dt = combine(d, t)
2220 self.assertEqual(dt, expected)
2221
2222 dt = combine(time=t, date=d)
2223 self.assertEqual(dt, expected)
2224
2225 self.assertEqual(d, dt.date())
2226 self.assertEqual(t, dt.time())
2227 self.assertEqual(dt, combine(dt.date(), dt.time()))
2228
2229 self.assertRaises(TypeError, combine) # need an arg
2230 self.assertRaises(TypeError, combine, d) # need two args
2231 self.assertRaises(TypeError, combine, t, d) # args reversed
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002232 self.assertRaises(TypeError, combine, d, t, 1) # wrong tzinfo type
2233 self.assertRaises(TypeError, combine, d, t, 1, 2) # too many args
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002234 self.assertRaises(TypeError, combine, "date", "time") # wrong types
2235 self.assertRaises(TypeError, combine, d, "time") # wrong type
2236 self.assertRaises(TypeError, combine, "date", t) # wrong type
2237
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002238 # tzinfo= argument
2239 dt = combine(d, t, timezone.utc)
2240 self.assertIs(dt.tzinfo, timezone.utc)
2241 dt = combine(d, t, tzinfo=timezone.utc)
2242 self.assertIs(dt.tzinfo, timezone.utc)
2243 t = time()
2244 dt = combine(dt, t)
2245 self.assertEqual(dt.date(), d)
2246 self.assertEqual(dt.time(), t)
2247
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002248 def test_replace(self):
2249 cls = self.theclass
2250 args = [1, 2, 3, 4, 5, 6, 7]
2251 base = cls(*args)
2252 self.assertEqual(base, base.replace())
2253
2254 i = 0
2255 for name, newval in (("year", 2),
2256 ("month", 3),
2257 ("day", 4),
2258 ("hour", 5),
2259 ("minute", 6),
2260 ("second", 7),
2261 ("microsecond", 8)):
2262 newargs = args[:]
2263 newargs[i] = newval
2264 expected = cls(*newargs)
2265 got = base.replace(**{name: newval})
2266 self.assertEqual(expected, got)
2267 i += 1
2268
2269 # Out of bounds.
2270 base = cls(2000, 2, 29)
2271 self.assertRaises(ValueError, base.replace, year=2001)
2272
2273 def test_astimezone(self):
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002274 return # The rest is no longer applicable
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002275 # Pretty boring! The TZ test is more interesting here. astimezone()
2276 # simply can't be applied to a naive object.
2277 dt = self.theclass.now()
2278 f = FixedOffset(44, "")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04002279 self.assertRaises(ValueError, dt.astimezone) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002280 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
2281 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
2282 self.assertRaises(ValueError, dt.astimezone, f) # naive
2283 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
2284
2285 class Bogus(tzinfo):
2286 def utcoffset(self, dt): return None
2287 def dst(self, dt): return timedelta(0)
2288 bog = Bogus()
2289 self.assertRaises(ValueError, dt.astimezone, bog) # naive
2290 self.assertRaises(ValueError,
2291 dt.replace(tzinfo=bog).astimezone, f)
2292
2293 class AlsoBogus(tzinfo):
2294 def utcoffset(self, dt): return timedelta(0)
2295 def dst(self, dt): return None
2296 alsobog = AlsoBogus()
2297 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
2298
2299 def test_subclass_datetime(self):
2300
2301 class C(self.theclass):
2302 theAnswer = 42
2303
2304 def __new__(cls, *args, **kws):
2305 temp = kws.copy()
2306 extra = temp.pop('extra')
2307 result = self.theclass.__new__(cls, *args, **temp)
2308 result.extra = extra
2309 return result
2310
2311 def newmeth(self, start):
2312 return start + self.year + self.month + self.second
2313
2314 args = 2003, 4, 14, 12, 13, 41
2315
2316 dt1 = self.theclass(*args)
2317 dt2 = C(*args, **{'extra': 7})
2318
2319 self.assertEqual(dt2.__class__, C)
2320 self.assertEqual(dt2.theAnswer, 42)
2321 self.assertEqual(dt2.extra, 7)
2322 self.assertEqual(dt1.toordinal(), dt2.toordinal())
2323 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
2324 dt1.second - 7)
2325
2326class TestSubclassDateTime(TestDateTime):
2327 theclass = SubclassDatetime
2328 # Override tests not designed for subclass
Zachary Ware9fe6d862013-12-08 00:20:35 -06002329 @unittest.skip('not appropriate for subclasses')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002330 def test_roundtrip(self):
2331 pass
2332
2333class SubclassTime(time):
2334 sub_var = 1
2335
2336class TestTime(HarmlessMixedComparison, unittest.TestCase):
2337
2338 theclass = time
2339
2340 def test_basic_attributes(self):
2341 t = self.theclass(12, 0)
2342 self.assertEqual(t.hour, 12)
2343 self.assertEqual(t.minute, 0)
2344 self.assertEqual(t.second, 0)
2345 self.assertEqual(t.microsecond, 0)
2346
2347 def test_basic_attributes_nonzero(self):
2348 # Make sure all attributes are non-zero so bugs in
2349 # bit-shifting access show up.
2350 t = self.theclass(12, 59, 59, 8000)
2351 self.assertEqual(t.hour, 12)
2352 self.assertEqual(t.minute, 59)
2353 self.assertEqual(t.second, 59)
2354 self.assertEqual(t.microsecond, 8000)
2355
2356 def test_roundtrip(self):
2357 t = self.theclass(1, 2, 3, 4)
2358
2359 # Verify t -> string -> time identity.
2360 s = repr(t)
2361 self.assertTrue(s.startswith('datetime.'))
2362 s = s[9:]
2363 t2 = eval(s)
2364 self.assertEqual(t, t2)
2365
2366 # Verify identity via reconstructing from pieces.
2367 t2 = self.theclass(t.hour, t.minute, t.second,
2368 t.microsecond)
2369 self.assertEqual(t, t2)
2370
2371 def test_comparing(self):
2372 args = [1, 2, 3, 4]
2373 t1 = self.theclass(*args)
2374 t2 = self.theclass(*args)
2375 self.assertEqual(t1, t2)
2376 self.assertTrue(t1 <= t2)
2377 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002378 self.assertFalse(t1 != t2)
2379 self.assertFalse(t1 < t2)
2380 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002381
2382 for i in range(len(args)):
2383 newargs = args[:]
2384 newargs[i] = args[i] + 1
2385 t2 = self.theclass(*newargs) # this is larger than t1
2386 self.assertTrue(t1 < t2)
2387 self.assertTrue(t2 > t1)
2388 self.assertTrue(t1 <= t2)
2389 self.assertTrue(t2 >= t1)
2390 self.assertTrue(t1 != t2)
2391 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002392 self.assertFalse(t1 == t2)
2393 self.assertFalse(t2 == t1)
2394 self.assertFalse(t1 > t2)
2395 self.assertFalse(t2 < t1)
2396 self.assertFalse(t1 >= t2)
2397 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002398
2399 for badarg in OTHERSTUFF:
2400 self.assertEqual(t1 == badarg, False)
2401 self.assertEqual(t1 != badarg, True)
2402 self.assertEqual(badarg == t1, False)
2403 self.assertEqual(badarg != t1, True)
2404
2405 self.assertRaises(TypeError, lambda: t1 <= badarg)
2406 self.assertRaises(TypeError, lambda: t1 < badarg)
2407 self.assertRaises(TypeError, lambda: t1 > badarg)
2408 self.assertRaises(TypeError, lambda: t1 >= badarg)
2409 self.assertRaises(TypeError, lambda: badarg <= t1)
2410 self.assertRaises(TypeError, lambda: badarg < t1)
2411 self.assertRaises(TypeError, lambda: badarg > t1)
2412 self.assertRaises(TypeError, lambda: badarg >= t1)
2413
2414 def test_bad_constructor_arguments(self):
2415 # bad hours
2416 self.theclass(0, 0) # no exception
2417 self.theclass(23, 0) # no exception
2418 self.assertRaises(ValueError, self.theclass, -1, 0)
2419 self.assertRaises(ValueError, self.theclass, 24, 0)
2420 # bad minutes
2421 self.theclass(23, 0) # no exception
2422 self.theclass(23, 59) # no exception
2423 self.assertRaises(ValueError, self.theclass, 23, -1)
2424 self.assertRaises(ValueError, self.theclass, 23, 60)
2425 # bad seconds
2426 self.theclass(23, 59, 0) # no exception
2427 self.theclass(23, 59, 59) # no exception
2428 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
2429 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
2430 # bad microseconds
2431 self.theclass(23, 59, 59, 0) # no exception
2432 self.theclass(23, 59, 59, 999999) # no exception
2433 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
2434 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
2435
2436 def test_hash_equality(self):
2437 d = self.theclass(23, 30, 17)
2438 e = self.theclass(23, 30, 17)
2439 self.assertEqual(d, e)
2440 self.assertEqual(hash(d), hash(e))
2441
2442 dic = {d: 1}
2443 dic[e] = 2
2444 self.assertEqual(len(dic), 1)
2445 self.assertEqual(dic[d], 2)
2446 self.assertEqual(dic[e], 2)
2447
2448 d = self.theclass(0, 5, 17)
2449 e = self.theclass(0, 5, 17)
2450 self.assertEqual(d, e)
2451 self.assertEqual(hash(d), hash(e))
2452
2453 dic = {d: 1}
2454 dic[e] = 2
2455 self.assertEqual(len(dic), 1)
2456 self.assertEqual(dic[d], 2)
2457 self.assertEqual(dic[e], 2)
2458
2459 def test_isoformat(self):
2460 t = self.theclass(4, 5, 1, 123)
2461 self.assertEqual(t.isoformat(), "04:05:01.000123")
2462 self.assertEqual(t.isoformat(), str(t))
2463
2464 t = self.theclass()
2465 self.assertEqual(t.isoformat(), "00:00:00")
2466 self.assertEqual(t.isoformat(), str(t))
2467
2468 t = self.theclass(microsecond=1)
2469 self.assertEqual(t.isoformat(), "00:00:00.000001")
2470 self.assertEqual(t.isoformat(), str(t))
2471
2472 t = self.theclass(microsecond=10)
2473 self.assertEqual(t.isoformat(), "00:00:00.000010")
2474 self.assertEqual(t.isoformat(), str(t))
2475
2476 t = self.theclass(microsecond=100)
2477 self.assertEqual(t.isoformat(), "00:00:00.000100")
2478 self.assertEqual(t.isoformat(), str(t))
2479
2480 t = self.theclass(microsecond=1000)
2481 self.assertEqual(t.isoformat(), "00:00:00.001000")
2482 self.assertEqual(t.isoformat(), str(t))
2483
2484 t = self.theclass(microsecond=10000)
2485 self.assertEqual(t.isoformat(), "00:00:00.010000")
2486 self.assertEqual(t.isoformat(), str(t))
2487
2488 t = self.theclass(microsecond=100000)
2489 self.assertEqual(t.isoformat(), "00:00:00.100000")
2490 self.assertEqual(t.isoformat(), str(t))
2491
Alexander Belopolskya2998a62016-03-06 14:58:43 -05002492 t = self.theclass(hour=12, minute=34, second=56, microsecond=123456)
2493 self.assertEqual(t.isoformat(timespec='hours'), "12")
2494 self.assertEqual(t.isoformat(timespec='minutes'), "12:34")
2495 self.assertEqual(t.isoformat(timespec='seconds'), "12:34:56")
2496 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.123")
2497 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.123456")
2498 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56.123456")
2499 self.assertRaises(ValueError, t.isoformat, timespec='monkey')
2500
2501 t = self.theclass(hour=12, minute=34, second=56, microsecond=999500)
2502 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.999")
2503
2504 t = self.theclass(hour=12, minute=34, second=56, microsecond=0)
2505 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.000")
2506 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.000000")
2507 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56")
2508
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002509 def test_1653736(self):
2510 # verify it doesn't accept extra keyword arguments
2511 t = self.theclass(second=1)
2512 self.assertRaises(TypeError, t.isoformat, foo=3)
2513
2514 def test_strftime(self):
2515 t = self.theclass(1, 2, 3, 4)
2516 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
2517 # A naive object replaces %z and %Z with empty strings.
2518 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
2519
2520 def test_format(self):
2521 t = self.theclass(1, 2, 3, 4)
2522 self.assertEqual(t.__format__(''), str(t))
2523
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02002524 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002525 t.__format__(123)
2526
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002527 # check that a derived class's __str__() gets called
2528 class A(self.theclass):
2529 def __str__(self):
2530 return 'A'
2531 a = A(1, 2, 3, 4)
2532 self.assertEqual(a.__format__(''), 'A')
2533
2534 # check that a derived class's strftime gets called
2535 class B(self.theclass):
2536 def strftime(self, format_spec):
2537 return 'B'
2538 b = B(1, 2, 3, 4)
2539 self.assertEqual(b.__format__(''), str(t))
2540
2541 for fmt in ['%H %M %S',
2542 ]:
2543 self.assertEqual(t.__format__(fmt), t.strftime(fmt))
2544 self.assertEqual(a.__format__(fmt), t.strftime(fmt))
2545 self.assertEqual(b.__format__(fmt), 'B')
2546
2547 def test_str(self):
2548 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
2549 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
2550 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
2551 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
2552 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
2553
2554 def test_repr(self):
2555 name = 'datetime.' + self.theclass.__name__
2556 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
2557 "%s(1, 2, 3, 4)" % name)
2558 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
2559 "%s(10, 2, 3, 4000)" % name)
2560 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
2561 "%s(0, 2, 3, 400000)" % name)
2562 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
2563 "%s(12, 2, 3)" % name)
2564 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
2565 "%s(23, 15)" % name)
2566
2567 def test_resolution_info(self):
2568 self.assertIsInstance(self.theclass.min, self.theclass)
2569 self.assertIsInstance(self.theclass.max, self.theclass)
2570 self.assertIsInstance(self.theclass.resolution, timedelta)
2571 self.assertTrue(self.theclass.max > self.theclass.min)
2572
2573 def test_pickling(self):
2574 args = 20, 59, 16, 64**2
2575 orig = self.theclass(*args)
2576 for pickler, unpickler, proto in pickle_choices:
2577 green = pickler.dumps(orig, proto)
2578 derived = unpickler.loads(green)
2579 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02002580 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002581
2582 def test_pickling_subclass_time(self):
2583 args = 20, 59, 16, 64**2
2584 orig = SubclassTime(*args)
2585 for pickler, unpickler, proto in pickle_choices:
2586 green = pickler.dumps(orig, proto)
2587 derived = unpickler.loads(green)
2588 self.assertEqual(orig, derived)
2589
2590 def test_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002591 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002592 cls = self.theclass
2593 self.assertTrue(cls(1))
2594 self.assertTrue(cls(0, 1))
2595 self.assertTrue(cls(0, 0, 1))
2596 self.assertTrue(cls(0, 0, 0, 1))
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002597 self.assertTrue(cls(0))
2598 self.assertTrue(cls())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002599
2600 def test_replace(self):
2601 cls = self.theclass
2602 args = [1, 2, 3, 4]
2603 base = cls(*args)
2604 self.assertEqual(base, base.replace())
2605
2606 i = 0
2607 for name, newval in (("hour", 5),
2608 ("minute", 6),
2609 ("second", 7),
2610 ("microsecond", 8)):
2611 newargs = args[:]
2612 newargs[i] = newval
2613 expected = cls(*newargs)
2614 got = base.replace(**{name: newval})
2615 self.assertEqual(expected, got)
2616 i += 1
2617
2618 # Out of bounds.
2619 base = cls(1)
2620 self.assertRaises(ValueError, base.replace, hour=24)
2621 self.assertRaises(ValueError, base.replace, minute=-1)
2622 self.assertRaises(ValueError, base.replace, second=100)
2623 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2624
2625 def test_subclass_time(self):
2626
2627 class C(self.theclass):
2628 theAnswer = 42
2629
2630 def __new__(cls, *args, **kws):
2631 temp = kws.copy()
2632 extra = temp.pop('extra')
2633 result = self.theclass.__new__(cls, *args, **temp)
2634 result.extra = extra
2635 return result
2636
2637 def newmeth(self, start):
2638 return start + self.hour + self.second
2639
2640 args = 4, 5, 6
2641
2642 dt1 = self.theclass(*args)
2643 dt2 = C(*args, **{'extra': 7})
2644
2645 self.assertEqual(dt2.__class__, C)
2646 self.assertEqual(dt2.theAnswer, 42)
2647 self.assertEqual(dt2.extra, 7)
2648 self.assertEqual(dt1.isoformat(), dt2.isoformat())
2649 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2650
2651 def test_backdoor_resistance(self):
2652 # see TestDate.test_backdoor_resistance().
2653 base = '2:59.0'
2654 for hour_byte in ' ', '9', chr(24), '\xff':
2655 self.assertRaises(TypeError, self.theclass,
2656 hour_byte + base[1:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002657 # Good bytes, but bad tzinfo:
2658 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
2659 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002660
2661# A mixin for classes with a tzinfo= argument. Subclasses must define
Martin Panter46f50722016-05-26 05:35:26 +00002662# theclass as a class attribute, and theclass(1, 1, 1, tzinfo=whatever)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002663# must be legit (which is true for time and datetime).
2664class TZInfoBase:
2665
2666 def test_argument_passing(self):
2667 cls = self.theclass
2668 # A datetime passes itself on, a time passes None.
2669 class introspective(tzinfo):
2670 def tzname(self, dt): return dt and "real" or "none"
2671 def utcoffset(self, dt):
2672 return timedelta(minutes = dt and 42 or -42)
2673 dst = utcoffset
2674
2675 obj = cls(1, 2, 3, tzinfo=introspective())
2676
2677 expected = cls is time and "none" or "real"
2678 self.assertEqual(obj.tzname(), expected)
2679
2680 expected = timedelta(minutes=(cls is time and -42 or 42))
2681 self.assertEqual(obj.utcoffset(), expected)
2682 self.assertEqual(obj.dst(), expected)
2683
2684 def test_bad_tzinfo_classes(self):
2685 cls = self.theclass
2686 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
2687
2688 class NiceTry(object):
2689 def __init__(self): pass
2690 def utcoffset(self, dt): pass
2691 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
2692
2693 class BetterTry(tzinfo):
2694 def __init__(self): pass
2695 def utcoffset(self, dt): pass
2696 b = BetterTry()
2697 t = cls(1, 1, 1, tzinfo=b)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002698 self.assertIs(t.tzinfo, b)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002699
2700 def test_utc_offset_out_of_bounds(self):
2701 class Edgy(tzinfo):
2702 def __init__(self, offset):
2703 self.offset = timedelta(minutes=offset)
2704 def utcoffset(self, dt):
2705 return self.offset
2706
2707 cls = self.theclass
2708 for offset, legit in ((-1440, False),
2709 (-1439, True),
2710 (1439, True),
2711 (1440, False)):
2712 if cls is time:
2713 t = cls(1, 2, 3, tzinfo=Edgy(offset))
2714 elif cls is datetime:
2715 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
2716 else:
2717 assert 0, "impossible"
2718 if legit:
2719 aofs = abs(offset)
2720 h, m = divmod(aofs, 60)
2721 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
2722 if isinstance(t, datetime):
2723 t = t.timetz()
2724 self.assertEqual(str(t), "01:02:03" + tag)
2725 else:
2726 self.assertRaises(ValueError, str, t)
2727
2728 def test_tzinfo_classes(self):
2729 cls = self.theclass
2730 class C1(tzinfo):
2731 def utcoffset(self, dt): return None
2732 def dst(self, dt): return None
2733 def tzname(self, dt): return None
2734 for t in (cls(1, 1, 1),
2735 cls(1, 1, 1, tzinfo=None),
2736 cls(1, 1, 1, tzinfo=C1())):
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002737 self.assertIsNone(t.utcoffset())
2738 self.assertIsNone(t.dst())
2739 self.assertIsNone(t.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002740
2741 class C3(tzinfo):
2742 def utcoffset(self, dt): return timedelta(minutes=-1439)
2743 def dst(self, dt): return timedelta(minutes=1439)
2744 def tzname(self, dt): return "aname"
2745 t = cls(1, 1, 1, tzinfo=C3())
2746 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
2747 self.assertEqual(t.dst(), timedelta(minutes=1439))
2748 self.assertEqual(t.tzname(), "aname")
2749
2750 # Wrong types.
2751 class C4(tzinfo):
2752 def utcoffset(self, dt): return "aname"
2753 def dst(self, dt): return 7
2754 def tzname(self, dt): return 0
2755 t = cls(1, 1, 1, tzinfo=C4())
2756 self.assertRaises(TypeError, t.utcoffset)
2757 self.assertRaises(TypeError, t.dst)
2758 self.assertRaises(TypeError, t.tzname)
2759
2760 # Offset out of range.
2761 class C6(tzinfo):
2762 def utcoffset(self, dt): return timedelta(hours=-24)
2763 def dst(self, dt): return timedelta(hours=24)
2764 t = cls(1, 1, 1, tzinfo=C6())
2765 self.assertRaises(ValueError, t.utcoffset)
2766 self.assertRaises(ValueError, t.dst)
2767
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002768 # Not a whole number of seconds.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002769 class C7(tzinfo):
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002770 def utcoffset(self, dt): return timedelta(microseconds=61)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002771 def dst(self, dt): return timedelta(microseconds=-81)
2772 t = cls(1, 1, 1, tzinfo=C7())
Alexander Belopolsky018d3532017-07-31 10:26:50 -04002773 self.assertEqual(t.utcoffset(), timedelta(microseconds=61))
2774 self.assertEqual(t.dst(), timedelta(microseconds=-81))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002775
2776 def test_aware_compare(self):
2777 cls = self.theclass
2778
2779 # Ensure that utcoffset() gets ignored if the comparands have
2780 # the same tzinfo member.
2781 class OperandDependentOffset(tzinfo):
2782 def utcoffset(self, t):
2783 if t.minute < 10:
2784 # d0 and d1 equal after adjustment
2785 return timedelta(minutes=t.minute)
2786 else:
2787 # d2 off in the weeds
2788 return timedelta(minutes=59)
2789
2790 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
2791 d0 = base.replace(minute=3)
2792 d1 = base.replace(minute=9)
2793 d2 = base.replace(minute=11)
2794 for x in d0, d1, d2:
2795 for y in d0, d1, d2:
2796 for op in lt, le, gt, ge, eq, ne:
2797 got = op(x, y)
2798 expected = op(x.minute, y.minute)
2799 self.assertEqual(got, expected)
2800
2801 # However, if they're different members, uctoffset is not ignored.
2802 # Note that a time can't actually have an operand-depedent offset,
2803 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
2804 # so skip this test for time.
2805 if cls is not time:
2806 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2807 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2808 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2809 for x in d0, d1, d2:
2810 for y in d0, d1, d2:
2811 got = (x > y) - (x < y)
2812 if (x is d0 or x is d1) and (y is d0 or y is d1):
2813 expected = 0
2814 elif x is y is d2:
2815 expected = 0
2816 elif x is d2:
2817 expected = -1
2818 else:
2819 assert y is d2
2820 expected = 1
2821 self.assertEqual(got, expected)
2822
2823
2824# Testing time objects with a non-None tzinfo.
2825class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
2826 theclass = time
2827
2828 def test_empty(self):
2829 t = self.theclass()
2830 self.assertEqual(t.hour, 0)
2831 self.assertEqual(t.minute, 0)
2832 self.assertEqual(t.second, 0)
2833 self.assertEqual(t.microsecond, 0)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002834 self.assertIsNone(t.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002835
2836 def test_zones(self):
2837 est = FixedOffset(-300, "EST", 1)
2838 utc = FixedOffset(0, "UTC", -2)
2839 met = FixedOffset(60, "MET", 3)
2840 t1 = time( 7, 47, tzinfo=est)
2841 t2 = time(12, 47, tzinfo=utc)
2842 t3 = time(13, 47, tzinfo=met)
2843 t4 = time(microsecond=40)
2844 t5 = time(microsecond=40, tzinfo=utc)
2845
2846 self.assertEqual(t1.tzinfo, est)
2847 self.assertEqual(t2.tzinfo, utc)
2848 self.assertEqual(t3.tzinfo, met)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002849 self.assertIsNone(t4.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002850 self.assertEqual(t5.tzinfo, utc)
2851
2852 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2853 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2854 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002855 self.assertIsNone(t4.utcoffset())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002856 self.assertRaises(TypeError, t1.utcoffset, "no args")
2857
2858 self.assertEqual(t1.tzname(), "EST")
2859 self.assertEqual(t2.tzname(), "UTC")
2860 self.assertEqual(t3.tzname(), "MET")
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002861 self.assertIsNone(t4.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002862 self.assertRaises(TypeError, t1.tzname, "no args")
2863
2864 self.assertEqual(t1.dst(), timedelta(minutes=1))
2865 self.assertEqual(t2.dst(), timedelta(minutes=-2))
2866 self.assertEqual(t3.dst(), timedelta(minutes=3))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002867 self.assertIsNone(t4.dst())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002868 self.assertRaises(TypeError, t1.dst, "no args")
2869
2870 self.assertEqual(hash(t1), hash(t2))
2871 self.assertEqual(hash(t1), hash(t3))
2872 self.assertEqual(hash(t2), hash(t3))
2873
2874 self.assertEqual(t1, t2)
2875 self.assertEqual(t1, t3)
2876 self.assertEqual(t2, t3)
Alexander Belopolsky08313822012-06-15 20:19:47 -04002877 self.assertNotEqual(t4, t5) # mixed tz-aware & naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002878 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
2879 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
2880
2881 self.assertEqual(str(t1), "07:47:00-05:00")
2882 self.assertEqual(str(t2), "12:47:00+00:00")
2883 self.assertEqual(str(t3), "13:47:00+01:00")
2884 self.assertEqual(str(t4), "00:00:00.000040")
2885 self.assertEqual(str(t5), "00:00:00.000040+00:00")
2886
2887 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
2888 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
2889 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
2890 self.assertEqual(t4.isoformat(), "00:00:00.000040")
2891 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
2892
2893 d = 'datetime.time'
2894 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
2895 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
2896 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
2897 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
2898 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
2899
2900 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
2901 "07:47:00 %Z=EST %z=-0500")
2902 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
2903 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
2904
2905 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
2906 t1 = time(23, 59, tzinfo=yuck)
2907 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
2908 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
2909
2910 # Check that an invalid tzname result raises an exception.
2911 class Badtzname(tzinfo):
Alexander Belopolskye239d232010-12-08 23:31:48 +00002912 tz = 42
2913 def tzname(self, dt): return self.tz
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002914 t = time(2, 3, 4, tzinfo=Badtzname())
2915 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
2916 self.assertRaises(TypeError, t.strftime, "%Z")
2917
Alexander Belopolskye239d232010-12-08 23:31:48 +00002918 # Issue #6697:
Utkarsh Upadhyay287c5592017-07-21 02:14:54 +02002919 if '_Fast' in self.__class__.__name__:
Alexander Belopolskye239d232010-12-08 23:31:48 +00002920 Badtzname.tz = '\ud800'
2921 self.assertRaises(ValueError, t.strftime, "%Z")
2922
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002923 def test_hash_edge_cases(self):
2924 # Offsets that overflow a basic time.
2925 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2926 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2927 self.assertEqual(hash(t1), hash(t2))
2928
2929 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2930 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2931 self.assertEqual(hash(t1), hash(t2))
2932
2933 def test_pickling(self):
2934 # Try one without a tzinfo.
2935 args = 20, 59, 16, 64**2
2936 orig = self.theclass(*args)
2937 for pickler, unpickler, proto in pickle_choices:
2938 green = pickler.dumps(orig, proto)
2939 derived = unpickler.loads(green)
2940 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02002941 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002942
2943 # Try one with a tzinfo.
2944 tinfo = PicklableFixedOffset(-300, 'cookie')
2945 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
2946 for pickler, unpickler, proto in pickle_choices:
2947 green = pickler.dumps(orig, proto)
2948 derived = unpickler.loads(green)
2949 self.assertEqual(orig, derived)
2950 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
2951 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2952 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02002953 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002954
2955 def test_more_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002956 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002957 cls = self.theclass
2958
2959 t = cls(0, tzinfo=FixedOffset(-300, ""))
2960 self.assertTrue(t)
2961
2962 t = cls(5, tzinfo=FixedOffset(-300, ""))
2963 self.assertTrue(t)
2964
2965 t = cls(5, tzinfo=FixedOffset(300, ""))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002966 self.assertTrue(t)
2967
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002968 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2969 self.assertTrue(t)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002970
2971 def test_replace(self):
2972 cls = self.theclass
2973 z100 = FixedOffset(100, "+100")
2974 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2975 args = [1, 2, 3, 4, z100]
2976 base = cls(*args)
2977 self.assertEqual(base, base.replace())
2978
2979 i = 0
2980 for name, newval in (("hour", 5),
2981 ("minute", 6),
2982 ("second", 7),
2983 ("microsecond", 8),
2984 ("tzinfo", zm200)):
2985 newargs = args[:]
2986 newargs[i] = newval
2987 expected = cls(*newargs)
2988 got = base.replace(**{name: newval})
2989 self.assertEqual(expected, got)
2990 i += 1
2991
2992 # Ensure we can get rid of a tzinfo.
2993 self.assertEqual(base.tzname(), "+100")
2994 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002995 self.assertIsNone(base2.tzinfo)
2996 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002997
2998 # Ensure we can add one.
2999 base3 = base2.replace(tzinfo=z100)
3000 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003001 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003002
3003 # Out of bounds.
3004 base = cls(1)
3005 self.assertRaises(ValueError, base.replace, hour=24)
3006 self.assertRaises(ValueError, base.replace, minute=-1)
3007 self.assertRaises(ValueError, base.replace, second=100)
3008 self.assertRaises(ValueError, base.replace, microsecond=1000000)
3009
3010 def test_mixed_compare(self):
3011 t1 = time(1, 2, 3)
3012 t2 = time(1, 2, 3)
3013 self.assertEqual(t1, t2)
3014 t2 = t2.replace(tzinfo=None)
3015 self.assertEqual(t1, t2)
3016 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3017 self.assertEqual(t1, t2)
3018 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04003019 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003020
3021 # In time w/ identical tzinfo objects, utcoffset is ignored.
3022 class Varies(tzinfo):
3023 def __init__(self):
3024 self.offset = timedelta(minutes=22)
3025 def utcoffset(self, t):
3026 self.offset += timedelta(minutes=1)
3027 return self.offset
3028
3029 v = Varies()
3030 t1 = t2.replace(tzinfo=v)
3031 t2 = t2.replace(tzinfo=v)
3032 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3033 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3034 self.assertEqual(t1, t2)
3035
3036 # But if they're not identical, it isn't ignored.
3037 t2 = t2.replace(tzinfo=Varies())
3038 self.assertTrue(t1 < t2) # t1's offset counter still going up
3039
3040 def test_subclass_timetz(self):
3041
3042 class C(self.theclass):
3043 theAnswer = 42
3044
3045 def __new__(cls, *args, **kws):
3046 temp = kws.copy()
3047 extra = temp.pop('extra')
3048 result = self.theclass.__new__(cls, *args, **temp)
3049 result.extra = extra
3050 return result
3051
3052 def newmeth(self, start):
3053 return start + self.hour + self.second
3054
3055 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3056
3057 dt1 = self.theclass(*args)
3058 dt2 = C(*args, **{'extra': 7})
3059
3060 self.assertEqual(dt2.__class__, C)
3061 self.assertEqual(dt2.theAnswer, 42)
3062 self.assertEqual(dt2.extra, 7)
3063 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3064 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
3065
3066
3067# Testing datetime objects with a non-None tzinfo.
3068
3069class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
3070 theclass = datetime
3071
3072 def test_trivial(self):
3073 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
3074 self.assertEqual(dt.year, 1)
3075 self.assertEqual(dt.month, 2)
3076 self.assertEqual(dt.day, 3)
3077 self.assertEqual(dt.hour, 4)
3078 self.assertEqual(dt.minute, 5)
3079 self.assertEqual(dt.second, 6)
3080 self.assertEqual(dt.microsecond, 7)
3081 self.assertEqual(dt.tzinfo, None)
3082
3083 def test_even_more_compare(self):
3084 # The test_compare() and test_more_compare() inherited from TestDate
3085 # and TestDateTime covered non-tzinfo cases.
3086
3087 # Smallest possible after UTC adjustment.
3088 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
3089 # Largest possible after UTC adjustment.
3090 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3091 tzinfo=FixedOffset(-1439, ""))
3092
3093 # Make sure those compare correctly, and w/o overflow.
3094 self.assertTrue(t1 < t2)
3095 self.assertTrue(t1 != t2)
3096 self.assertTrue(t2 > t1)
3097
3098 self.assertEqual(t1, t1)
3099 self.assertEqual(t2, t2)
3100
3101 # Equal afer adjustment.
3102 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
3103 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
3104 self.assertEqual(t1, t2)
3105
3106 # Change t1 not to subtract a minute, and t1 should be larger.
3107 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
3108 self.assertTrue(t1 > t2)
3109
3110 # Change t1 to subtract 2 minutes, and t1 should be smaller.
3111 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
3112 self.assertTrue(t1 < t2)
3113
3114 # Back to the original t1, but make seconds resolve it.
3115 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3116 second=1)
3117 self.assertTrue(t1 > t2)
3118
3119 # Likewise, but make microseconds resolve it.
3120 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3121 microsecond=1)
3122 self.assertTrue(t1 > t2)
3123
Alexander Belopolsky08313822012-06-15 20:19:47 -04003124 # Make t2 naive and it should differ.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003125 t2 = self.theclass.min
Alexander Belopolsky08313822012-06-15 20:19:47 -04003126 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003127 self.assertEqual(t2, t2)
3128
3129 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
3130 class Naive(tzinfo):
3131 def utcoffset(self, dt): return None
3132 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
Alexander Belopolsky08313822012-06-15 20:19:47 -04003133 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003134 self.assertEqual(t2, t2)
3135
3136 # OTOH, it's OK to compare two of these mixing the two ways of being
3137 # naive.
3138 t1 = self.theclass(5, 6, 7)
3139 self.assertEqual(t1, t2)
3140
3141 # Try a bogus uctoffset.
3142 class Bogus(tzinfo):
3143 def utcoffset(self, dt):
3144 return timedelta(minutes=1440) # out of bounds
3145 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
3146 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
3147 self.assertRaises(ValueError, lambda: t1 == t2)
3148
3149 def test_pickling(self):
3150 # Try one without a tzinfo.
3151 args = 6, 7, 23, 20, 59, 1, 64**2
3152 orig = self.theclass(*args)
3153 for pickler, unpickler, proto in pickle_choices:
3154 green = pickler.dumps(orig, proto)
3155 derived = unpickler.loads(green)
3156 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003157 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003158
3159 # Try one with a tzinfo.
3160 tinfo = PicklableFixedOffset(-300, 'cookie')
3161 orig = self.theclass(*args, **{'tzinfo': tinfo})
3162 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
3163 for pickler, unpickler, proto in pickle_choices:
3164 green = pickler.dumps(orig, proto)
3165 derived = unpickler.loads(green)
3166 self.assertEqual(orig, derived)
3167 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3168 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3169 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003170 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003171
3172 def test_extreme_hashes(self):
3173 # If an attempt is made to hash these via subtracting the offset
3174 # then hashing a datetime object, OverflowError results. The
3175 # Python implementation used to blow up here.
3176 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
3177 hash(t)
3178 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3179 tzinfo=FixedOffset(-1439, ""))
3180 hash(t)
3181
3182 # OTOH, an OOB offset should blow up.
3183 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
3184 self.assertRaises(ValueError, hash, t)
3185
3186 def test_zones(self):
3187 est = FixedOffset(-300, "EST")
3188 utc = FixedOffset(0, "UTC")
3189 met = FixedOffset(60, "MET")
3190 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
3191 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
3192 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
3193 self.assertEqual(t1.tzinfo, est)
3194 self.assertEqual(t2.tzinfo, utc)
3195 self.assertEqual(t3.tzinfo, met)
3196 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
3197 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
3198 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
3199 self.assertEqual(t1.tzname(), "EST")
3200 self.assertEqual(t2.tzname(), "UTC")
3201 self.assertEqual(t3.tzname(), "MET")
3202 self.assertEqual(hash(t1), hash(t2))
3203 self.assertEqual(hash(t1), hash(t3))
3204 self.assertEqual(hash(t2), hash(t3))
3205 self.assertEqual(t1, t2)
3206 self.assertEqual(t1, t3)
3207 self.assertEqual(t2, t3)
3208 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
3209 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
3210 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
3211 d = 'datetime.datetime(2002, 3, 19, '
3212 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
3213 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
3214 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
3215
3216 def test_combine(self):
3217 met = FixedOffset(60, "MET")
3218 d = date(2002, 3, 4)
3219 tz = time(18, 45, 3, 1234, tzinfo=met)
3220 dt = datetime.combine(d, tz)
3221 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
3222 tzinfo=met))
3223
3224 def test_extract(self):
3225 met = FixedOffset(60, "MET")
3226 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
3227 self.assertEqual(dt.date(), date(2002, 3, 4))
3228 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
3229 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
3230
3231 def test_tz_aware_arithmetic(self):
3232 import random
3233
3234 now = self.theclass.now()
3235 tz55 = FixedOffset(-330, "west 5:30")
3236 timeaware = now.time().replace(tzinfo=tz55)
3237 nowaware = self.theclass.combine(now.date(), timeaware)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003238 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003239 self.assertEqual(nowaware.timetz(), timeaware)
3240
3241 # Can't mix aware and non-aware.
3242 self.assertRaises(TypeError, lambda: now - nowaware)
3243 self.assertRaises(TypeError, lambda: nowaware - now)
3244
3245 # And adding datetime's doesn't make sense, aware or not.
3246 self.assertRaises(TypeError, lambda: now + nowaware)
3247 self.assertRaises(TypeError, lambda: nowaware + now)
3248 self.assertRaises(TypeError, lambda: nowaware + nowaware)
3249
3250 # Subtracting should yield 0.
3251 self.assertEqual(now - now, timedelta(0))
3252 self.assertEqual(nowaware - nowaware, timedelta(0))
3253
3254 # Adding a delta should preserve tzinfo.
3255 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
3256 nowawareplus = nowaware + delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003257 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003258 nowawareplus2 = delta + nowaware
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003259 self.assertIs(nowawareplus2.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003260 self.assertEqual(nowawareplus, nowawareplus2)
3261
3262 # that - delta should be what we started with, and that - what we
3263 # started with should be delta.
3264 diff = nowawareplus - delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003265 self.assertIs(diff.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003266 self.assertEqual(nowaware, diff)
3267 self.assertRaises(TypeError, lambda: delta - nowawareplus)
3268 self.assertEqual(nowawareplus - nowaware, delta)
3269
3270 # Make up a random timezone.
3271 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
3272 # Attach it to nowawareplus.
3273 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003274 self.assertIs(nowawareplus.tzinfo, tzr)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003275 # Make sure the difference takes the timezone adjustments into account.
3276 got = nowaware - nowawareplus
3277 # Expected: (nowaware base - nowaware offset) -
3278 # (nowawareplus base - nowawareplus offset) =
3279 # (nowaware base - nowawareplus base) +
3280 # (nowawareplus offset - nowaware offset) =
3281 # -delta + nowawareplus offset - nowaware offset
3282 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
3283 self.assertEqual(got, expected)
3284
3285 # Try max possible difference.
3286 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
3287 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3288 tzinfo=FixedOffset(-1439, "max"))
3289 maxdiff = max - min
3290 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
3291 timedelta(minutes=2*1439))
3292 # Different tzinfo, but the same offset
3293 tza = timezone(HOUR, 'A')
3294 tzb = timezone(HOUR, 'B')
3295 delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
3296 self.assertEqual(delta, self.theclass.min - self.theclass.max)
3297
3298 def test_tzinfo_now(self):
3299 meth = self.theclass.now
3300 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3301 base = meth()
3302 # Try with and without naming the keyword.
3303 off42 = FixedOffset(42, "42")
3304 another = meth(off42)
3305 again = meth(tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003306 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003307 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3308 # Bad argument with and w/o naming the keyword.
3309 self.assertRaises(TypeError, meth, 16)
3310 self.assertRaises(TypeError, meth, tzinfo=16)
3311 # Bad keyword name.
3312 self.assertRaises(TypeError, meth, tinfo=off42)
3313 # Too many args.
3314 self.assertRaises(TypeError, meth, off42, off42)
3315
3316 # We don't know which time zone we're in, and don't have a tzinfo
3317 # class to represent it, so seeing whether a tz argument actually
3318 # does a conversion is tricky.
3319 utc = FixedOffset(0, "utc", 0)
3320 for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
3321 timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
3322 for dummy in range(3):
3323 now = datetime.now(weirdtz)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003324 self.assertIs(now.tzinfo, weirdtz)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003325 utcnow = datetime.utcnow().replace(tzinfo=utc)
3326 now2 = utcnow.astimezone(weirdtz)
3327 if abs(now - now2) < timedelta(seconds=30):
3328 break
3329 # Else the code is broken, or more than 30 seconds passed between
3330 # calls; assuming the latter, just try again.
3331 else:
3332 # Three strikes and we're out.
3333 self.fail("utcnow(), now(tz), or astimezone() may be broken")
3334
3335 def test_tzinfo_fromtimestamp(self):
3336 import time
3337 meth = self.theclass.fromtimestamp
3338 ts = time.time()
3339 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3340 base = meth(ts)
3341 # Try with and without naming the keyword.
3342 off42 = FixedOffset(42, "42")
3343 another = meth(ts, off42)
3344 again = meth(ts, tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003345 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003346 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3347 # Bad argument with and w/o naming the keyword.
3348 self.assertRaises(TypeError, meth, ts, 16)
3349 self.assertRaises(TypeError, meth, ts, tzinfo=16)
3350 # Bad keyword name.
3351 self.assertRaises(TypeError, meth, ts, tinfo=off42)
3352 # Too many args.
3353 self.assertRaises(TypeError, meth, ts, off42, off42)
3354 # Too few args.
3355 self.assertRaises(TypeError, meth)
3356
3357 # Try to make sure tz= actually does some conversion.
3358 timestamp = 1000000000
3359 utcdatetime = datetime.utcfromtimestamp(timestamp)
3360 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
3361 # But on some flavor of Mac, it's nowhere near that. So we can't have
3362 # any idea here what time that actually is, we can only test that
3363 # relative changes match.
3364 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
3365 tz = FixedOffset(utcoffset, "tz", 0)
3366 expected = utcdatetime + utcoffset
3367 got = datetime.fromtimestamp(timestamp, tz)
3368 self.assertEqual(expected, got.replace(tzinfo=None))
3369
3370 def test_tzinfo_utcnow(self):
3371 meth = self.theclass.utcnow
3372 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3373 base = meth()
3374 # Try with and without naming the keyword; for whatever reason,
3375 # utcnow() doesn't accept a tzinfo argument.
3376 off42 = FixedOffset(42, "42")
3377 self.assertRaises(TypeError, meth, off42)
3378 self.assertRaises(TypeError, meth, tzinfo=off42)
3379
3380 def test_tzinfo_utcfromtimestamp(self):
3381 import time
3382 meth = self.theclass.utcfromtimestamp
3383 ts = time.time()
3384 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3385 base = meth(ts)
3386 # Try with and without naming the keyword; for whatever reason,
3387 # utcfromtimestamp() doesn't accept a tzinfo argument.
3388 off42 = FixedOffset(42, "42")
3389 self.assertRaises(TypeError, meth, ts, off42)
3390 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
3391
3392 def test_tzinfo_timetuple(self):
3393 # TestDateTime tested most of this. datetime adds a twist to the
3394 # DST flag.
3395 class DST(tzinfo):
3396 def __init__(self, dstvalue):
3397 if isinstance(dstvalue, int):
3398 dstvalue = timedelta(minutes=dstvalue)
3399 self.dstvalue = dstvalue
3400 def dst(self, dt):
3401 return self.dstvalue
3402
3403 cls = self.theclass
3404 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
3405 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
3406 t = d.timetuple()
3407 self.assertEqual(1, t.tm_year)
3408 self.assertEqual(1, t.tm_mon)
3409 self.assertEqual(1, t.tm_mday)
3410 self.assertEqual(10, t.tm_hour)
3411 self.assertEqual(20, t.tm_min)
3412 self.assertEqual(30, t.tm_sec)
3413 self.assertEqual(0, t.tm_wday)
3414 self.assertEqual(1, t.tm_yday)
3415 self.assertEqual(flag, t.tm_isdst)
3416
3417 # dst() returns wrong type.
3418 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
3419
3420 # dst() at the edge.
3421 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
3422 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
3423
3424 # dst() out of range.
3425 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
3426 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
3427
3428 def test_utctimetuple(self):
3429 class DST(tzinfo):
3430 def __init__(self, dstvalue=0):
3431 if isinstance(dstvalue, int):
3432 dstvalue = timedelta(minutes=dstvalue)
3433 self.dstvalue = dstvalue
3434 def dst(self, dt):
3435 return self.dstvalue
3436
3437 cls = self.theclass
3438 # This can't work: DST didn't implement utcoffset.
3439 self.assertRaises(NotImplementedError,
3440 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
3441
3442 class UOFS(DST):
3443 def __init__(self, uofs, dofs=None):
3444 DST.__init__(self, dofs)
3445 self.uofs = timedelta(minutes=uofs)
3446 def utcoffset(self, dt):
3447 return self.uofs
3448
3449 for dstvalue in -33, 33, 0, None:
3450 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
3451 t = d.utctimetuple()
3452 self.assertEqual(d.year, t.tm_year)
3453 self.assertEqual(d.month, t.tm_mon)
3454 self.assertEqual(d.day, t.tm_mday)
3455 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
3456 self.assertEqual(13, t.tm_min)
3457 self.assertEqual(d.second, t.tm_sec)
3458 self.assertEqual(d.weekday(), t.tm_wday)
3459 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
3460 t.tm_yday)
3461 # Ensure tm_isdst is 0 regardless of what dst() says: DST
3462 # is never in effect for a UTC time.
3463 self.assertEqual(0, t.tm_isdst)
3464
3465 # For naive datetime, utctimetuple == timetuple except for isdst
3466 d = cls(1, 2, 3, 10, 20, 30, 40)
3467 t = d.utctimetuple()
3468 self.assertEqual(t[:-1], d.timetuple()[:-1])
3469 self.assertEqual(0, t.tm_isdst)
3470 # Same if utcoffset is None
3471 class NOFS(DST):
3472 def utcoffset(self, dt):
3473 return None
3474 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
3475 t = d.utctimetuple()
3476 self.assertEqual(t[:-1], d.timetuple()[:-1])
3477 self.assertEqual(0, t.tm_isdst)
3478 # Check that bad tzinfo is detected
3479 class BOFS(DST):
3480 def utcoffset(self, dt):
3481 return "EST"
3482 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
3483 self.assertRaises(TypeError, d.utctimetuple)
3484
3485 # Check that utctimetuple() is the same as
3486 # astimezone(utc).timetuple()
3487 d = cls(2010, 11, 13, 14, 15, 16, 171819)
3488 for tz in [timezone.min, timezone.utc, timezone.max]:
3489 dtz = d.replace(tzinfo=tz)
3490 self.assertEqual(dtz.utctimetuple()[:-1],
3491 dtz.astimezone(timezone.utc).timetuple()[:-1])
3492 # At the edges, UTC adjustment can produce years out-of-range
3493 # for a datetime object. Ensure that an OverflowError is
3494 # raised.
3495 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
3496 # That goes back 1 minute less than a full day.
3497 self.assertRaises(OverflowError, tiny.utctimetuple)
3498
3499 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
3500 # That goes forward 1 minute less than a full day.
3501 self.assertRaises(OverflowError, huge.utctimetuple)
3502 # More overflow cases
3503 tiny = cls.min.replace(tzinfo=timezone(MINUTE))
3504 self.assertRaises(OverflowError, tiny.utctimetuple)
3505 huge = cls.max.replace(tzinfo=timezone(-MINUTE))
3506 self.assertRaises(OverflowError, huge.utctimetuple)
3507
3508 def test_tzinfo_isoformat(self):
3509 zero = FixedOffset(0, "+00:00")
3510 plus = FixedOffset(220, "+03:40")
3511 minus = FixedOffset(-231, "-03:51")
3512 unknown = FixedOffset(None, "")
3513
3514 cls = self.theclass
3515 datestr = '0001-02-03'
3516 for ofs in None, zero, plus, minus, unknown:
3517 for us in 0, 987001:
3518 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
3519 timestr = '04:05:59' + (us and '.987001' or '')
3520 ofsstr = ofs is not None and d.tzname() or ''
3521 tailstr = timestr + ofsstr
3522 iso = d.isoformat()
3523 self.assertEqual(iso, datestr + 'T' + tailstr)
3524 self.assertEqual(iso, d.isoformat('T'))
3525 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
3526 self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
3527 self.assertEqual(str(d), datestr + ' ' + tailstr)
3528
3529 def test_replace(self):
3530 cls = self.theclass
3531 z100 = FixedOffset(100, "+100")
3532 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3533 args = [1, 2, 3, 4, 5, 6, 7, z100]
3534 base = cls(*args)
3535 self.assertEqual(base, base.replace())
3536
3537 i = 0
3538 for name, newval in (("year", 2),
3539 ("month", 3),
3540 ("day", 4),
3541 ("hour", 5),
3542 ("minute", 6),
3543 ("second", 7),
3544 ("microsecond", 8),
3545 ("tzinfo", zm200)):
3546 newargs = args[:]
3547 newargs[i] = newval
3548 expected = cls(*newargs)
3549 got = base.replace(**{name: newval})
3550 self.assertEqual(expected, got)
3551 i += 1
3552
3553 # Ensure we can get rid of a tzinfo.
3554 self.assertEqual(base.tzname(), "+100")
3555 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003556 self.assertIsNone(base2.tzinfo)
3557 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003558
3559 # Ensure we can add one.
3560 base3 = base2.replace(tzinfo=z100)
3561 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003562 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003563
3564 # Out of bounds.
3565 base = cls(2000, 2, 29)
3566 self.assertRaises(ValueError, base.replace, year=2001)
3567
3568 def test_more_astimezone(self):
3569 # The inherited test_astimezone covered some trivial and error cases.
3570 fnone = FixedOffset(None, "None")
3571 f44m = FixedOffset(44, "44")
3572 fm5h = FixedOffset(-timedelta(hours=5), "m300")
3573
3574 dt = self.theclass.now(tz=f44m)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003575 self.assertIs(dt.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003576 # Replacing with degenerate tzinfo raises an exception.
3577 self.assertRaises(ValueError, dt.astimezone, fnone)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003578 # Replacing with same tzinfo makes no change.
3579 x = dt.astimezone(dt.tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003580 self.assertIs(x.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003581 self.assertEqual(x.date(), dt.date())
3582 self.assertEqual(x.time(), dt.time())
3583
3584 # Replacing with different tzinfo does adjust.
3585 got = dt.astimezone(fm5h)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003586 self.assertIs(got.tzinfo, fm5h)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003587 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
3588 expected = dt - dt.utcoffset() # in effect, convert to UTC
3589 expected += fm5h.utcoffset(dt) # and from there to local time
3590 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
3591 self.assertEqual(got.date(), expected.date())
3592 self.assertEqual(got.time(), expected.time())
3593 self.assertEqual(got.timetz(), expected.timetz())
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003594 self.assertIs(got.tzinfo, expected.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003595 self.assertEqual(got, expected)
3596
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003597 @support.run_with_tz('UTC')
3598 def test_astimezone_default_utc(self):
3599 dt = self.theclass.now(timezone.utc)
3600 self.assertEqual(dt.astimezone(None), dt)
3601 self.assertEqual(dt.astimezone(), dt)
3602
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003603 # Note that offset in TZ variable has the opposite sign to that
3604 # produced by %z directive.
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003605 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
3606 def test_astimezone_default_eastern(self):
3607 dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
3608 local = dt.astimezone()
3609 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003610 self.assertEqual(local.strftime("%z %Z"), "-0500 EST")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003611 dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc)
3612 local = dt.astimezone()
3613 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003614 self.assertEqual(local.strftime("%z %Z"), "-0400 EDT")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003615
Alexander Belopolsky1dcf4f92016-03-25 15:42:59 -04003616 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
3617 def test_astimezone_default_near_fold(self):
3618 # Issue #26616.
3619 u = datetime(2015, 11, 1, 5, tzinfo=timezone.utc)
3620 t = u.astimezone()
3621 s = t.astimezone()
3622 self.assertEqual(t.tzinfo, s.tzinfo)
3623
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003624 def test_aware_subtract(self):
3625 cls = self.theclass
3626
3627 # Ensure that utcoffset() is ignored when the operands have the
3628 # same tzinfo member.
3629 class OperandDependentOffset(tzinfo):
3630 def utcoffset(self, t):
3631 if t.minute < 10:
3632 # d0 and d1 equal after adjustment
3633 return timedelta(minutes=t.minute)
3634 else:
3635 # d2 off in the weeds
3636 return timedelta(minutes=59)
3637
3638 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
3639 d0 = base.replace(minute=3)
3640 d1 = base.replace(minute=9)
3641 d2 = base.replace(minute=11)
3642 for x in d0, d1, d2:
3643 for y in d0, d1, d2:
3644 got = x - y
3645 expected = timedelta(minutes=x.minute - y.minute)
3646 self.assertEqual(got, expected)
3647
3648 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
3649 # ignored.
3650 base = cls(8, 9, 10, 11, 12, 13, 14)
3651 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
3652 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
3653 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
3654 for x in d0, d1, d2:
3655 for y in d0, d1, d2:
3656 got = x - y
3657 if (x is d0 or x is d1) and (y is d0 or y is d1):
3658 expected = timedelta(0)
3659 elif x is y is d2:
3660 expected = timedelta(0)
3661 elif x is d2:
3662 expected = timedelta(minutes=(11-59)-0)
3663 else:
3664 assert y is d2
3665 expected = timedelta(minutes=0-(11-59))
3666 self.assertEqual(got, expected)
3667
3668 def test_mixed_compare(self):
3669 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
3670 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
3671 self.assertEqual(t1, t2)
3672 t2 = t2.replace(tzinfo=None)
3673 self.assertEqual(t1, t2)
3674 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3675 self.assertEqual(t1, t2)
3676 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04003677 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003678
3679 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
3680 class Varies(tzinfo):
3681 def __init__(self):
3682 self.offset = timedelta(minutes=22)
3683 def utcoffset(self, t):
3684 self.offset += timedelta(minutes=1)
3685 return self.offset
3686
3687 v = Varies()
3688 t1 = t2.replace(tzinfo=v)
3689 t2 = t2.replace(tzinfo=v)
3690 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3691 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3692 self.assertEqual(t1, t2)
3693
3694 # But if they're not identical, it isn't ignored.
3695 t2 = t2.replace(tzinfo=Varies())
3696 self.assertTrue(t1 < t2) # t1's offset counter still going up
3697
3698 def test_subclass_datetimetz(self):
3699
3700 class C(self.theclass):
3701 theAnswer = 42
3702
3703 def __new__(cls, *args, **kws):
3704 temp = kws.copy()
3705 extra = temp.pop('extra')
3706 result = self.theclass.__new__(cls, *args, **temp)
3707 result.extra = extra
3708 return result
3709
3710 def newmeth(self, start):
3711 return start + self.hour + self.year
3712
3713 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3714
3715 dt1 = self.theclass(*args)
3716 dt2 = C(*args, **{'extra': 7})
3717
3718 self.assertEqual(dt2.__class__, C)
3719 self.assertEqual(dt2.theAnswer, 42)
3720 self.assertEqual(dt2.extra, 7)
3721 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3722 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
3723
3724# Pain to set up DST-aware tzinfo classes.
3725
3726def first_sunday_on_or_after(dt):
3727 days_to_go = 6 - dt.weekday()
3728 if days_to_go:
3729 dt += timedelta(days_to_go)
3730 return dt
3731
3732ZERO = timedelta(0)
3733MINUTE = timedelta(minutes=1)
3734HOUR = timedelta(hours=1)
3735DAY = timedelta(days=1)
3736# In the US, DST starts at 2am (standard time) on the first Sunday in April.
3737DSTSTART = datetime(1, 4, 1, 2)
3738# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
3739# which is the first Sunday on or after Oct 25. Because we view 1:MM as
3740# being standard time on that day, there is no spelling in local time of
3741# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
3742DSTEND = datetime(1, 10, 25, 1)
3743
3744class USTimeZone(tzinfo):
3745
3746 def __init__(self, hours, reprname, stdname, dstname):
3747 self.stdoffset = timedelta(hours=hours)
3748 self.reprname = reprname
3749 self.stdname = stdname
3750 self.dstname = dstname
3751
3752 def __repr__(self):
3753 return self.reprname
3754
3755 def tzname(self, dt):
3756 if self.dst(dt):
3757 return self.dstname
3758 else:
3759 return self.stdname
3760
3761 def utcoffset(self, dt):
3762 return self.stdoffset + self.dst(dt)
3763
3764 def dst(self, dt):
3765 if dt is None or dt.tzinfo is None:
3766 # An exception instead may be sensible here, in one or more of
3767 # the cases.
3768 return ZERO
3769 assert dt.tzinfo is self
3770
3771 # Find first Sunday in April.
3772 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
3773 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
3774
3775 # Find last Sunday in October.
3776 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
3777 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
3778
3779 # Can't compare naive to aware objects, so strip the timezone from
3780 # dt first.
3781 if start <= dt.replace(tzinfo=None) < end:
3782 return HOUR
3783 else:
3784 return ZERO
3785
3786Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
3787Central = USTimeZone(-6, "Central", "CST", "CDT")
3788Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
3789Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
3790utc_real = FixedOffset(0, "UTC", 0)
3791# For better test coverage, we want another flavor of UTC that's west of
3792# the Eastern and Pacific timezones.
3793utc_fake = FixedOffset(-12*60, "UTCfake", 0)
3794
3795class TestTimezoneConversions(unittest.TestCase):
3796 # The DST switch times for 2002, in std time.
3797 dston = datetime(2002, 4, 7, 2)
3798 dstoff = datetime(2002, 10, 27, 1)
3799
3800 theclass = datetime
3801
3802 # Check a time that's inside DST.
3803 def checkinside(self, dt, tz, utc, dston, dstoff):
3804 self.assertEqual(dt.dst(), HOUR)
3805
3806 # Conversion to our own timezone is always an identity.
3807 self.assertEqual(dt.astimezone(tz), dt)
3808
3809 asutc = dt.astimezone(utc)
3810 there_and_back = asutc.astimezone(tz)
3811
3812 # Conversion to UTC and back isn't always an identity here,
3813 # because there are redundant spellings (in local time) of
3814 # UTC time when DST begins: the clock jumps from 1:59:59
3815 # to 3:00:00, and a local time of 2:MM:SS doesn't really
3816 # make sense then. The classes above treat 2:MM:SS as
3817 # daylight time then (it's "after 2am"), really an alias
3818 # for 1:MM:SS standard time. The latter form is what
3819 # conversion back from UTC produces.
3820 if dt.date() == dston.date() and dt.hour == 2:
3821 # We're in the redundant hour, and coming back from
3822 # UTC gives the 1:MM:SS standard-time spelling.
3823 self.assertEqual(there_and_back + HOUR, dt)
3824 # Although during was considered to be in daylight
3825 # time, there_and_back is not.
3826 self.assertEqual(there_and_back.dst(), ZERO)
3827 # They're the same times in UTC.
3828 self.assertEqual(there_and_back.astimezone(utc),
3829 dt.astimezone(utc))
3830 else:
3831 # We're not in the redundant hour.
3832 self.assertEqual(dt, there_and_back)
3833
3834 # Because we have a redundant spelling when DST begins, there is
Ezio Melotti3b3499b2011-03-16 11:35:38 +02003835 # (unfortunately) an hour when DST ends that can't be spelled at all in
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003836 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
3837 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
3838 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
3839 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
3840 # expressed in local time. Nevertheless, we want conversion back
3841 # from UTC to mimic the local clock's "repeat an hour" behavior.
3842 nexthour_utc = asutc + HOUR
3843 nexthour_tz = nexthour_utc.astimezone(tz)
3844 if dt.date() == dstoff.date() and dt.hour == 0:
3845 # We're in the hour before the last DST hour. The last DST hour
3846 # is ineffable. We want the conversion back to repeat 1:MM.
3847 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3848 nexthour_utc += HOUR
3849 nexthour_tz = nexthour_utc.astimezone(tz)
3850 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3851 else:
3852 self.assertEqual(nexthour_tz - dt, HOUR)
3853
3854 # Check a time that's outside DST.
3855 def checkoutside(self, dt, tz, utc):
3856 self.assertEqual(dt.dst(), ZERO)
3857
3858 # Conversion to our own timezone is always an identity.
3859 self.assertEqual(dt.astimezone(tz), dt)
3860
3861 # Converting to UTC and back is an identity too.
3862 asutc = dt.astimezone(utc)
3863 there_and_back = asutc.astimezone(tz)
3864 self.assertEqual(dt, there_and_back)
3865
3866 def convert_between_tz_and_utc(self, tz, utc):
3867 dston = self.dston.replace(tzinfo=tz)
3868 # Because 1:MM on the day DST ends is taken as being standard time,
3869 # there is no spelling in tz for the last hour of daylight time.
3870 # For purposes of the test, the last hour of DST is 0:MM, which is
3871 # taken as being daylight time (and 1:MM is taken as being standard
3872 # time).
3873 dstoff = self.dstoff.replace(tzinfo=tz)
3874 for delta in (timedelta(weeks=13),
3875 DAY,
3876 HOUR,
3877 timedelta(minutes=1),
3878 timedelta(microseconds=1)):
3879
3880 self.checkinside(dston, tz, utc, dston, dstoff)
3881 for during in dston + delta, dstoff - delta:
3882 self.checkinside(during, tz, utc, dston, dstoff)
3883
3884 self.checkoutside(dstoff, tz, utc)
3885 for outside in dston - delta, dstoff + delta:
3886 self.checkoutside(outside, tz, utc)
3887
3888 def test_easy(self):
3889 # Despite the name of this test, the endcases are excruciating.
3890 self.convert_between_tz_and_utc(Eastern, utc_real)
3891 self.convert_between_tz_and_utc(Pacific, utc_real)
3892 self.convert_between_tz_and_utc(Eastern, utc_fake)
3893 self.convert_between_tz_and_utc(Pacific, utc_fake)
3894 # The next is really dancing near the edge. It works because
3895 # Pacific and Eastern are far enough apart that their "problem
3896 # hours" don't overlap.
3897 self.convert_between_tz_and_utc(Eastern, Pacific)
3898 self.convert_between_tz_and_utc(Pacific, Eastern)
3899 # OTOH, these fail! Don't enable them. The difficulty is that
3900 # the edge case tests assume that every hour is representable in
3901 # the "utc" class. This is always true for a fixed-offset tzinfo
3902 # class (lke utc_real and utc_fake), but not for Eastern or Central.
3903 # For these adjacent DST-aware time zones, the range of time offsets
3904 # tested ends up creating hours in the one that aren't representable
3905 # in the other. For the same reason, we would see failures in the
3906 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
3907 # offset deltas in convert_between_tz_and_utc().
3908 #
3909 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
3910 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
3911
3912 def test_tricky(self):
3913 # 22:00 on day before daylight starts.
3914 fourback = self.dston - timedelta(hours=4)
3915 ninewest = FixedOffset(-9*60, "-0900", 0)
3916 fourback = fourback.replace(tzinfo=ninewest)
3917 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
3918 # 2", we should get the 3 spelling.
3919 # If we plug 22:00 the day before into Eastern, it "looks like std
3920 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
3921 # to 22:00 lands on 2:00, which makes no sense in local time (the
3922 # local clock jumps from 1 to 3). The point here is to make sure we
3923 # get the 3 spelling.
3924 expected = self.dston.replace(hour=3)
3925 got = fourback.astimezone(Eastern).replace(tzinfo=None)
3926 self.assertEqual(expected, got)
3927
3928 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
3929 # case we want the 1:00 spelling.
3930 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
3931 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
3932 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
3933 # spelling.
3934 expected = self.dston.replace(hour=1)
3935 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
3936 self.assertEqual(expected, got)
3937
3938 # Now on the day DST ends, we want "repeat an hour" behavior.
3939 # UTC 4:MM 5:MM 6:MM 7:MM checking these
3940 # EST 23:MM 0:MM 1:MM 2:MM
3941 # EDT 0:MM 1:MM 2:MM 3:MM
3942 # wall 0:MM 1:MM 1:MM 2:MM against these
3943 for utc in utc_real, utc_fake:
3944 for tz in Eastern, Pacific:
3945 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
3946 # Convert that to UTC.
3947 first_std_hour -= tz.utcoffset(None)
3948 # Adjust for possibly fake UTC.
3949 asutc = first_std_hour + utc.utcoffset(None)
3950 # First UTC hour to convert; this is 4:00 when utc=utc_real &
3951 # tz=Eastern.
3952 asutcbase = asutc.replace(tzinfo=utc)
3953 for tzhour in (0, 1, 1, 2):
3954 expectedbase = self.dstoff.replace(hour=tzhour)
3955 for minute in 0, 30, 59:
3956 expected = expectedbase.replace(minute=minute)
3957 asutc = asutcbase.replace(minute=minute)
3958 astz = asutc.astimezone(tz)
3959 self.assertEqual(astz.replace(tzinfo=None), expected)
3960 asutcbase += HOUR
3961
3962
3963 def test_bogus_dst(self):
3964 class ok(tzinfo):
3965 def utcoffset(self, dt): return HOUR
3966 def dst(self, dt): return HOUR
3967
3968 now = self.theclass.now().replace(tzinfo=utc_real)
3969 # Doesn't blow up.
3970 now.astimezone(ok())
3971
3972 # Does blow up.
3973 class notok(ok):
3974 def dst(self, dt): return None
3975 self.assertRaises(ValueError, now.astimezone, notok())
3976
3977 # Sometimes blow up. In the following, tzinfo.dst()
3978 # implementation may return None or not None depending on
3979 # whether DST is assumed to be in effect. In this situation,
3980 # a ValueError should be raised by astimezone().
3981 class tricky_notok(ok):
3982 def dst(self, dt):
3983 if dt.year == 2000:
3984 return None
3985 else:
3986 return 10*HOUR
3987 dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
3988 self.assertRaises(ValueError, dt.astimezone, tricky_notok())
3989
3990 def test_fromutc(self):
3991 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
3992 now = datetime.utcnow().replace(tzinfo=utc_real)
3993 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3994 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
3995 enow = Eastern.fromutc(now) # doesn't blow up
3996 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
3997 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
3998 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
3999
4000 # Always converts UTC to standard time.
4001 class FauxUSTimeZone(USTimeZone):
4002 def fromutc(self, dt):
4003 return dt + self.stdoffset
4004 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
4005
4006 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
4007 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
4008 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
4009
4010 # Check around DST start.
4011 start = self.dston.replace(hour=4, tzinfo=Eastern)
4012 fstart = start.replace(tzinfo=FEastern)
4013 for wall in 23, 0, 1, 3, 4, 5:
4014 expected = start.replace(hour=wall)
4015 if wall == 23:
4016 expected -= timedelta(days=1)
4017 got = Eastern.fromutc(start)
4018 self.assertEqual(expected, got)
4019
4020 expected = fstart + FEastern.stdoffset
4021 got = FEastern.fromutc(fstart)
4022 self.assertEqual(expected, got)
4023
4024 # Ensure astimezone() calls fromutc() too.
4025 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
4026 self.assertEqual(expected, got)
4027
4028 start += HOUR
4029 fstart += HOUR
4030
4031 # Check around DST end.
4032 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
4033 fstart = start.replace(tzinfo=FEastern)
4034 for wall in 0, 1, 1, 2, 3, 4:
4035 expected = start.replace(hour=wall)
4036 got = Eastern.fromutc(start)
4037 self.assertEqual(expected, got)
4038
4039 expected = fstart + FEastern.stdoffset
4040 got = FEastern.fromutc(fstart)
4041 self.assertEqual(expected, got)
4042
4043 # Ensure astimezone() calls fromutc() too.
4044 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
4045 self.assertEqual(expected, got)
4046
4047 start += HOUR
4048 fstart += HOUR
4049
4050
4051#############################################################################
4052# oddballs
4053
4054class Oddballs(unittest.TestCase):
4055
4056 def test_bug_1028306(self):
4057 # Trying to compare a date to a datetime should act like a mixed-
4058 # type comparison, despite that datetime is a subclass of date.
4059 as_date = date.today()
4060 as_datetime = datetime.combine(as_date, time())
4061 self.assertTrue(as_date != as_datetime)
4062 self.assertTrue(as_datetime != as_date)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004063 self.assertFalse(as_date == as_datetime)
4064 self.assertFalse(as_datetime == as_date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004065 self.assertRaises(TypeError, lambda: as_date < as_datetime)
4066 self.assertRaises(TypeError, lambda: as_datetime < as_date)
4067 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
4068 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
4069 self.assertRaises(TypeError, lambda: as_date > as_datetime)
4070 self.assertRaises(TypeError, lambda: as_datetime > as_date)
4071 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
4072 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
4073
Raymond Hettinger15f44ab2016-08-30 10:47:49 -07004074 # Nevertheless, comparison should work with the base-class (date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004075 # projection if use of a date method is forced.
4076 self.assertEqual(as_date.__eq__(as_datetime), True)
4077 different_day = (as_date.day + 1) % 20 + 1
4078 as_different = as_datetime.replace(day= different_day)
4079 self.assertEqual(as_date.__eq__(as_different), False)
4080
4081 # And date should compare with other subclasses of date. If a
4082 # subclass wants to stop this, it's up to the subclass to do so.
4083 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
4084 self.assertEqual(as_date, date_sc)
4085 self.assertEqual(date_sc, as_date)
4086
4087 # Ditto for datetimes.
4088 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
4089 as_date.day, 0, 0, 0)
4090 self.assertEqual(as_datetime, datetime_sc)
4091 self.assertEqual(datetime_sc, as_datetime)
4092
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004093 def test_extra_attributes(self):
4094 for x in [date.today(),
4095 time(),
4096 datetime.utcnow(),
4097 timedelta(),
4098 tzinfo(),
4099 timezone(timedelta())]:
4100 with self.assertRaises(AttributeError):
4101 x.abc = 1
4102
4103 def test_check_arg_types(self):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004104 class Number:
4105 def __init__(self, value):
4106 self.value = value
4107 def __int__(self):
4108 return self.value
4109
4110 for xx in [decimal.Decimal(10),
4111 decimal.Decimal('10.9'),
4112 Number(10)]:
4113 self.assertEqual(datetime(10, 10, 10, 10, 10, 10, 10),
4114 datetime(xx, xx, xx, xx, xx, xx, xx))
4115
4116 with self.assertRaisesRegex(TypeError, '^an integer is required '
R David Murray44b548d2016-09-08 13:59:53 -04004117 r'\(got type str\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004118 datetime(10, 10, '10')
4119
4120 f10 = Number(10.9)
4121 with self.assertRaisesRegex(TypeError, '^__int__ returned non-int '
R David Murray44b548d2016-09-08 13:59:53 -04004122 r'\(type float\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004123 datetime(10, 10, f10)
4124
4125 class Float(float):
4126 pass
4127 s10 = Float(10.9)
4128 with self.assertRaisesRegex(TypeError, '^integer argument expected, '
4129 'got float$'):
4130 datetime(10, 10, s10)
4131
4132 with self.assertRaises(TypeError):
4133 datetime(10., 10, 10)
4134 with self.assertRaises(TypeError):
4135 datetime(10, 10., 10)
4136 with self.assertRaises(TypeError):
4137 datetime(10, 10, 10.)
4138 with self.assertRaises(TypeError):
4139 datetime(10, 10, 10, 10.)
4140 with self.assertRaises(TypeError):
4141 datetime(10, 10, 10, 10, 10.)
4142 with self.assertRaises(TypeError):
4143 datetime(10, 10, 10, 10, 10, 10.)
4144 with self.assertRaises(TypeError):
4145 datetime(10, 10, 10, 10, 10, 10, 10.)
4146
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004147#############################################################################
4148# Local Time Disambiguation
4149
4150# An experimental reimplementation of fromutc that respects the "fold" flag.
4151
4152class tzinfo2(tzinfo):
4153
4154 def fromutc(self, dt):
4155 "datetime in UTC -> datetime in local time."
4156
4157 if not isinstance(dt, datetime):
4158 raise TypeError("fromutc() requires a datetime argument")
4159 if dt.tzinfo is not self:
4160 raise ValueError("dt.tzinfo is not self")
4161 # Returned value satisfies
4162 # dt + ldt.utcoffset() = ldt
4163 off0 = dt.replace(fold=0).utcoffset()
4164 off1 = dt.replace(fold=1).utcoffset()
4165 if off0 is None or off1 is None or dt.dst() is None:
4166 raise ValueError
4167 if off0 == off1:
4168 ldt = dt + off0
4169 off1 = ldt.utcoffset()
4170 if off0 == off1:
4171 return ldt
4172 # Now, we discovered both possible offsets, so
4173 # we can just try four possible solutions:
4174 for off in [off0, off1]:
4175 ldt = dt + off
4176 if ldt.utcoffset() == off:
4177 return ldt
4178 ldt = ldt.replace(fold=1)
4179 if ldt.utcoffset() == off:
4180 return ldt
4181
4182 raise ValueError("No suitable local time found")
4183
4184# Reimplementing simplified US timezones to respect the "fold" flag:
4185
4186class USTimeZone2(tzinfo2):
4187
4188 def __init__(self, hours, reprname, stdname, dstname):
4189 self.stdoffset = timedelta(hours=hours)
4190 self.reprname = reprname
4191 self.stdname = stdname
4192 self.dstname = dstname
4193
4194 def __repr__(self):
4195 return self.reprname
4196
4197 def tzname(self, dt):
4198 if self.dst(dt):
4199 return self.dstname
4200 else:
4201 return self.stdname
4202
4203 def utcoffset(self, dt):
4204 return self.stdoffset + self.dst(dt)
4205
4206 def dst(self, dt):
4207 if dt is None or dt.tzinfo is None:
4208 # An exception instead may be sensible here, in one or more of
4209 # the cases.
4210 return ZERO
4211 assert dt.tzinfo is self
4212
4213 # Find first Sunday in April.
4214 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
4215 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
4216
4217 # Find last Sunday in October.
4218 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
4219 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
4220
4221 # Can't compare naive to aware objects, so strip the timezone from
4222 # dt first.
4223 dt = dt.replace(tzinfo=None)
4224 if start + HOUR <= dt < end:
4225 # DST is in effect.
4226 return HOUR
4227 elif end <= dt < end + HOUR:
4228 # Fold (an ambiguous hour): use dt.fold to disambiguate.
4229 return ZERO if dt.fold else HOUR
4230 elif start <= dt < start + HOUR:
4231 # Gap (a non-existent hour): reverse the fold rule.
4232 return HOUR if dt.fold else ZERO
4233 else:
4234 # DST is off.
4235 return ZERO
4236
4237Eastern2 = USTimeZone2(-5, "Eastern2", "EST", "EDT")
4238Central2 = USTimeZone2(-6, "Central2", "CST", "CDT")
4239Mountain2 = USTimeZone2(-7, "Mountain2", "MST", "MDT")
4240Pacific2 = USTimeZone2(-8, "Pacific2", "PST", "PDT")
4241
4242# Europe_Vilnius_1941 tzinfo implementation reproduces the following
4243# 1941 transition from Olson's tzdist:
4244#
4245# Zone NAME GMTOFF RULES FORMAT [UNTIL]
4246# ZoneEurope/Vilnius 1:00 - CET 1940 Aug 3
4247# 3:00 - MSK 1941 Jun 24
4248# 1:00 C-Eur CE%sT 1944 Aug
4249#
4250# $ zdump -v Europe/Vilnius | grep 1941
4251# Europe/Vilnius Mon Jun 23 20:59:59 1941 UTC = Mon Jun 23 23:59:59 1941 MSK isdst=0 gmtoff=10800
4252# Europe/Vilnius Mon Jun 23 21:00:00 1941 UTC = Mon Jun 23 23:00:00 1941 CEST isdst=1 gmtoff=7200
4253
4254class Europe_Vilnius_1941(tzinfo):
4255 def _utc_fold(self):
4256 return [datetime(1941, 6, 23, 21, tzinfo=self), # Mon Jun 23 21:00:00 1941 UTC
4257 datetime(1941, 6, 23, 22, tzinfo=self)] # Mon Jun 23 22:00:00 1941 UTC
4258
4259 def _loc_fold(self):
4260 return [datetime(1941, 6, 23, 23, tzinfo=self), # Mon Jun 23 23:00:00 1941 MSK / CEST
4261 datetime(1941, 6, 24, 0, tzinfo=self)] # Mon Jun 24 00:00:00 1941 CEST
4262
4263 def utcoffset(self, dt):
4264 fold_start, fold_stop = self._loc_fold()
4265 if dt < fold_start:
4266 return 3 * HOUR
4267 if dt < fold_stop:
4268 return (2 if dt.fold else 3) * HOUR
4269 # if dt >= fold_stop
4270 return 2 * HOUR
4271
4272 def dst(self, dt):
4273 fold_start, fold_stop = self._loc_fold()
4274 if dt < fold_start:
4275 return 0 * HOUR
4276 if dt < fold_stop:
4277 return (1 if dt.fold else 0) * HOUR
4278 # if dt >= fold_stop
4279 return 1 * HOUR
4280
4281 def tzname(self, dt):
4282 fold_start, fold_stop = self._loc_fold()
4283 if dt < fold_start:
4284 return 'MSK'
4285 if dt < fold_stop:
4286 return ('MSK', 'CEST')[dt.fold]
4287 # if dt >= fold_stop
4288 return 'CEST'
4289
4290 def fromutc(self, dt):
4291 assert dt.fold == 0
4292 assert dt.tzinfo is self
4293 if dt.year != 1941:
4294 raise NotImplementedError
4295 fold_start, fold_stop = self._utc_fold()
4296 if dt < fold_start:
4297 return dt + 3 * HOUR
4298 if dt < fold_stop:
4299 return (dt + 2 * HOUR).replace(fold=1)
4300 # if dt >= fold_stop
4301 return dt + 2 * HOUR
4302
4303
4304class TestLocalTimeDisambiguation(unittest.TestCase):
4305
4306 def test_vilnius_1941_fromutc(self):
4307 Vilnius = Europe_Vilnius_1941()
4308
4309 gdt = datetime(1941, 6, 23, 20, 59, 59, tzinfo=timezone.utc)
4310 ldt = gdt.astimezone(Vilnius)
4311 self.assertEqual(ldt.strftime("%c %Z%z"),
4312 'Mon Jun 23 23:59:59 1941 MSK+0300')
4313 self.assertEqual(ldt.fold, 0)
4314 self.assertFalse(ldt.dst())
4315
4316 gdt = datetime(1941, 6, 23, 21, tzinfo=timezone.utc)
4317 ldt = gdt.astimezone(Vilnius)
4318 self.assertEqual(ldt.strftime("%c %Z%z"),
4319 'Mon Jun 23 23:00:00 1941 CEST+0200')
4320 self.assertEqual(ldt.fold, 1)
4321 self.assertTrue(ldt.dst())
4322
4323 gdt = datetime(1941, 6, 23, 22, tzinfo=timezone.utc)
4324 ldt = gdt.astimezone(Vilnius)
4325 self.assertEqual(ldt.strftime("%c %Z%z"),
4326 'Tue Jun 24 00:00:00 1941 CEST+0200')
4327 self.assertEqual(ldt.fold, 0)
4328 self.assertTrue(ldt.dst())
4329
4330 def test_vilnius_1941_toutc(self):
4331 Vilnius = Europe_Vilnius_1941()
4332
4333 ldt = datetime(1941, 6, 23, 22, 59, 59, tzinfo=Vilnius)
4334 gdt = ldt.astimezone(timezone.utc)
4335 self.assertEqual(gdt.strftime("%c %Z"),
4336 'Mon Jun 23 19:59:59 1941 UTC')
4337
4338 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius)
4339 gdt = ldt.astimezone(timezone.utc)
4340 self.assertEqual(gdt.strftime("%c %Z"),
4341 'Mon Jun 23 20:59:59 1941 UTC')
4342
4343 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius, fold=1)
4344 gdt = ldt.astimezone(timezone.utc)
4345 self.assertEqual(gdt.strftime("%c %Z"),
4346 'Mon Jun 23 21:59:59 1941 UTC')
4347
4348 ldt = datetime(1941, 6, 24, 0, tzinfo=Vilnius)
4349 gdt = ldt.astimezone(timezone.utc)
4350 self.assertEqual(gdt.strftime("%c %Z"),
4351 'Mon Jun 23 22:00:00 1941 UTC')
4352
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004353 def test_constructors(self):
4354 t = time(0, fold=1)
4355 dt = datetime(1, 1, 1, fold=1)
4356 self.assertEqual(t.fold, 1)
4357 self.assertEqual(dt.fold, 1)
4358 with self.assertRaises(TypeError):
4359 time(0, 0, 0, 0, None, 0)
4360
4361 def test_member(self):
4362 dt = datetime(1, 1, 1, fold=1)
4363 t = dt.time()
4364 self.assertEqual(t.fold, 1)
4365 t = dt.timetz()
4366 self.assertEqual(t.fold, 1)
4367
4368 def test_replace(self):
4369 t = time(0)
4370 dt = datetime(1, 1, 1)
4371 self.assertEqual(t.replace(fold=1).fold, 1)
4372 self.assertEqual(dt.replace(fold=1).fold, 1)
4373 self.assertEqual(t.replace(fold=0).fold, 0)
4374 self.assertEqual(dt.replace(fold=0).fold, 0)
4375 # Check that replacement of other fields does not change "fold".
4376 t = t.replace(fold=1, tzinfo=Eastern)
4377 dt = dt.replace(fold=1, tzinfo=Eastern)
4378 self.assertEqual(t.replace(tzinfo=None).fold, 1)
4379 self.assertEqual(dt.replace(tzinfo=None).fold, 1)
Serhiy Storchaka314d6fc2017-03-31 22:48:16 +03004380 # Out of bounds.
4381 with self.assertRaises(ValueError):
4382 t.replace(fold=2)
4383 with self.assertRaises(ValueError):
4384 dt.replace(fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004385 # Check that fold is a keyword-only argument
4386 with self.assertRaises(TypeError):
4387 t.replace(1, 1, 1, None, 1)
4388 with self.assertRaises(TypeError):
4389 dt.replace(1, 1, 1, 1, 1, 1, 1, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04004390
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004391 def test_comparison(self):
4392 t = time(0)
4393 dt = datetime(1, 1, 1)
4394 self.assertEqual(t, t.replace(fold=1))
4395 self.assertEqual(dt, dt.replace(fold=1))
4396
4397 def test_hash(self):
4398 t = time(0)
4399 dt = datetime(1, 1, 1)
4400 self.assertEqual(hash(t), hash(t.replace(fold=1)))
4401 self.assertEqual(hash(dt), hash(dt.replace(fold=1)))
4402
4403 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4404 def test_fromtimestamp(self):
4405 s = 1414906200
4406 dt0 = datetime.fromtimestamp(s)
4407 dt1 = datetime.fromtimestamp(s + 3600)
4408 self.assertEqual(dt0.fold, 0)
4409 self.assertEqual(dt1.fold, 1)
4410
4411 @support.run_with_tz('Australia/Lord_Howe')
4412 def test_fromtimestamp_lord_howe(self):
4413 tm = _time.localtime(1.4e9)
4414 if _time.strftime('%Z%z', tm) != 'LHST+1030':
4415 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
4416 # $ TZ=Australia/Lord_Howe date -r 1428158700
4417 # Sun Apr 5 01:45:00 LHDT 2015
4418 # $ TZ=Australia/Lord_Howe date -r 1428160500
4419 # Sun Apr 5 01:45:00 LHST 2015
4420 s = 1428158700
4421 t0 = datetime.fromtimestamp(s)
4422 t1 = datetime.fromtimestamp(s + 1800)
4423 self.assertEqual(t0, t1)
4424 self.assertEqual(t0.fold, 0)
4425 self.assertEqual(t1.fold, 1)
4426
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004427 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4428 def test_timestamp(self):
4429 dt0 = datetime(2014, 11, 2, 1, 30)
4430 dt1 = dt0.replace(fold=1)
4431 self.assertEqual(dt0.timestamp() + 3600,
4432 dt1.timestamp())
4433
4434 @support.run_with_tz('Australia/Lord_Howe')
4435 def test_timestamp_lord_howe(self):
4436 tm = _time.localtime(1.4e9)
4437 if _time.strftime('%Z%z', tm) != 'LHST+1030':
4438 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
4439 t = datetime(2015, 4, 5, 1, 45)
4440 s0 = t.replace(fold=0).timestamp()
4441 s1 = t.replace(fold=1).timestamp()
4442 self.assertEqual(s0 + 1800, s1)
4443
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004444 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4445 def test_astimezone(self):
4446 dt0 = datetime(2014, 11, 2, 1, 30)
4447 dt1 = dt0.replace(fold=1)
4448 # Convert both naive instances to aware.
4449 adt0 = dt0.astimezone()
4450 adt1 = dt1.astimezone()
4451 # Check that the first instance in DST zone and the second in STD
4452 self.assertEqual(adt0.tzname(), 'EDT')
4453 self.assertEqual(adt1.tzname(), 'EST')
4454 self.assertEqual(adt0 + HOUR, adt1)
4455 # Aware instances with fixed offset tzinfo's always have fold=0
4456 self.assertEqual(adt0.fold, 0)
4457 self.assertEqual(adt1.fold, 0)
4458
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004459 def test_pickle_fold(self):
4460 t = time(fold=1)
4461 dt = datetime(1, 1, 1, fold=1)
4462 for pickler, unpickler, proto in pickle_choices:
4463 for x in [t, dt]:
4464 s = pickler.dumps(x, proto)
4465 y = unpickler.loads(s)
4466 self.assertEqual(x, y)
4467 self.assertEqual((0 if proto < 4 else x.fold), y.fold)
4468
4469 def test_repr(self):
4470 t = time(fold=1)
4471 dt = datetime(1, 1, 1, fold=1)
4472 self.assertEqual(repr(t), 'datetime.time(0, 0, fold=1)')
4473 self.assertEqual(repr(dt),
4474 'datetime.datetime(1, 1, 1, 0, 0, fold=1)')
4475
4476 def test_dst(self):
4477 # Let's first establish that things work in regular times.
4478 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
4479 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
4480 self.assertEqual(dt_summer.dst(), HOUR)
4481 self.assertEqual(dt_winter.dst(), ZERO)
4482 # The disambiguation flag is ignored
4483 self.assertEqual(dt_summer.replace(fold=1).dst(), HOUR)
4484 self.assertEqual(dt_winter.replace(fold=1).dst(), ZERO)
4485
4486 # Pick local time in the fold.
4487 for minute in [0, 30, 59]:
4488 dt = datetime(2002, 10, 27, 1, minute, tzinfo=Eastern2)
4489 # With fold=0 (the default) it is in DST.
4490 self.assertEqual(dt.dst(), HOUR)
4491 # With fold=1 it is in STD.
4492 self.assertEqual(dt.replace(fold=1).dst(), ZERO)
4493
4494 # Pick local time in the gap.
4495 for minute in [0, 30, 59]:
4496 dt = datetime(2002, 4, 7, 2, minute, tzinfo=Eastern2)
4497 # With fold=0 (the default) it is in STD.
4498 self.assertEqual(dt.dst(), ZERO)
4499 # With fold=1 it is in DST.
4500 self.assertEqual(dt.replace(fold=1).dst(), HOUR)
4501
4502
4503 def test_utcoffset(self):
4504 # Let's first establish that things work in regular times.
4505 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
4506 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
4507 self.assertEqual(dt_summer.utcoffset(), -4 * HOUR)
4508 self.assertEqual(dt_winter.utcoffset(), -5 * HOUR)
4509 # The disambiguation flag is ignored
4510 self.assertEqual(dt_summer.replace(fold=1).utcoffset(), -4 * HOUR)
4511 self.assertEqual(dt_winter.replace(fold=1).utcoffset(), -5 * HOUR)
4512
4513 def test_fromutc(self):
4514 # Let's first establish that things work in regular times.
4515 u_summer = datetime(2002, 10, 27, 6, tzinfo=Eastern2) - timedelta.resolution
4516 u_winter = datetime(2002, 10, 27, 7, tzinfo=Eastern2)
4517 t_summer = Eastern2.fromutc(u_summer)
4518 t_winter = Eastern2.fromutc(u_winter)
4519 self.assertEqual(t_summer, u_summer - 4 * HOUR)
4520 self.assertEqual(t_winter, u_winter - 5 * HOUR)
4521 self.assertEqual(t_summer.fold, 0)
4522 self.assertEqual(t_winter.fold, 0)
4523
4524 # What happens in the fall-back fold?
4525 u = datetime(2002, 10, 27, 5, 30, tzinfo=Eastern2)
4526 t0 = Eastern2.fromutc(u)
4527 u += HOUR
4528 t1 = Eastern2.fromutc(u)
4529 self.assertEqual(t0, t1)
4530 self.assertEqual(t0.fold, 0)
4531 self.assertEqual(t1.fold, 1)
4532 # The tricky part is when u is in the local fold:
4533 u = datetime(2002, 10, 27, 1, 30, tzinfo=Eastern2)
4534 t = Eastern2.fromutc(u)
4535 self.assertEqual((t.day, t.hour), (26, 21))
4536 # .. or gets into the local fold after a standard time adjustment
4537 u = datetime(2002, 10, 27, 6, 30, tzinfo=Eastern2)
4538 t = Eastern2.fromutc(u)
4539 self.assertEqual((t.day, t.hour), (27, 1))
4540
4541 # What happens in the spring-forward gap?
4542 u = datetime(2002, 4, 7, 2, 0, tzinfo=Eastern2)
4543 t = Eastern2.fromutc(u)
4544 self.assertEqual((t.day, t.hour), (6, 21))
4545
4546 def test_mixed_compare_regular(self):
4547 t = datetime(2000, 1, 1, tzinfo=Eastern2)
4548 self.assertEqual(t, t.astimezone(timezone.utc))
4549 t = datetime(2000, 6, 1, tzinfo=Eastern2)
4550 self.assertEqual(t, t.astimezone(timezone.utc))
4551
4552 def test_mixed_compare_fold(self):
4553 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
4554 t_fold_utc = t_fold.astimezone(timezone.utc)
4555 self.assertNotEqual(t_fold, t_fold_utc)
4556
4557 def test_mixed_compare_gap(self):
4558 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
4559 t_gap_utc = t_gap.astimezone(timezone.utc)
4560 self.assertNotEqual(t_gap, t_gap_utc)
4561
4562 def test_hash_aware(self):
4563 t = datetime(2000, 1, 1, tzinfo=Eastern2)
4564 self.assertEqual(hash(t), hash(t.replace(fold=1)))
4565 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
4566 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
4567 self.assertEqual(hash(t_fold), hash(t_fold.replace(fold=1)))
4568 self.assertEqual(hash(t_gap), hash(t_gap.replace(fold=1)))
4569
4570SEC = timedelta(0, 1)
4571
4572def pairs(iterable):
4573 a, b = itertools.tee(iterable)
4574 next(b, None)
4575 return zip(a, b)
4576
4577class ZoneInfo(tzinfo):
4578 zoneroot = '/usr/share/zoneinfo'
4579 def __init__(self, ut, ti):
4580 """
4581
4582 :param ut: array
4583 Array of transition point timestamps
4584 :param ti: list
4585 A list of (offset, isdst, abbr) tuples
4586 :return: None
4587 """
4588 self.ut = ut
4589 self.ti = ti
4590 self.lt = self.invert(ut, ti)
4591
4592 @staticmethod
4593 def invert(ut, ti):
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04004594 lt = (array('q', ut), array('q', ut))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004595 if ut:
4596 offset = ti[0][0] // SEC
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04004597 lt[0][0] += offset
4598 lt[1][0] += offset
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004599 for i in range(1, len(ut)):
4600 lt[0][i] += ti[i-1][0] // SEC
4601 lt[1][i] += ti[i][0] // SEC
4602 return lt
4603
4604 @classmethod
4605 def fromfile(cls, fileobj):
4606 if fileobj.read(4).decode() != "TZif":
4607 raise ValueError("not a zoneinfo file")
4608 fileobj.seek(32)
4609 counts = array('i')
4610 counts.fromfile(fileobj, 3)
4611 if sys.byteorder != 'big':
4612 counts.byteswap()
4613
4614 ut = array('i')
4615 ut.fromfile(fileobj, counts[0])
4616 if sys.byteorder != 'big':
4617 ut.byteswap()
4618
4619 type_indices = array('B')
4620 type_indices.fromfile(fileobj, counts[0])
4621
4622 ttis = []
4623 for i in range(counts[1]):
4624 ttis.append(struct.unpack(">lbb", fileobj.read(6)))
4625
4626 abbrs = fileobj.read(counts[2])
4627
4628 # Convert ttis
4629 for i, (gmtoff, isdst, abbrind) in enumerate(ttis):
4630 abbr = abbrs[abbrind:abbrs.find(0, abbrind)].decode()
4631 ttis[i] = (timedelta(0, gmtoff), isdst, abbr)
4632
4633 ti = [None] * len(ut)
4634 for i, idx in enumerate(type_indices):
4635 ti[i] = ttis[idx]
4636
4637 self = cls(ut, ti)
4638
4639 return self
4640
4641 @classmethod
4642 def fromname(cls, name):
4643 path = os.path.join(cls.zoneroot, name)
4644 with open(path, 'rb') as f:
4645 return cls.fromfile(f)
4646
4647 EPOCHORDINAL = date(1970, 1, 1).toordinal()
4648
4649 def fromutc(self, dt):
4650 """datetime in UTC -> datetime in local time."""
4651
4652 if not isinstance(dt, datetime):
4653 raise TypeError("fromutc() requires a datetime argument")
4654 if dt.tzinfo is not self:
4655 raise ValueError("dt.tzinfo is not self")
4656
4657 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
4658 + dt.hour * 3600
4659 + dt.minute * 60
4660 + dt.second)
4661
4662 if timestamp < self.ut[1]:
4663 tti = self.ti[0]
4664 fold = 0
4665 else:
4666 idx = bisect.bisect_right(self.ut, timestamp)
4667 assert self.ut[idx-1] <= timestamp
4668 assert idx == len(self.ut) or timestamp < self.ut[idx]
4669 tti_prev, tti = self.ti[idx-2:idx]
4670 # Detect fold
4671 shift = tti_prev[0] - tti[0]
4672 fold = (shift > timedelta(0, timestamp - self.ut[idx-1]))
4673 dt += tti[0]
4674 if fold:
4675 return dt.replace(fold=1)
4676 else:
4677 return dt
4678
4679 def _find_ti(self, dt, i):
4680 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
4681 + dt.hour * 3600
4682 + dt.minute * 60
4683 + dt.second)
4684 lt = self.lt[dt.fold]
4685 idx = bisect.bisect_right(lt, timestamp)
4686
4687 return self.ti[max(0, idx - 1)][i]
4688
4689 def utcoffset(self, dt):
4690 return self._find_ti(dt, 0)
4691
4692 def dst(self, dt):
4693 isdst = self._find_ti(dt, 1)
4694 # XXX: We cannot accurately determine the "save" value,
4695 # so let's return 1h whenever DST is in effect. Since
4696 # we don't use dst() in fromutc(), it is unlikely that
4697 # it will be needed for anything more than bool(dst()).
4698 return ZERO if isdst else HOUR
4699
4700 def tzname(self, dt):
4701 return self._find_ti(dt, 2)
4702
4703 @classmethod
4704 def zonenames(cls, zonedir=None):
4705 if zonedir is None:
4706 zonedir = cls.zoneroot
Alexander Belopolsky1b8f26c2016-08-11 11:01:45 -04004707 zone_tab = os.path.join(zonedir, 'zone.tab')
4708 try:
4709 f = open(zone_tab)
4710 except OSError:
4711 return
4712 with f:
4713 for line in f:
4714 line = line.strip()
4715 if line and not line.startswith('#'):
4716 yield line.split()[2]
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004717
4718 @classmethod
4719 def stats(cls, start_year=1):
4720 count = gap_count = fold_count = zeros_count = 0
4721 min_gap = min_fold = timedelta.max
4722 max_gap = max_fold = ZERO
4723 min_gap_datetime = max_gap_datetime = datetime.min
4724 min_gap_zone = max_gap_zone = None
4725 min_fold_datetime = max_fold_datetime = datetime.min
4726 min_fold_zone = max_fold_zone = None
4727 stats_since = datetime(start_year, 1, 1) # Starting from 1970 eliminates a lot of noise
4728 for zonename in cls.zonenames():
4729 count += 1
4730 tz = cls.fromname(zonename)
4731 for dt, shift in tz.transitions():
4732 if dt < stats_since:
4733 continue
4734 if shift > ZERO:
4735 gap_count += 1
4736 if (shift, dt) > (max_gap, max_gap_datetime):
4737 max_gap = shift
4738 max_gap_zone = zonename
4739 max_gap_datetime = dt
4740 if (shift, datetime.max - dt) < (min_gap, datetime.max - min_gap_datetime):
4741 min_gap = shift
4742 min_gap_zone = zonename
4743 min_gap_datetime = dt
4744 elif shift < ZERO:
4745 fold_count += 1
4746 shift = -shift
4747 if (shift, dt) > (max_fold, max_fold_datetime):
4748 max_fold = shift
4749 max_fold_zone = zonename
4750 max_fold_datetime = dt
4751 if (shift, datetime.max - dt) < (min_fold, datetime.max - min_fold_datetime):
4752 min_fold = shift
4753 min_fold_zone = zonename
4754 min_fold_datetime = dt
4755 else:
4756 zeros_count += 1
4757 trans_counts = (gap_count, fold_count, zeros_count)
4758 print("Number of zones: %5d" % count)
4759 print("Number of transitions: %5d = %d (gaps) + %d (folds) + %d (zeros)" %
4760 ((sum(trans_counts),) + trans_counts))
4761 print("Min gap: %16s at %s in %s" % (min_gap, min_gap_datetime, min_gap_zone))
4762 print("Max gap: %16s at %s in %s" % (max_gap, max_gap_datetime, max_gap_zone))
4763 print("Min fold: %16s at %s in %s" % (min_fold, min_fold_datetime, min_fold_zone))
4764 print("Max fold: %16s at %s in %s" % (max_fold, max_fold_datetime, max_fold_zone))
4765
4766
4767 def transitions(self):
4768 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
4769 shift = ti[0] - prev_ti[0]
4770 yield datetime.utcfromtimestamp(t), shift
4771
4772 def nondst_folds(self):
4773 """Find all folds with the same value of isdst on both sides of the transition."""
4774 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
4775 shift = ti[0] - prev_ti[0]
4776 if shift < ZERO and ti[1] == prev_ti[1]:
4777 yield datetime.utcfromtimestamp(t), -shift, prev_ti[2], ti[2]
4778
4779 @classmethod
4780 def print_all_nondst_folds(cls, same_abbr=False, start_year=1):
4781 count = 0
4782 for zonename in cls.zonenames():
4783 tz = cls.fromname(zonename)
4784 for dt, shift, prev_abbr, abbr in tz.nondst_folds():
4785 if dt.year < start_year or same_abbr and prev_abbr != abbr:
4786 continue
4787 count += 1
4788 print("%3d) %-30s %s %10s %5s -> %s" %
4789 (count, zonename, dt, shift, prev_abbr, abbr))
4790
4791 def folds(self):
4792 for t, shift in self.transitions():
4793 if shift < ZERO:
4794 yield t, -shift
4795
4796 def gaps(self):
4797 for t, shift in self.transitions():
4798 if shift > ZERO:
4799 yield t, shift
4800
4801 def zeros(self):
4802 for t, shift in self.transitions():
4803 if not shift:
4804 yield t
4805
4806
4807class ZoneInfoTest(unittest.TestCase):
4808 zonename = 'America/New_York'
4809
4810 def setUp(self):
4811 if sys.platform == "win32":
4812 self.skipTest("Skipping zoneinfo tests on Windows")
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04004813 try:
4814 self.tz = ZoneInfo.fromname(self.zonename)
4815 except FileNotFoundError as err:
4816 self.skipTest("Skipping %s: %s" % (self.zonename, err))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004817
4818 def assertEquivDatetimes(self, a, b):
4819 self.assertEqual((a.replace(tzinfo=None), a.fold, id(a.tzinfo)),
4820 (b.replace(tzinfo=None), b.fold, id(b.tzinfo)))
4821
4822 def test_folds(self):
4823 tz = self.tz
4824 for dt, shift in tz.folds():
4825 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
4826 udt = dt + x
4827 ldt = tz.fromutc(udt.replace(tzinfo=tz))
4828 self.assertEqual(ldt.fold, 1)
4829 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
4830 self.assertEquivDatetimes(adt, ldt)
4831 utcoffset = ldt.utcoffset()
4832 self.assertEqual(ldt.replace(tzinfo=None), udt + utcoffset)
4833 # Round trip
4834 self.assertEquivDatetimes(ldt.astimezone(timezone.utc),
4835 udt.replace(tzinfo=timezone.utc))
4836
4837
4838 for x in [-timedelta.resolution, shift]:
4839 udt = dt + x
4840 udt = udt.replace(tzinfo=tz)
4841 ldt = tz.fromutc(udt)
4842 self.assertEqual(ldt.fold, 0)
4843
4844 def test_gaps(self):
4845 tz = self.tz
4846 for dt, shift in tz.gaps():
4847 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
4848 udt = dt + x
4849 udt = udt.replace(tzinfo=tz)
4850 ldt = tz.fromutc(udt)
4851 self.assertEqual(ldt.fold, 0)
4852 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
4853 self.assertEquivDatetimes(adt, ldt)
4854 utcoffset = ldt.utcoffset()
4855 self.assertEqual(ldt.replace(tzinfo=None), udt.replace(tzinfo=None) + utcoffset)
4856 # Create a local time inside the gap
4857 ldt = tz.fromutc(dt.replace(tzinfo=tz)) - shift + x
4858 self.assertLess(ldt.replace(fold=1).utcoffset(),
4859 ldt.replace(fold=0).utcoffset(),
4860 "At %s." % ldt)
4861
4862 for x in [-timedelta.resolution, shift]:
4863 udt = dt + x
4864 ldt = tz.fromutc(udt.replace(tzinfo=tz))
4865 self.assertEqual(ldt.fold, 0)
4866
4867 def test_system_transitions(self):
4868 if ('Riyadh8' in self.zonename or
4869 # From tzdata NEWS file:
4870 # The files solar87, solar88, and solar89 are no longer distributed.
4871 # They were a negative experiment - that is, a demonstration that
4872 # tz data can represent solar time only with some difficulty and error.
4873 # Their presence in the distribution caused confusion, as Riyadh
4874 # civil time was generally not solar time in those years.
4875 self.zonename.startswith('right/')):
4876 self.skipTest("Skipping %s" % self.zonename)
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04004877 tz = self.tz
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004878 TZ = os.environ.get('TZ')
4879 os.environ['TZ'] = self.zonename
4880 try:
4881 _time.tzset()
4882 for udt, shift in tz.transitions():
Alexander Belopolsky10c2dd22016-08-12 19:08:15 -04004883 if udt.year >= 2037:
4884 # System support for times around the end of 32-bit time_t
4885 # and later is flaky on many systems.
4886 break
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004887 s0 = (udt - datetime(1970, 1, 1)) // SEC
4888 ss = shift // SEC # shift seconds
4889 for x in [-40 * 3600, -20*3600, -1, 0,
4890 ss - 1, ss + 20 * 3600, ss + 40 * 3600]:
4891 s = s0 + x
4892 sdt = datetime.fromtimestamp(s)
4893 tzdt = datetime.fromtimestamp(s, tz).replace(tzinfo=None)
4894 self.assertEquivDatetimes(sdt, tzdt)
4895 s1 = sdt.timestamp()
4896 self.assertEqual(s, s1)
4897 if ss > 0: # gap
4898 # Create local time inside the gap
4899 dt = datetime.fromtimestamp(s0) - shift / 2
4900 ts0 = dt.timestamp()
4901 ts1 = dt.replace(fold=1).timestamp()
4902 self.assertEqual(ts0, s0 + ss / 2)
4903 self.assertEqual(ts1, s0 - ss / 2)
4904 finally:
4905 if TZ is None:
4906 del os.environ['TZ']
4907 else:
4908 os.environ['TZ'] = TZ
4909 _time.tzset()
4910
4911
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04004912class ZoneInfoCompleteTest(unittest.TestSuite):
4913 def __init__(self):
4914 tests = []
4915 if is_resource_enabled('tzdata'):
4916 for name in ZoneInfo.zonenames():
4917 Test = type('ZoneInfoTest[%s]' % name, (ZoneInfoTest,), {})
4918 Test.zonename = name
4919 for method in dir(Test):
4920 if method.startswith('test_'):
4921 tests.append(Test(method))
4922 super().__init__(tests)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004923
4924# Iran had a sub-minute UTC offset before 1946.
4925class IranTest(ZoneInfoTest):
Alexander Belopolsky07e2a0a2016-07-30 11:41:02 -04004926 zonename = 'Asia/Tehran'
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004927
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04004928def load_tests(loader, standard_tests, pattern):
4929 standard_tests.addTest(ZoneInfoCompleteTest())
4930 return standard_tests
4931
4932
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004933if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -05004934 unittest.main()