blob: a042efd878b341661c3cc0b9219ce0ef2915692d [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
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000889
890#############################################################################
891# date tests
892
893class TestDateOnly(unittest.TestCase):
894 # Tests here won't pass if also run on datetime objects, so don't
895 # subclass this to test datetimes too.
896
897 def test_delta_non_days_ignored(self):
898 dt = date(2000, 1, 2)
899 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
900 microseconds=5)
901 days = timedelta(delta.days)
902 self.assertEqual(days, timedelta(1))
903
904 dt2 = dt + delta
905 self.assertEqual(dt2, dt + days)
906
907 dt2 = delta + dt
908 self.assertEqual(dt2, dt + days)
909
910 dt2 = dt - delta
911 self.assertEqual(dt2, dt - days)
912
913 delta = -delta
914 days = timedelta(delta.days)
915 self.assertEqual(days, timedelta(-2))
916
917 dt2 = dt + delta
918 self.assertEqual(dt2, dt + days)
919
920 dt2 = delta + dt
921 self.assertEqual(dt2, dt + days)
922
923 dt2 = dt - delta
924 self.assertEqual(dt2, dt - days)
925
926class SubclassDate(date):
927 sub_var = 1
928
929class TestDate(HarmlessMixedComparison, unittest.TestCase):
930 # Tests here should pass for both dates and datetimes, except for a
931 # few tests that TestDateTime overrides.
932
933 theclass = date
934
935 def test_basic_attributes(self):
936 dt = self.theclass(2002, 3, 1)
937 self.assertEqual(dt.year, 2002)
938 self.assertEqual(dt.month, 3)
939 self.assertEqual(dt.day, 1)
940
941 def test_roundtrip(self):
942 for dt in (self.theclass(1, 2, 3),
943 self.theclass.today()):
944 # Verify dt -> string -> date identity.
945 s = repr(dt)
946 self.assertTrue(s.startswith('datetime.'))
947 s = s[9:]
948 dt2 = eval(s)
949 self.assertEqual(dt, dt2)
950
951 # Verify identity via reconstructing from pieces.
952 dt2 = self.theclass(dt.year, dt.month, dt.day)
953 self.assertEqual(dt, dt2)
954
955 def test_ordinal_conversions(self):
956 # Check some fixed values.
957 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
958 (1, 12, 31, 365),
959 (2, 1, 1, 366),
960 # first example from "Calendrical Calculations"
961 (1945, 11, 12, 710347)]:
962 d = self.theclass(y, m, d)
963 self.assertEqual(n, d.toordinal())
964 fromord = self.theclass.fromordinal(n)
965 self.assertEqual(d, fromord)
966 if hasattr(fromord, "hour"):
967 # if we're checking something fancier than a date, verify
968 # the extra fields have been zeroed out
969 self.assertEqual(fromord.hour, 0)
970 self.assertEqual(fromord.minute, 0)
971 self.assertEqual(fromord.second, 0)
972 self.assertEqual(fromord.microsecond, 0)
973
974 # Check first and last days of year spottily across the whole
975 # range of years supported.
976 for year in range(MINYEAR, MAXYEAR+1, 7):
977 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
978 d = self.theclass(year, 1, 1)
979 n = d.toordinal()
980 d2 = self.theclass.fromordinal(n)
981 self.assertEqual(d, d2)
982 # Verify that moving back a day gets to the end of year-1.
983 if year > 1:
984 d = self.theclass.fromordinal(n-1)
985 d2 = self.theclass(year-1, 12, 31)
986 self.assertEqual(d, d2)
987 self.assertEqual(d2.toordinal(), n-1)
988
989 # Test every day in a leap-year and a non-leap year.
990 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
991 for year, isleap in (2000, True), (2002, False):
992 n = self.theclass(year, 1, 1).toordinal()
993 for month, maxday in zip(range(1, 13), dim):
994 if month == 2 and isleap:
995 maxday += 1
996 for day in range(1, maxday+1):
997 d = self.theclass(year, month, day)
998 self.assertEqual(d.toordinal(), n)
999 self.assertEqual(d, self.theclass.fromordinal(n))
1000 n += 1
1001
1002 def test_extreme_ordinals(self):
1003 a = self.theclass.min
1004 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
1005 aord = a.toordinal()
1006 b = a.fromordinal(aord)
1007 self.assertEqual(a, b)
1008
1009 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
1010
1011 b = a + timedelta(days=1)
1012 self.assertEqual(b.toordinal(), aord + 1)
1013 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
1014
1015 a = self.theclass.max
1016 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
1017 aord = a.toordinal()
1018 b = a.fromordinal(aord)
1019 self.assertEqual(a, b)
1020
1021 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
1022
1023 b = a - timedelta(days=1)
1024 self.assertEqual(b.toordinal(), aord - 1)
1025 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
1026
1027 def test_bad_constructor_arguments(self):
1028 # bad years
1029 self.theclass(MINYEAR, 1, 1) # no exception
1030 self.theclass(MAXYEAR, 1, 1) # no exception
1031 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1032 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1033 # bad months
1034 self.theclass(2000, 1, 1) # no exception
1035 self.theclass(2000, 12, 1) # no exception
1036 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1037 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1038 # bad days
1039 self.theclass(2000, 2, 29) # no exception
1040 self.theclass(2004, 2, 29) # no exception
1041 self.theclass(2400, 2, 29) # no exception
1042 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1043 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1044 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1045 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1046 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1047 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1048
1049 def test_hash_equality(self):
1050 d = self.theclass(2000, 12, 31)
1051 # same thing
1052 e = self.theclass(2000, 12, 31)
1053 self.assertEqual(d, e)
1054 self.assertEqual(hash(d), hash(e))
1055
1056 dic = {d: 1}
1057 dic[e] = 2
1058 self.assertEqual(len(dic), 1)
1059 self.assertEqual(dic[d], 2)
1060 self.assertEqual(dic[e], 2)
1061
1062 d = self.theclass(2001, 1, 1)
1063 # same thing
1064 e = self.theclass(2001, 1, 1)
1065 self.assertEqual(d, e)
1066 self.assertEqual(hash(d), hash(e))
1067
1068 dic = {d: 1}
1069 dic[e] = 2
1070 self.assertEqual(len(dic), 1)
1071 self.assertEqual(dic[d], 2)
1072 self.assertEqual(dic[e], 2)
1073
1074 def test_computations(self):
1075 a = self.theclass(2002, 1, 31)
1076 b = self.theclass(1956, 1, 31)
1077 c = self.theclass(2001,2,1)
1078
1079 diff = a-b
1080 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1081 self.assertEqual(diff.seconds, 0)
1082 self.assertEqual(diff.microseconds, 0)
1083
1084 day = timedelta(1)
1085 week = timedelta(7)
1086 a = self.theclass(2002, 3, 2)
1087 self.assertEqual(a + day, self.theclass(2002, 3, 3))
1088 self.assertEqual(day + a, self.theclass(2002, 3, 3))
1089 self.assertEqual(a - day, self.theclass(2002, 3, 1))
1090 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
1091 self.assertEqual(a + week, self.theclass(2002, 3, 9))
1092 self.assertEqual(a - week, self.theclass(2002, 2, 23))
1093 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
1094 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
1095 self.assertEqual((a + week) - a, week)
1096 self.assertEqual((a + day) - a, day)
1097 self.assertEqual((a - week) - a, -week)
1098 self.assertEqual((a - day) - a, -day)
1099 self.assertEqual(a - (a + week), -week)
1100 self.assertEqual(a - (a + day), -day)
1101 self.assertEqual(a - (a - week), week)
1102 self.assertEqual(a - (a - day), day)
1103 self.assertEqual(c - (c - day), day)
1104
1105 # Add/sub ints or floats should be illegal
1106 for i in 1, 1.0:
1107 self.assertRaises(TypeError, lambda: a+i)
1108 self.assertRaises(TypeError, lambda: a-i)
1109 self.assertRaises(TypeError, lambda: i+a)
1110 self.assertRaises(TypeError, lambda: i-a)
1111
1112 # delta - date is senseless.
1113 self.assertRaises(TypeError, lambda: day - a)
1114 # mixing date and (delta or date) via * or // is senseless
1115 self.assertRaises(TypeError, lambda: day * a)
1116 self.assertRaises(TypeError, lambda: a * day)
1117 self.assertRaises(TypeError, lambda: day // a)
1118 self.assertRaises(TypeError, lambda: a // day)
1119 self.assertRaises(TypeError, lambda: a * a)
1120 self.assertRaises(TypeError, lambda: a // a)
1121 # date + date is senseless
1122 self.assertRaises(TypeError, lambda: a + a)
1123
1124 def test_overflow(self):
1125 tiny = self.theclass.resolution
1126
1127 for delta in [tiny, timedelta(1), timedelta(2)]:
1128 dt = self.theclass.min + delta
1129 dt -= delta # no problem
1130 self.assertRaises(OverflowError, dt.__sub__, delta)
1131 self.assertRaises(OverflowError, dt.__add__, -delta)
1132
1133 dt = self.theclass.max - delta
1134 dt += delta # no problem
1135 self.assertRaises(OverflowError, dt.__add__, delta)
1136 self.assertRaises(OverflowError, dt.__sub__, -delta)
1137
1138 def test_fromtimestamp(self):
1139 import time
1140
1141 # Try an arbitrary fixed value.
1142 year, month, day = 1999, 9, 19
1143 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
1144 d = self.theclass.fromtimestamp(ts)
1145 self.assertEqual(d.year, year)
1146 self.assertEqual(d.month, month)
1147 self.assertEqual(d.day, day)
1148
1149 def test_insane_fromtimestamp(self):
1150 # It's possible that some platform maps time_t to double,
1151 # and that this test will fail there. This test should
1152 # exempt such platforms (provided they return reasonable
1153 # results!).
1154 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001155 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001156 insane)
1157
1158 def test_today(self):
1159 import time
1160
1161 # We claim that today() is like fromtimestamp(time.time()), so
1162 # prove it.
1163 for dummy in range(3):
1164 today = self.theclass.today()
1165 ts = time.time()
1166 todayagain = self.theclass.fromtimestamp(ts)
1167 if today == todayagain:
1168 break
1169 # There are several legit reasons that could fail:
1170 # 1. It recently became midnight, between the today() and the
1171 # time() calls.
1172 # 2. The platform time() has such fine resolution that we'll
1173 # never get the same value twice.
1174 # 3. The platform time() has poor resolution, and we just
1175 # happened to call today() right before a resolution quantum
1176 # boundary.
1177 # 4. The system clock got fiddled between calls.
1178 # In any case, wait a little while and try again.
1179 time.sleep(0.1)
1180
1181 # It worked or it didn't. If it didn't, assume it's reason #2, and
1182 # let the test pass if they're within half a second of each other.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001183 if today != todayagain:
1184 self.assertAlmostEqual(todayagain, today,
1185 delta=timedelta(seconds=0.5))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001186
1187 def test_weekday(self):
1188 for i in range(7):
1189 # March 4, 2002 is a Monday
1190 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
1191 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
1192 # January 2, 1956 is a Monday
1193 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
1194 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
1195
1196 def test_isocalendar(self):
1197 # Check examples from
1198 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1199 for i in range(7):
1200 d = self.theclass(2003, 12, 22+i)
1201 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
1202 d = self.theclass(2003, 12, 29) + timedelta(i)
1203 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
1204 d = self.theclass(2004, 1, 5+i)
1205 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
1206 d = self.theclass(2009, 12, 21+i)
1207 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
1208 d = self.theclass(2009, 12, 28) + timedelta(i)
1209 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
1210 d = self.theclass(2010, 1, 4+i)
1211 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
1212
1213 def test_iso_long_years(self):
1214 # Calculate long ISO years and compare to table from
1215 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1216 ISO_LONG_YEARS_TABLE = """
1217 4 32 60 88
1218 9 37 65 93
1219 15 43 71 99
1220 20 48 76
1221 26 54 82
1222
1223 105 133 161 189
1224 111 139 167 195
1225 116 144 172
1226 122 150 178
1227 128 156 184
1228
1229 201 229 257 285
1230 207 235 263 291
1231 212 240 268 296
1232 218 246 274
1233 224 252 280
1234
1235 303 331 359 387
1236 308 336 364 392
1237 314 342 370 398
1238 320 348 376
1239 325 353 381
1240 """
1241 iso_long_years = sorted(map(int, ISO_LONG_YEARS_TABLE.split()))
1242 L = []
1243 for i in range(400):
1244 d = self.theclass(2000+i, 12, 31)
1245 d1 = self.theclass(1600+i, 12, 31)
1246 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
1247 if d.isocalendar()[1] == 53:
1248 L.append(i)
1249 self.assertEqual(L, iso_long_years)
1250
1251 def test_isoformat(self):
1252 t = self.theclass(2, 3, 2)
1253 self.assertEqual(t.isoformat(), "0002-03-02")
1254
1255 def test_ctime(self):
1256 t = self.theclass(2002, 3, 2)
1257 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
1258
1259 def test_strftime(self):
1260 t = self.theclass(2005, 3, 2)
1261 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
1262 self.assertEqual(t.strftime(""), "") # SF bug #761337
1263 self.assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784
1264
1265 self.assertRaises(TypeError, t.strftime) # needs an arg
1266 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
1267 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
1268
1269 # test that unicode input is allowed (issue 2782)
1270 self.assertEqual(t.strftime("%m"), "03")
1271
1272 # A naive object replaces %z and %Z w/ empty strings.
1273 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1274
1275 #make sure that invalid format specifiers are handled correctly
1276 #self.assertRaises(ValueError, t.strftime, "%e")
1277 #self.assertRaises(ValueError, t.strftime, "%")
1278 #self.assertRaises(ValueError, t.strftime, "%#")
1279
1280 #oh well, some systems just ignore those invalid ones.
Martin Panter46f50722016-05-26 05:35:26 +00001281 #at least, exercise them to make sure that no crashes
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001282 #are generated
1283 for f in ["%e", "%", "%#"]:
1284 try:
1285 t.strftime(f)
1286 except ValueError:
1287 pass
1288
1289 #check that this standard extension works
1290 t.strftime("%f")
1291
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001292 def test_format(self):
1293 dt = self.theclass(2007, 9, 10)
1294 self.assertEqual(dt.__format__(''), str(dt))
1295
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02001296 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001297 dt.__format__(123)
1298
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001299 # check that a derived class's __str__() gets called
1300 class A(self.theclass):
1301 def __str__(self):
1302 return 'A'
1303 a = A(2007, 9, 10)
1304 self.assertEqual(a.__format__(''), 'A')
1305
1306 # check that a derived class's strftime gets called
1307 class B(self.theclass):
1308 def strftime(self, format_spec):
1309 return 'B'
1310 b = B(2007, 9, 10)
1311 self.assertEqual(b.__format__(''), str(dt))
1312
1313 for fmt in ["m:%m d:%d y:%y",
1314 "m:%m d:%d y:%y H:%H M:%M S:%S",
1315 "%z %Z",
1316 ]:
1317 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1318 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1319 self.assertEqual(b.__format__(fmt), 'B')
1320
1321 def test_resolution_info(self):
1322 # XXX: Should min and max respect subclassing?
1323 if issubclass(self.theclass, datetime):
1324 expected_class = datetime
1325 else:
1326 expected_class = date
1327 self.assertIsInstance(self.theclass.min, expected_class)
1328 self.assertIsInstance(self.theclass.max, expected_class)
1329 self.assertIsInstance(self.theclass.resolution, timedelta)
1330 self.assertTrue(self.theclass.max > self.theclass.min)
1331
1332 def test_extreme_timedelta(self):
1333 big = self.theclass.max - self.theclass.min
1334 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
1335 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
1336 # n == 315537897599999999 ~= 2**58.13
1337 justasbig = timedelta(0, 0, n)
1338 self.assertEqual(big, justasbig)
1339 self.assertEqual(self.theclass.min + big, self.theclass.max)
1340 self.assertEqual(self.theclass.max - big, self.theclass.min)
1341
1342 def test_timetuple(self):
1343 for i in range(7):
1344 # January 2, 1956 is a Monday (0)
1345 d = self.theclass(1956, 1, 2+i)
1346 t = d.timetuple()
1347 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
1348 # February 1, 1956 is a Wednesday (2)
1349 d = self.theclass(1956, 2, 1+i)
1350 t = d.timetuple()
1351 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
1352 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
1353 # of the year.
1354 d = self.theclass(1956, 3, 1+i)
1355 t = d.timetuple()
1356 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
1357 self.assertEqual(t.tm_year, 1956)
1358 self.assertEqual(t.tm_mon, 3)
1359 self.assertEqual(t.tm_mday, 1+i)
1360 self.assertEqual(t.tm_hour, 0)
1361 self.assertEqual(t.tm_min, 0)
1362 self.assertEqual(t.tm_sec, 0)
1363 self.assertEqual(t.tm_wday, (3+i)%7)
1364 self.assertEqual(t.tm_yday, 61+i)
1365 self.assertEqual(t.tm_isdst, -1)
1366
1367 def test_pickling(self):
1368 args = 6, 7, 23
1369 orig = self.theclass(*args)
1370 for pickler, unpickler, proto in pickle_choices:
1371 green = pickler.dumps(orig, proto)
1372 derived = unpickler.loads(green)
1373 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02001374 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001375
1376 def test_compare(self):
1377 t1 = self.theclass(2, 3, 4)
1378 t2 = self.theclass(2, 3, 4)
1379 self.assertEqual(t1, t2)
1380 self.assertTrue(t1 <= t2)
1381 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001382 self.assertFalse(t1 != t2)
1383 self.assertFalse(t1 < t2)
1384 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001385
1386 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
1387 t2 = self.theclass(*args) # this is larger than t1
1388 self.assertTrue(t1 < t2)
1389 self.assertTrue(t2 > t1)
1390 self.assertTrue(t1 <= t2)
1391 self.assertTrue(t2 >= t1)
1392 self.assertTrue(t1 != t2)
1393 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001394 self.assertFalse(t1 == t2)
1395 self.assertFalse(t2 == t1)
1396 self.assertFalse(t1 > t2)
1397 self.assertFalse(t2 < t1)
1398 self.assertFalse(t1 >= t2)
1399 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001400
1401 for badarg in OTHERSTUFF:
1402 self.assertEqual(t1 == badarg, False)
1403 self.assertEqual(t1 != badarg, True)
1404 self.assertEqual(badarg == t1, False)
1405 self.assertEqual(badarg != t1, True)
1406
1407 self.assertRaises(TypeError, lambda: t1 < badarg)
1408 self.assertRaises(TypeError, lambda: t1 > badarg)
1409 self.assertRaises(TypeError, lambda: t1 >= badarg)
1410 self.assertRaises(TypeError, lambda: badarg <= t1)
1411 self.assertRaises(TypeError, lambda: badarg < t1)
1412 self.assertRaises(TypeError, lambda: badarg > t1)
1413 self.assertRaises(TypeError, lambda: badarg >= t1)
1414
1415 def test_mixed_compare(self):
1416 our = self.theclass(2000, 4, 5)
1417
1418 # Our class can be compared for equality to other classes
1419 self.assertEqual(our == 1, False)
1420 self.assertEqual(1 == our, False)
1421 self.assertEqual(our != 1, True)
1422 self.assertEqual(1 != our, True)
1423
1424 # But the ordering is undefined
1425 self.assertRaises(TypeError, lambda: our < 1)
1426 self.assertRaises(TypeError, lambda: 1 < our)
1427
1428 # Repeat those tests with a different class
1429
1430 class SomeClass:
1431 pass
1432
1433 their = SomeClass()
1434 self.assertEqual(our == their, False)
1435 self.assertEqual(their == our, False)
1436 self.assertEqual(our != their, True)
1437 self.assertEqual(their != our, True)
1438 self.assertRaises(TypeError, lambda: our < their)
1439 self.assertRaises(TypeError, lambda: their < our)
1440
1441 # However, if the other class explicitly defines ordering
1442 # relative to our class, it is allowed to do so
1443
1444 class LargerThanAnything:
1445 def __lt__(self, other):
1446 return False
1447 def __le__(self, other):
1448 return isinstance(other, LargerThanAnything)
1449 def __eq__(self, other):
1450 return isinstance(other, LargerThanAnything)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001451 def __gt__(self, other):
1452 return not isinstance(other, LargerThanAnything)
1453 def __ge__(self, other):
1454 return True
1455
1456 their = LargerThanAnything()
1457 self.assertEqual(our == their, False)
1458 self.assertEqual(their == our, False)
1459 self.assertEqual(our != their, True)
1460 self.assertEqual(their != our, True)
1461 self.assertEqual(our < their, True)
1462 self.assertEqual(their < our, False)
1463
1464 def test_bool(self):
1465 # All dates are considered true.
1466 self.assertTrue(self.theclass.min)
1467 self.assertTrue(self.theclass.max)
1468
Alexander Belopolsky89da3492011-05-02 13:14:24 -04001469 def test_strftime_y2k(self):
1470 for y in (1, 49, 70, 99, 100, 999, 1000, 1970):
Florent Xicluna49ce0682011-11-01 12:56:14 +01001471 d = self.theclass(y, 1, 1)
1472 # Issue 13305: For years < 1000, the value is not always
1473 # padded to 4 digits across platforms. The C standard
1474 # assumes year >= 1900, so it does not specify the number
1475 # of digits.
1476 if d.strftime("%Y") != '%04d' % y:
1477 # Year 42 returns '42', not padded
1478 self.assertEqual(d.strftime("%Y"), '%d' % y)
Victor Stinner15a83e82016-03-12 08:16:48 +01001479 # '0042' is obtained anyway
1480 self.assertEqual(d.strftime("%4Y"), '%04d' % y)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001481
1482 def test_replace(self):
1483 cls = self.theclass
1484 args = [1, 2, 3]
1485 base = cls(*args)
1486 self.assertEqual(base, base.replace())
1487
1488 i = 0
1489 for name, newval in (("year", 2),
1490 ("month", 3),
1491 ("day", 4)):
1492 newargs = args[:]
1493 newargs[i] = newval
1494 expected = cls(*newargs)
1495 got = base.replace(**{name: newval})
1496 self.assertEqual(expected, got)
1497 i += 1
1498
1499 # Out of bounds.
1500 base = cls(2000, 2, 29)
1501 self.assertRaises(ValueError, base.replace, year=2001)
1502
1503 def test_subclass_date(self):
1504
1505 class C(self.theclass):
1506 theAnswer = 42
1507
1508 def __new__(cls, *args, **kws):
1509 temp = kws.copy()
1510 extra = temp.pop('extra')
1511 result = self.theclass.__new__(cls, *args, **temp)
1512 result.extra = extra
1513 return result
1514
1515 def newmeth(self, start):
1516 return start + self.year + self.month
1517
1518 args = 2003, 4, 14
1519
1520 dt1 = self.theclass(*args)
1521 dt2 = C(*args, **{'extra': 7})
1522
1523 self.assertEqual(dt2.__class__, C)
1524 self.assertEqual(dt2.theAnswer, 42)
1525 self.assertEqual(dt2.extra, 7)
1526 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1527 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1528
1529 def test_pickling_subclass_date(self):
1530
1531 args = 6, 7, 23
1532 orig = SubclassDate(*args)
1533 for pickler, unpickler, proto in pickle_choices:
1534 green = pickler.dumps(orig, proto)
1535 derived = unpickler.loads(green)
1536 self.assertEqual(orig, derived)
1537
1538 def test_backdoor_resistance(self):
1539 # For fast unpickling, the constructor accepts a pickle byte string.
1540 # This is a low-overhead backdoor. A user can (by intent or
1541 # mistake) pass a string directly, which (if it's the right length)
1542 # will get treated like a pickle, and bypass the normal sanity
1543 # checks in the constructor. This can create insane objects.
1544 # The constructor doesn't want to burn the time to validate all
1545 # fields, but does check the month field. This stops, e.g.,
1546 # datetime.datetime('1995-03-25') from yielding an insane object.
1547 base = b'1995-03-25'
1548 if not issubclass(self.theclass, datetime):
1549 base = base[:4]
1550 for month_byte in b'9', b'\0', b'\r', b'\xff':
1551 self.assertRaises(TypeError, self.theclass,
1552 base[:2] + month_byte + base[3:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001553 if issubclass(self.theclass, datetime):
1554 # Good bytes, but bad tzinfo:
1555 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
1556 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001557
1558 for ord_byte in range(1, 13):
1559 # This shouldn't blow up because of the month byte alone. If
1560 # the implementation changes to do more-careful checking, it may
1561 # blow up because other fields are insane.
1562 self.theclass(base[:2] + bytes([ord_byte]) + base[3:])
1563
1564#############################################################################
1565# datetime tests
1566
1567class SubclassDatetime(datetime):
1568 sub_var = 1
1569
1570class TestDateTime(TestDate):
1571
1572 theclass = datetime
1573
1574 def test_basic_attributes(self):
1575 dt = self.theclass(2002, 3, 1, 12, 0)
1576 self.assertEqual(dt.year, 2002)
1577 self.assertEqual(dt.month, 3)
1578 self.assertEqual(dt.day, 1)
1579 self.assertEqual(dt.hour, 12)
1580 self.assertEqual(dt.minute, 0)
1581 self.assertEqual(dt.second, 0)
1582 self.assertEqual(dt.microsecond, 0)
1583
1584 def test_basic_attributes_nonzero(self):
1585 # Make sure all attributes are non-zero so bugs in
1586 # bit-shifting access show up.
1587 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1588 self.assertEqual(dt.year, 2002)
1589 self.assertEqual(dt.month, 3)
1590 self.assertEqual(dt.day, 1)
1591 self.assertEqual(dt.hour, 12)
1592 self.assertEqual(dt.minute, 59)
1593 self.assertEqual(dt.second, 59)
1594 self.assertEqual(dt.microsecond, 8000)
1595
1596 def test_roundtrip(self):
1597 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1598 self.theclass.now()):
1599 # Verify dt -> string -> datetime identity.
1600 s = repr(dt)
1601 self.assertTrue(s.startswith('datetime.'))
1602 s = s[9:]
1603 dt2 = eval(s)
1604 self.assertEqual(dt, dt2)
1605
1606 # Verify identity via reconstructing from pieces.
1607 dt2 = self.theclass(dt.year, dt.month, dt.day,
1608 dt.hour, dt.minute, dt.second,
1609 dt.microsecond)
1610 self.assertEqual(dt, dt2)
1611
1612 def test_isoformat(self):
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001613 t = self.theclass(1, 2, 3, 4, 5, 1, 123)
1614 self.assertEqual(t.isoformat(), "0001-02-03T04:05:01.000123")
1615 self.assertEqual(t.isoformat('T'), "0001-02-03T04:05:01.000123")
1616 self.assertEqual(t.isoformat(' '), "0001-02-03 04:05:01.000123")
1617 self.assertEqual(t.isoformat('\x00'), "0001-02-03\x0004:05:01.000123")
1618 self.assertEqual(t.isoformat(timespec='hours'), "0001-02-03T04")
1619 self.assertEqual(t.isoformat(timespec='minutes'), "0001-02-03T04:05")
1620 self.assertEqual(t.isoformat(timespec='seconds'), "0001-02-03T04:05:01")
1621 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1622 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000123")
1623 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01.000123")
1624 self.assertEqual(t.isoformat(sep=' ', timespec='minutes'), "0001-02-03 04:05")
1625 self.assertRaises(ValueError, t.isoformat, timespec='foo')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001626 # str is ISO format with the separator forced to a blank.
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001627 self.assertEqual(str(t), "0001-02-03 04:05:01.000123")
1628
1629 t = self.theclass(1, 2, 3, 4, 5, 1, 999500, tzinfo=timezone.utc)
1630 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999+00:00")
1631
1632 t = self.theclass(1, 2, 3, 4, 5, 1, 999500)
1633 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999")
1634
1635 t = self.theclass(1, 2, 3, 4, 5, 1)
1636 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01")
1637 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1638 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000000")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001639
1640 t = self.theclass(2, 3, 2)
1641 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1642 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1643 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1644 # str is ISO format with the separator forced to a blank.
1645 self.assertEqual(str(t), "0002-03-02 00:00:00")
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001646 # ISO format with timezone
1647 tz = FixedOffset(timedelta(seconds=16), 'XXX')
1648 t = self.theclass(2, 3, 2, tzinfo=tz)
1649 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00+00:00:16")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001650
1651 def test_format(self):
1652 dt = self.theclass(2007, 9, 10, 4, 5, 1, 123)
1653 self.assertEqual(dt.__format__(''), str(dt))
1654
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02001655 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001656 dt.__format__(123)
1657
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001658 # check that a derived class's __str__() gets called
1659 class A(self.theclass):
1660 def __str__(self):
1661 return 'A'
1662 a = A(2007, 9, 10, 4, 5, 1, 123)
1663 self.assertEqual(a.__format__(''), 'A')
1664
1665 # check that a derived class's strftime gets called
1666 class B(self.theclass):
1667 def strftime(self, format_spec):
1668 return 'B'
1669 b = B(2007, 9, 10, 4, 5, 1, 123)
1670 self.assertEqual(b.__format__(''), str(dt))
1671
1672 for fmt in ["m:%m d:%d y:%y",
1673 "m:%m d:%d y:%y H:%H M:%M S:%S",
1674 "%z %Z",
1675 ]:
1676 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1677 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1678 self.assertEqual(b.__format__(fmt), 'B')
1679
1680 def test_more_ctime(self):
1681 # Test fields that TestDate doesn't touch.
1682 import time
1683
1684 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1685 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1686 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1687 # out. The difference is that t.ctime() produces " 2" for the day,
1688 # but platform ctime() produces "02" for the day. According to
1689 # C99, t.ctime() is correct here.
1690 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1691
1692 # So test a case where that difference doesn't matter.
1693 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1694 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1695
1696 def test_tz_independent_comparing(self):
1697 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1698 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1699 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1700 self.assertEqual(dt1, dt3)
1701 self.assertTrue(dt2 > dt3)
1702
1703 # Make sure comparison doesn't forget microseconds, and isn't done
1704 # via comparing a float timestamp (an IEEE double doesn't have enough
1705 # precision to span microsecond resolution across years 1 thru 9999,
1706 # so comparing via timestamp necessarily calls some distinct values
1707 # equal).
1708 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1709 us = timedelta(microseconds=1)
1710 dt2 = dt1 + us
1711 self.assertEqual(dt2 - dt1, us)
1712 self.assertTrue(dt1 < dt2)
1713
1714 def test_strftime_with_bad_tzname_replace(self):
1715 # verify ok if tzinfo.tzname().replace() returns a non-string
1716 class MyTzInfo(FixedOffset):
1717 def tzname(self, dt):
1718 class MyStr(str):
1719 def replace(self, *args):
1720 return None
1721 return MyStr('name')
1722 t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
1723 self.assertRaises(TypeError, t.strftime, '%Z')
1724
1725 def test_bad_constructor_arguments(self):
1726 # bad years
1727 self.theclass(MINYEAR, 1, 1) # no exception
1728 self.theclass(MAXYEAR, 1, 1) # no exception
1729 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1730 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1731 # bad months
1732 self.theclass(2000, 1, 1) # no exception
1733 self.theclass(2000, 12, 1) # no exception
1734 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1735 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1736 # bad days
1737 self.theclass(2000, 2, 29) # no exception
1738 self.theclass(2004, 2, 29) # no exception
1739 self.theclass(2400, 2, 29) # no exception
1740 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1741 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1742 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1743 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1744 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1745 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1746 # bad hours
1747 self.theclass(2000, 1, 31, 0) # no exception
1748 self.theclass(2000, 1, 31, 23) # no exception
1749 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1750 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1751 # bad minutes
1752 self.theclass(2000, 1, 31, 23, 0) # no exception
1753 self.theclass(2000, 1, 31, 23, 59) # no exception
1754 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1755 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1756 # bad seconds
1757 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1758 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1759 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1760 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1761 # bad microseconds
1762 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1763 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1764 self.assertRaises(ValueError, self.theclass,
1765 2000, 1, 31, 23, 59, 59, -1)
1766 self.assertRaises(ValueError, self.theclass,
1767 2000, 1, 31, 23, 59, 59,
1768 1000000)
Alexander Belopolsky47649ab2016-08-08 17:05:40 -04001769 # bad fold
1770 self.assertRaises(ValueError, self.theclass,
1771 2000, 1, 31, fold=-1)
1772 self.assertRaises(ValueError, self.theclass,
1773 2000, 1, 31, fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001774 # Positional fold:
1775 self.assertRaises(TypeError, self.theclass,
1776 2000, 1, 31, 23, 59, 59, 0, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04001777
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001778 def test_hash_equality(self):
1779 d = self.theclass(2000, 12, 31, 23, 30, 17)
1780 e = self.theclass(2000, 12, 31, 23, 30, 17)
1781 self.assertEqual(d, e)
1782 self.assertEqual(hash(d), hash(e))
1783
1784 dic = {d: 1}
1785 dic[e] = 2
1786 self.assertEqual(len(dic), 1)
1787 self.assertEqual(dic[d], 2)
1788 self.assertEqual(dic[e], 2)
1789
1790 d = self.theclass(2001, 1, 1, 0, 5, 17)
1791 e = self.theclass(2001, 1, 1, 0, 5, 17)
1792 self.assertEqual(d, e)
1793 self.assertEqual(hash(d), hash(e))
1794
1795 dic = {d: 1}
1796 dic[e] = 2
1797 self.assertEqual(len(dic), 1)
1798 self.assertEqual(dic[d], 2)
1799 self.assertEqual(dic[e], 2)
1800
1801 def test_computations(self):
1802 a = self.theclass(2002, 1, 31)
1803 b = self.theclass(1956, 1, 31)
1804 diff = a-b
1805 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1806 self.assertEqual(diff.seconds, 0)
1807 self.assertEqual(diff.microseconds, 0)
1808 a = self.theclass(2002, 3, 2, 17, 6)
1809 millisec = timedelta(0, 0, 1000)
1810 hour = timedelta(0, 3600)
1811 day = timedelta(1)
1812 week = timedelta(7)
1813 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1814 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1815 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1816 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1817 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1818 self.assertEqual(a - hour, a + -hour)
1819 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1820 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1821 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1822 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1823 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1824 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1825 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1826 self.assertEqual((a + week) - a, week)
1827 self.assertEqual((a + day) - a, day)
1828 self.assertEqual((a + hour) - a, hour)
1829 self.assertEqual((a + millisec) - a, millisec)
1830 self.assertEqual((a - week) - a, -week)
1831 self.assertEqual((a - day) - a, -day)
1832 self.assertEqual((a - hour) - a, -hour)
1833 self.assertEqual((a - millisec) - a, -millisec)
1834 self.assertEqual(a - (a + week), -week)
1835 self.assertEqual(a - (a + day), -day)
1836 self.assertEqual(a - (a + hour), -hour)
1837 self.assertEqual(a - (a + millisec), -millisec)
1838 self.assertEqual(a - (a - week), week)
1839 self.assertEqual(a - (a - day), day)
1840 self.assertEqual(a - (a - hour), hour)
1841 self.assertEqual(a - (a - millisec), millisec)
1842 self.assertEqual(a + (week + day + hour + millisec),
1843 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1844 self.assertEqual(a + (week + day + hour + millisec),
1845 (((a + week) + day) + hour) + millisec)
1846 self.assertEqual(a - (week + day + hour + millisec),
1847 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1848 self.assertEqual(a - (week + day + hour + millisec),
1849 (((a - week) - day) - hour) - millisec)
1850 # Add/sub ints or floats should be illegal
1851 for i in 1, 1.0:
1852 self.assertRaises(TypeError, lambda: a+i)
1853 self.assertRaises(TypeError, lambda: a-i)
1854 self.assertRaises(TypeError, lambda: i+a)
1855 self.assertRaises(TypeError, lambda: i-a)
1856
1857 # delta - datetime is senseless.
1858 self.assertRaises(TypeError, lambda: day - a)
1859 # mixing datetime and (delta or datetime) via * or // is senseless
1860 self.assertRaises(TypeError, lambda: day * a)
1861 self.assertRaises(TypeError, lambda: a * day)
1862 self.assertRaises(TypeError, lambda: day // a)
1863 self.assertRaises(TypeError, lambda: a // day)
1864 self.assertRaises(TypeError, lambda: a * a)
1865 self.assertRaises(TypeError, lambda: a // a)
1866 # datetime + datetime is senseless
1867 self.assertRaises(TypeError, lambda: a + a)
1868
1869 def test_pickling(self):
1870 args = 6, 7, 23, 20, 59, 1, 64**2
1871 orig = self.theclass(*args)
1872 for pickler, unpickler, proto in pickle_choices:
1873 green = pickler.dumps(orig, proto)
1874 derived = unpickler.loads(green)
1875 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02001876 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001877
1878 def test_more_pickling(self):
1879 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
Serhiy Storchakabad12572014-12-15 14:03:42 +02001880 for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1881 s = pickle.dumps(a, proto)
1882 b = pickle.loads(s)
1883 self.assertEqual(b.year, 2003)
1884 self.assertEqual(b.month, 2)
1885 self.assertEqual(b.day, 7)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001886
1887 def test_pickling_subclass_datetime(self):
1888 args = 6, 7, 23, 20, 59, 1, 64**2
1889 orig = SubclassDatetime(*args)
1890 for pickler, unpickler, proto in pickle_choices:
1891 green = pickler.dumps(orig, proto)
1892 derived = unpickler.loads(green)
1893 self.assertEqual(orig, derived)
1894
1895 def test_more_compare(self):
1896 # The test_compare() inherited from TestDate covers the error cases.
1897 # We just want to test lexicographic ordering on the members datetime
1898 # has that date lacks.
1899 args = [2000, 11, 29, 20, 58, 16, 999998]
1900 t1 = self.theclass(*args)
1901 t2 = self.theclass(*args)
1902 self.assertEqual(t1, t2)
1903 self.assertTrue(t1 <= t2)
1904 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001905 self.assertFalse(t1 != t2)
1906 self.assertFalse(t1 < t2)
1907 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001908
1909 for i in range(len(args)):
1910 newargs = args[:]
1911 newargs[i] = args[i] + 1
1912 t2 = self.theclass(*newargs) # this is larger than t1
1913 self.assertTrue(t1 < t2)
1914 self.assertTrue(t2 > t1)
1915 self.assertTrue(t1 <= t2)
1916 self.assertTrue(t2 >= t1)
1917 self.assertTrue(t1 != t2)
1918 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001919 self.assertFalse(t1 == t2)
1920 self.assertFalse(t2 == t1)
1921 self.assertFalse(t1 > t2)
1922 self.assertFalse(t2 < t1)
1923 self.assertFalse(t1 >= t2)
1924 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001925
1926
1927 # A helper for timestamp constructor tests.
1928 def verify_field_equality(self, expected, got):
1929 self.assertEqual(expected.tm_year, got.year)
1930 self.assertEqual(expected.tm_mon, got.month)
1931 self.assertEqual(expected.tm_mday, got.day)
1932 self.assertEqual(expected.tm_hour, got.hour)
1933 self.assertEqual(expected.tm_min, got.minute)
1934 self.assertEqual(expected.tm_sec, got.second)
1935
1936 def test_fromtimestamp(self):
1937 import time
1938
1939 ts = time.time()
1940 expected = time.localtime(ts)
1941 got = self.theclass.fromtimestamp(ts)
1942 self.verify_field_equality(expected, got)
1943
1944 def test_utcfromtimestamp(self):
1945 import time
1946
1947 ts = time.time()
1948 expected = time.gmtime(ts)
1949 got = self.theclass.utcfromtimestamp(ts)
1950 self.verify_field_equality(expected, got)
1951
Alexander Belopolskya4415142012-06-08 12:33:09 -04001952 # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in
1953 # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0).
1954 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
1955 def test_timestamp_naive(self):
1956 t = self.theclass(1970, 1, 1)
1957 self.assertEqual(t.timestamp(), 18000.0)
1958 t = self.theclass(1970, 1, 1, 1, 2, 3, 4)
1959 self.assertEqual(t.timestamp(),
1960 18000.0 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001961 # Missing hour
1962 t0 = self.theclass(2012, 3, 11, 2, 30)
1963 t1 = t0.replace(fold=1)
1964 self.assertEqual(self.theclass.fromtimestamp(t1.timestamp()),
1965 t0 - timedelta(hours=1))
1966 self.assertEqual(self.theclass.fromtimestamp(t0.timestamp()),
1967 t1 + timedelta(hours=1))
Alexander Belopolskya4415142012-06-08 12:33:09 -04001968 # Ambiguous hour defaults to DST
1969 t = self.theclass(2012, 11, 4, 1, 30)
1970 self.assertEqual(self.theclass.fromtimestamp(t.timestamp()), t)
1971
1972 # Timestamp may raise an overflow error on some platforms
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001973 # XXX: Do we care to support the first and last year?
1974 for t in [self.theclass(2,1,1), self.theclass(9998,12,12)]:
Alexander Belopolskya4415142012-06-08 12:33:09 -04001975 try:
1976 s = t.timestamp()
1977 except OverflowError:
1978 pass
1979 else:
1980 self.assertEqual(self.theclass.fromtimestamp(s), t)
1981
1982 def test_timestamp_aware(self):
1983 t = self.theclass(1970, 1, 1, tzinfo=timezone.utc)
1984 self.assertEqual(t.timestamp(), 0.0)
1985 t = self.theclass(1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone.utc)
1986 self.assertEqual(t.timestamp(),
1987 3600 + 2*60 + 3 + 4*1e-6)
1988 t = self.theclass(1970, 1, 1, 1, 2, 3, 4,
1989 tzinfo=timezone(timedelta(hours=-5), 'EST'))
1990 self.assertEqual(t.timestamp(),
1991 18000 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001992
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001993 @support.run_with_tz('MSK-03') # Something east of Greenwich
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001994 def test_microsecond_rounding(self):
Victor Stinner5ebfe422015-09-18 14:52:15 +02001995 for fts in [self.theclass.fromtimestamp,
1996 self.theclass.utcfromtimestamp]:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001997 zero = fts(0)
1998 self.assertEqual(zero.second, 0)
1999 self.assertEqual(zero.microsecond, 0)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002000 one = fts(1e-6)
Victor Stinner8050ca92012-03-14 00:17:05 +01002001 try:
2002 minus_one = fts(-1e-6)
2003 except OSError:
2004 # localtime(-1) and gmtime(-1) is not supported on Windows
2005 pass
2006 else:
2007 self.assertEqual(minus_one.second, 59)
2008 self.assertEqual(minus_one.microsecond, 999999)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002009
Victor Stinner8050ca92012-03-14 00:17:05 +01002010 t = fts(-1e-8)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002011 self.assertEqual(t, zero)
Victor Stinner8050ca92012-03-14 00:17:05 +01002012 t = fts(-9e-7)
2013 self.assertEqual(t, minus_one)
2014 t = fts(-1e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002015 self.assertEqual(t, zero)
Victor Stinner8820a352015-09-05 10:50:20 +02002016 t = fts(-1/2**7)
2017 self.assertEqual(t.second, 59)
Victor Stinner7667f582015-09-09 01:02:23 +02002018 self.assertEqual(t.microsecond, 992188)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002019
2020 t = fts(1e-7)
2021 self.assertEqual(t, zero)
2022 t = fts(9e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002023 self.assertEqual(t, one)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002024 t = fts(0.99999949)
2025 self.assertEqual(t.second, 0)
2026 self.assertEqual(t.microsecond, 999999)
2027 t = fts(0.9999999)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002028 self.assertEqual(t.second, 1)
2029 self.assertEqual(t.microsecond, 0)
Victor Stinneradfefa52015-09-04 23:57:25 +02002030 t = fts(1/2**7)
2031 self.assertEqual(t.second, 0)
Victor Stinner7667f582015-09-09 01:02:23 +02002032 self.assertEqual(t.microsecond, 7812)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002033
Victor Stinnerb67f0962017-02-10 10:34:02 +01002034 def test_timestamp_limits(self):
2035 # minimum timestamp
2036 min_dt = self.theclass.min.replace(tzinfo=timezone.utc)
2037 min_ts = min_dt.timestamp()
Victor Stinner6f37e362017-02-10 11:45:14 +01002038 try:
2039 # date 0001-01-01 00:00:00+00:00: timestamp=-62135596800
2040 self.assertEqual(self.theclass.fromtimestamp(min_ts, tz=timezone.utc),
2041 min_dt)
Victor Stinner2a35c932017-02-10 12:37:21 +01002042 except (OverflowError, OSError) as exc:
2043 # the date 0001-01-01 doesn't fit into 32-bit time_t,
2044 # or platform doesn't support such very old date
Victor Stinner6f37e362017-02-10 11:45:14 +01002045 self.skipTest(str(exc))
Victor Stinnerb67f0962017-02-10 10:34:02 +01002046
2047 # maximum timestamp: set seconds to zero to avoid rounding issues
2048 max_dt = self.theclass.max.replace(tzinfo=timezone.utc,
2049 second=0, microsecond=0)
2050 max_ts = max_dt.timestamp()
2051 # date 9999-12-31 23:59:00+00:00: timestamp 253402300740
2052 self.assertEqual(self.theclass.fromtimestamp(max_ts, tz=timezone.utc),
2053 max_dt)
2054
2055 # number of seconds greater than 1 year: make sure that the new date
2056 # is not valid in datetime.datetime limits
2057 delta = 3600 * 24 * 400
2058
2059 # too small
2060 ts = min_ts - delta
2061 # converting a Python int to C time_t can raise a OverflowError,
2062 # especially on 32-bit platforms.
2063 with self.assertRaises((ValueError, OverflowError)):
2064 self.theclass.fromtimestamp(ts)
2065 with self.assertRaises((ValueError, OverflowError)):
2066 self.theclass.utcfromtimestamp(ts)
2067
2068 # too big
2069 ts = max_dt.timestamp() + delta
2070 with self.assertRaises((ValueError, OverflowError)):
2071 self.theclass.fromtimestamp(ts)
2072 with self.assertRaises((ValueError, OverflowError)):
2073 self.theclass.utcfromtimestamp(ts)
2074
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002075 def test_insane_fromtimestamp(self):
2076 # It's possible that some platform maps time_t to double,
2077 # and that this test will fail there. This test should
2078 # exempt such platforms (provided they return reasonable
2079 # results!).
2080 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002081 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002082 insane)
2083
2084 def test_insane_utcfromtimestamp(self):
2085 # It's possible that some platform maps time_t to double,
2086 # and that this test will fail there. This test should
2087 # exempt such platforms (provided they return reasonable
2088 # results!).
2089 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002090 self.assertRaises(OverflowError, self.theclass.utcfromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002091 insane)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002092
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002093 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2094 def test_negative_float_fromtimestamp(self):
2095 # The result is tz-dependent; at least test that this doesn't
2096 # fail (like it did before bug 1646728 was fixed).
2097 self.theclass.fromtimestamp(-1.05)
2098
2099 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2100 def test_negative_float_utcfromtimestamp(self):
2101 d = self.theclass.utcfromtimestamp(-1.05)
2102 self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
2103
2104 def test_utcnow(self):
2105 import time
2106
2107 # Call it a success if utcnow() and utcfromtimestamp() are within
2108 # a second of each other.
2109 tolerance = timedelta(seconds=1)
2110 for dummy in range(3):
2111 from_now = self.theclass.utcnow()
2112 from_timestamp = self.theclass.utcfromtimestamp(time.time())
2113 if abs(from_timestamp - from_now) <= tolerance:
2114 break
2115 # Else try again a few times.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002116 self.assertLessEqual(abs(from_timestamp - from_now), tolerance)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002117
2118 def test_strptime(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002119 string = '2004-12-01 13:02:47.197'
2120 format = '%Y-%m-%d %H:%M:%S.%f'
2121 expected = _strptime._strptime_datetime(self.theclass, string, format)
2122 got = self.theclass.strptime(string, format)
2123 self.assertEqual(expected, got)
2124 self.assertIs(type(expected), self.theclass)
2125 self.assertIs(type(got), self.theclass)
2126
2127 strptime = self.theclass.strptime
2128 self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
2129 self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
2130 # Only local timezone and UTC are supported
2131 for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
2132 (-_time.timezone, _time.tzname[0])):
2133 if tzseconds < 0:
2134 sign = '-'
2135 seconds = -tzseconds
2136 else:
2137 sign ='+'
2138 seconds = tzseconds
2139 hours, minutes = divmod(seconds//60, 60)
2140 dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
Martin Panterfca22322015-11-16 09:22:19 +00002141 dt = strptime(dtstr, "%z %Z")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002142 self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
2143 self.assertEqual(dt.tzname(), tzname)
2144 # Can produce inconsistent datetime
2145 dtstr, fmt = "+1234 UTC", "%z %Z"
2146 dt = strptime(dtstr, fmt)
2147 self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE)
2148 self.assertEqual(dt.tzname(), 'UTC')
2149 # yet will roundtrip
2150 self.assertEqual(dt.strftime(fmt), dtstr)
2151
2152 # Produce naive datetime if no %z is provided
2153 self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
2154
2155 with self.assertRaises(ValueError): strptime("-2400", "%z")
2156 with self.assertRaises(ValueError): strptime("-000", "%z")
2157
2158 def test_more_timetuple(self):
2159 # This tests fields beyond those tested by the TestDate.test_timetuple.
2160 t = self.theclass(2004, 12, 31, 6, 22, 33)
2161 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
2162 self.assertEqual(t.timetuple(),
2163 (t.year, t.month, t.day,
2164 t.hour, t.minute, t.second,
2165 t.weekday(),
2166 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
2167 -1))
2168 tt = t.timetuple()
2169 self.assertEqual(tt.tm_year, t.year)
2170 self.assertEqual(tt.tm_mon, t.month)
2171 self.assertEqual(tt.tm_mday, t.day)
2172 self.assertEqual(tt.tm_hour, t.hour)
2173 self.assertEqual(tt.tm_min, t.minute)
2174 self.assertEqual(tt.tm_sec, t.second)
2175 self.assertEqual(tt.tm_wday, t.weekday())
2176 self.assertEqual(tt.tm_yday, t.toordinal() -
2177 date(t.year, 1, 1).toordinal() + 1)
2178 self.assertEqual(tt.tm_isdst, -1)
2179
2180 def test_more_strftime(self):
2181 # This tests fields beyond those tested by the TestDate.test_strftime.
2182 t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
2183 self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
2184 "12 31 04 000047 33 22 06 366")
Alexander Belopolsky018d3532017-07-31 10:26:50 -04002185 tz = timezone(-timedelta(hours=2, seconds=33, microseconds=123))
2186 t = t.replace(tzinfo=tz)
2187 self.assertEqual(t.strftime("%z"), "-020033.000123")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002188
2189 def test_extract(self):
2190 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2191 self.assertEqual(dt.date(), date(2002, 3, 4))
2192 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2193
2194 def test_combine(self):
2195 d = date(2002, 3, 4)
2196 t = time(18, 45, 3, 1234)
2197 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2198 combine = self.theclass.combine
2199 dt = combine(d, t)
2200 self.assertEqual(dt, expected)
2201
2202 dt = combine(time=t, date=d)
2203 self.assertEqual(dt, expected)
2204
2205 self.assertEqual(d, dt.date())
2206 self.assertEqual(t, dt.time())
2207 self.assertEqual(dt, combine(dt.date(), dt.time()))
2208
2209 self.assertRaises(TypeError, combine) # need an arg
2210 self.assertRaises(TypeError, combine, d) # need two args
2211 self.assertRaises(TypeError, combine, t, d) # args reversed
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002212 self.assertRaises(TypeError, combine, d, t, 1) # wrong tzinfo type
2213 self.assertRaises(TypeError, combine, d, t, 1, 2) # too many args
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002214 self.assertRaises(TypeError, combine, "date", "time") # wrong types
2215 self.assertRaises(TypeError, combine, d, "time") # wrong type
2216 self.assertRaises(TypeError, combine, "date", t) # wrong type
2217
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002218 # tzinfo= argument
2219 dt = combine(d, t, timezone.utc)
2220 self.assertIs(dt.tzinfo, timezone.utc)
2221 dt = combine(d, t, tzinfo=timezone.utc)
2222 self.assertIs(dt.tzinfo, timezone.utc)
2223 t = time()
2224 dt = combine(dt, t)
2225 self.assertEqual(dt.date(), d)
2226 self.assertEqual(dt.time(), t)
2227
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002228 def test_replace(self):
2229 cls = self.theclass
2230 args = [1, 2, 3, 4, 5, 6, 7]
2231 base = cls(*args)
2232 self.assertEqual(base, base.replace())
2233
2234 i = 0
2235 for name, newval in (("year", 2),
2236 ("month", 3),
2237 ("day", 4),
2238 ("hour", 5),
2239 ("minute", 6),
2240 ("second", 7),
2241 ("microsecond", 8)):
2242 newargs = args[:]
2243 newargs[i] = newval
2244 expected = cls(*newargs)
2245 got = base.replace(**{name: newval})
2246 self.assertEqual(expected, got)
2247 i += 1
2248
2249 # Out of bounds.
2250 base = cls(2000, 2, 29)
2251 self.assertRaises(ValueError, base.replace, year=2001)
2252
2253 def test_astimezone(self):
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002254 return # The rest is no longer applicable
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002255 # Pretty boring! The TZ test is more interesting here. astimezone()
2256 # simply can't be applied to a naive object.
2257 dt = self.theclass.now()
2258 f = FixedOffset(44, "")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04002259 self.assertRaises(ValueError, dt.astimezone) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002260 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
2261 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
2262 self.assertRaises(ValueError, dt.astimezone, f) # naive
2263 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
2264
2265 class Bogus(tzinfo):
2266 def utcoffset(self, dt): return None
2267 def dst(self, dt): return timedelta(0)
2268 bog = Bogus()
2269 self.assertRaises(ValueError, dt.astimezone, bog) # naive
2270 self.assertRaises(ValueError,
2271 dt.replace(tzinfo=bog).astimezone, f)
2272
2273 class AlsoBogus(tzinfo):
2274 def utcoffset(self, dt): return timedelta(0)
2275 def dst(self, dt): return None
2276 alsobog = AlsoBogus()
2277 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
2278
2279 def test_subclass_datetime(self):
2280
2281 class C(self.theclass):
2282 theAnswer = 42
2283
2284 def __new__(cls, *args, **kws):
2285 temp = kws.copy()
2286 extra = temp.pop('extra')
2287 result = self.theclass.__new__(cls, *args, **temp)
2288 result.extra = extra
2289 return result
2290
2291 def newmeth(self, start):
2292 return start + self.year + self.month + self.second
2293
2294 args = 2003, 4, 14, 12, 13, 41
2295
2296 dt1 = self.theclass(*args)
2297 dt2 = C(*args, **{'extra': 7})
2298
2299 self.assertEqual(dt2.__class__, C)
2300 self.assertEqual(dt2.theAnswer, 42)
2301 self.assertEqual(dt2.extra, 7)
2302 self.assertEqual(dt1.toordinal(), dt2.toordinal())
2303 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
2304 dt1.second - 7)
2305
2306class TestSubclassDateTime(TestDateTime):
2307 theclass = SubclassDatetime
2308 # Override tests not designed for subclass
Zachary Ware9fe6d862013-12-08 00:20:35 -06002309 @unittest.skip('not appropriate for subclasses')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002310 def test_roundtrip(self):
2311 pass
2312
2313class SubclassTime(time):
2314 sub_var = 1
2315
2316class TestTime(HarmlessMixedComparison, unittest.TestCase):
2317
2318 theclass = time
2319
2320 def test_basic_attributes(self):
2321 t = self.theclass(12, 0)
2322 self.assertEqual(t.hour, 12)
2323 self.assertEqual(t.minute, 0)
2324 self.assertEqual(t.second, 0)
2325 self.assertEqual(t.microsecond, 0)
2326
2327 def test_basic_attributes_nonzero(self):
2328 # Make sure all attributes are non-zero so bugs in
2329 # bit-shifting access show up.
2330 t = self.theclass(12, 59, 59, 8000)
2331 self.assertEqual(t.hour, 12)
2332 self.assertEqual(t.minute, 59)
2333 self.assertEqual(t.second, 59)
2334 self.assertEqual(t.microsecond, 8000)
2335
2336 def test_roundtrip(self):
2337 t = self.theclass(1, 2, 3, 4)
2338
2339 # Verify t -> string -> time identity.
2340 s = repr(t)
2341 self.assertTrue(s.startswith('datetime.'))
2342 s = s[9:]
2343 t2 = eval(s)
2344 self.assertEqual(t, t2)
2345
2346 # Verify identity via reconstructing from pieces.
2347 t2 = self.theclass(t.hour, t.minute, t.second,
2348 t.microsecond)
2349 self.assertEqual(t, t2)
2350
2351 def test_comparing(self):
2352 args = [1, 2, 3, 4]
2353 t1 = self.theclass(*args)
2354 t2 = self.theclass(*args)
2355 self.assertEqual(t1, t2)
2356 self.assertTrue(t1 <= t2)
2357 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002358 self.assertFalse(t1 != t2)
2359 self.assertFalse(t1 < t2)
2360 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002361
2362 for i in range(len(args)):
2363 newargs = args[:]
2364 newargs[i] = args[i] + 1
2365 t2 = self.theclass(*newargs) # this is larger than t1
2366 self.assertTrue(t1 < t2)
2367 self.assertTrue(t2 > t1)
2368 self.assertTrue(t1 <= t2)
2369 self.assertTrue(t2 >= t1)
2370 self.assertTrue(t1 != t2)
2371 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002372 self.assertFalse(t1 == t2)
2373 self.assertFalse(t2 == t1)
2374 self.assertFalse(t1 > t2)
2375 self.assertFalse(t2 < t1)
2376 self.assertFalse(t1 >= t2)
2377 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002378
2379 for badarg in OTHERSTUFF:
2380 self.assertEqual(t1 == badarg, False)
2381 self.assertEqual(t1 != badarg, True)
2382 self.assertEqual(badarg == t1, False)
2383 self.assertEqual(badarg != t1, True)
2384
2385 self.assertRaises(TypeError, lambda: t1 <= badarg)
2386 self.assertRaises(TypeError, lambda: t1 < badarg)
2387 self.assertRaises(TypeError, lambda: t1 > badarg)
2388 self.assertRaises(TypeError, lambda: t1 >= badarg)
2389 self.assertRaises(TypeError, lambda: badarg <= t1)
2390 self.assertRaises(TypeError, lambda: badarg < t1)
2391 self.assertRaises(TypeError, lambda: badarg > t1)
2392 self.assertRaises(TypeError, lambda: badarg >= t1)
2393
2394 def test_bad_constructor_arguments(self):
2395 # bad hours
2396 self.theclass(0, 0) # no exception
2397 self.theclass(23, 0) # no exception
2398 self.assertRaises(ValueError, self.theclass, -1, 0)
2399 self.assertRaises(ValueError, self.theclass, 24, 0)
2400 # bad minutes
2401 self.theclass(23, 0) # no exception
2402 self.theclass(23, 59) # no exception
2403 self.assertRaises(ValueError, self.theclass, 23, -1)
2404 self.assertRaises(ValueError, self.theclass, 23, 60)
2405 # bad seconds
2406 self.theclass(23, 59, 0) # no exception
2407 self.theclass(23, 59, 59) # no exception
2408 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
2409 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
2410 # bad microseconds
2411 self.theclass(23, 59, 59, 0) # no exception
2412 self.theclass(23, 59, 59, 999999) # no exception
2413 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
2414 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
2415
2416 def test_hash_equality(self):
2417 d = self.theclass(23, 30, 17)
2418 e = self.theclass(23, 30, 17)
2419 self.assertEqual(d, e)
2420 self.assertEqual(hash(d), hash(e))
2421
2422 dic = {d: 1}
2423 dic[e] = 2
2424 self.assertEqual(len(dic), 1)
2425 self.assertEqual(dic[d], 2)
2426 self.assertEqual(dic[e], 2)
2427
2428 d = self.theclass(0, 5, 17)
2429 e = self.theclass(0, 5, 17)
2430 self.assertEqual(d, e)
2431 self.assertEqual(hash(d), hash(e))
2432
2433 dic = {d: 1}
2434 dic[e] = 2
2435 self.assertEqual(len(dic), 1)
2436 self.assertEqual(dic[d], 2)
2437 self.assertEqual(dic[e], 2)
2438
2439 def test_isoformat(self):
2440 t = self.theclass(4, 5, 1, 123)
2441 self.assertEqual(t.isoformat(), "04:05:01.000123")
2442 self.assertEqual(t.isoformat(), str(t))
2443
2444 t = self.theclass()
2445 self.assertEqual(t.isoformat(), "00:00:00")
2446 self.assertEqual(t.isoformat(), str(t))
2447
2448 t = self.theclass(microsecond=1)
2449 self.assertEqual(t.isoformat(), "00:00:00.000001")
2450 self.assertEqual(t.isoformat(), str(t))
2451
2452 t = self.theclass(microsecond=10)
2453 self.assertEqual(t.isoformat(), "00:00:00.000010")
2454 self.assertEqual(t.isoformat(), str(t))
2455
2456 t = self.theclass(microsecond=100)
2457 self.assertEqual(t.isoformat(), "00:00:00.000100")
2458 self.assertEqual(t.isoformat(), str(t))
2459
2460 t = self.theclass(microsecond=1000)
2461 self.assertEqual(t.isoformat(), "00:00:00.001000")
2462 self.assertEqual(t.isoformat(), str(t))
2463
2464 t = self.theclass(microsecond=10000)
2465 self.assertEqual(t.isoformat(), "00:00:00.010000")
2466 self.assertEqual(t.isoformat(), str(t))
2467
2468 t = self.theclass(microsecond=100000)
2469 self.assertEqual(t.isoformat(), "00:00:00.100000")
2470 self.assertEqual(t.isoformat(), str(t))
2471
Alexander Belopolskya2998a62016-03-06 14:58:43 -05002472 t = self.theclass(hour=12, minute=34, second=56, microsecond=123456)
2473 self.assertEqual(t.isoformat(timespec='hours'), "12")
2474 self.assertEqual(t.isoformat(timespec='minutes'), "12:34")
2475 self.assertEqual(t.isoformat(timespec='seconds'), "12:34:56")
2476 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.123")
2477 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.123456")
2478 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56.123456")
2479 self.assertRaises(ValueError, t.isoformat, timespec='monkey')
2480
2481 t = self.theclass(hour=12, minute=34, second=56, microsecond=999500)
2482 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.999")
2483
2484 t = self.theclass(hour=12, minute=34, second=56, microsecond=0)
2485 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.000")
2486 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.000000")
2487 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56")
2488
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002489 def test_1653736(self):
2490 # verify it doesn't accept extra keyword arguments
2491 t = self.theclass(second=1)
2492 self.assertRaises(TypeError, t.isoformat, foo=3)
2493
2494 def test_strftime(self):
2495 t = self.theclass(1, 2, 3, 4)
2496 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
2497 # A naive object replaces %z and %Z with empty strings.
2498 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
2499
2500 def test_format(self):
2501 t = self.theclass(1, 2, 3, 4)
2502 self.assertEqual(t.__format__(''), str(t))
2503
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02002504 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002505 t.__format__(123)
2506
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002507 # check that a derived class's __str__() gets called
2508 class A(self.theclass):
2509 def __str__(self):
2510 return 'A'
2511 a = A(1, 2, 3, 4)
2512 self.assertEqual(a.__format__(''), 'A')
2513
2514 # check that a derived class's strftime gets called
2515 class B(self.theclass):
2516 def strftime(self, format_spec):
2517 return 'B'
2518 b = B(1, 2, 3, 4)
2519 self.assertEqual(b.__format__(''), str(t))
2520
2521 for fmt in ['%H %M %S',
2522 ]:
2523 self.assertEqual(t.__format__(fmt), t.strftime(fmt))
2524 self.assertEqual(a.__format__(fmt), t.strftime(fmt))
2525 self.assertEqual(b.__format__(fmt), 'B')
2526
2527 def test_str(self):
2528 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
2529 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
2530 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
2531 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
2532 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
2533
2534 def test_repr(self):
2535 name = 'datetime.' + self.theclass.__name__
2536 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
2537 "%s(1, 2, 3, 4)" % name)
2538 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
2539 "%s(10, 2, 3, 4000)" % name)
2540 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
2541 "%s(0, 2, 3, 400000)" % name)
2542 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
2543 "%s(12, 2, 3)" % name)
2544 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
2545 "%s(23, 15)" % name)
2546
2547 def test_resolution_info(self):
2548 self.assertIsInstance(self.theclass.min, self.theclass)
2549 self.assertIsInstance(self.theclass.max, self.theclass)
2550 self.assertIsInstance(self.theclass.resolution, timedelta)
2551 self.assertTrue(self.theclass.max > self.theclass.min)
2552
2553 def test_pickling(self):
2554 args = 20, 59, 16, 64**2
2555 orig = self.theclass(*args)
2556 for pickler, unpickler, proto in pickle_choices:
2557 green = pickler.dumps(orig, proto)
2558 derived = unpickler.loads(green)
2559 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02002560 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002561
2562 def test_pickling_subclass_time(self):
2563 args = 20, 59, 16, 64**2
2564 orig = SubclassTime(*args)
2565 for pickler, unpickler, proto in pickle_choices:
2566 green = pickler.dumps(orig, proto)
2567 derived = unpickler.loads(green)
2568 self.assertEqual(orig, derived)
2569
2570 def test_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002571 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002572 cls = self.theclass
2573 self.assertTrue(cls(1))
2574 self.assertTrue(cls(0, 1))
2575 self.assertTrue(cls(0, 0, 1))
2576 self.assertTrue(cls(0, 0, 0, 1))
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002577 self.assertTrue(cls(0))
2578 self.assertTrue(cls())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002579
2580 def test_replace(self):
2581 cls = self.theclass
2582 args = [1, 2, 3, 4]
2583 base = cls(*args)
2584 self.assertEqual(base, base.replace())
2585
2586 i = 0
2587 for name, newval in (("hour", 5),
2588 ("minute", 6),
2589 ("second", 7),
2590 ("microsecond", 8)):
2591 newargs = args[:]
2592 newargs[i] = newval
2593 expected = cls(*newargs)
2594 got = base.replace(**{name: newval})
2595 self.assertEqual(expected, got)
2596 i += 1
2597
2598 # Out of bounds.
2599 base = cls(1)
2600 self.assertRaises(ValueError, base.replace, hour=24)
2601 self.assertRaises(ValueError, base.replace, minute=-1)
2602 self.assertRaises(ValueError, base.replace, second=100)
2603 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2604
2605 def test_subclass_time(self):
2606
2607 class C(self.theclass):
2608 theAnswer = 42
2609
2610 def __new__(cls, *args, **kws):
2611 temp = kws.copy()
2612 extra = temp.pop('extra')
2613 result = self.theclass.__new__(cls, *args, **temp)
2614 result.extra = extra
2615 return result
2616
2617 def newmeth(self, start):
2618 return start + self.hour + self.second
2619
2620 args = 4, 5, 6
2621
2622 dt1 = self.theclass(*args)
2623 dt2 = C(*args, **{'extra': 7})
2624
2625 self.assertEqual(dt2.__class__, C)
2626 self.assertEqual(dt2.theAnswer, 42)
2627 self.assertEqual(dt2.extra, 7)
2628 self.assertEqual(dt1.isoformat(), dt2.isoformat())
2629 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2630
2631 def test_backdoor_resistance(self):
2632 # see TestDate.test_backdoor_resistance().
2633 base = '2:59.0'
2634 for hour_byte in ' ', '9', chr(24), '\xff':
2635 self.assertRaises(TypeError, self.theclass,
2636 hour_byte + base[1:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002637 # Good bytes, but bad tzinfo:
2638 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
2639 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002640
2641# A mixin for classes with a tzinfo= argument. Subclasses must define
Martin Panter46f50722016-05-26 05:35:26 +00002642# theclass as a class attribute, and theclass(1, 1, 1, tzinfo=whatever)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002643# must be legit (which is true for time and datetime).
2644class TZInfoBase:
2645
2646 def test_argument_passing(self):
2647 cls = self.theclass
2648 # A datetime passes itself on, a time passes None.
2649 class introspective(tzinfo):
2650 def tzname(self, dt): return dt and "real" or "none"
2651 def utcoffset(self, dt):
2652 return timedelta(minutes = dt and 42 or -42)
2653 dst = utcoffset
2654
2655 obj = cls(1, 2, 3, tzinfo=introspective())
2656
2657 expected = cls is time and "none" or "real"
2658 self.assertEqual(obj.tzname(), expected)
2659
2660 expected = timedelta(minutes=(cls is time and -42 or 42))
2661 self.assertEqual(obj.utcoffset(), expected)
2662 self.assertEqual(obj.dst(), expected)
2663
2664 def test_bad_tzinfo_classes(self):
2665 cls = self.theclass
2666 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
2667
2668 class NiceTry(object):
2669 def __init__(self): pass
2670 def utcoffset(self, dt): pass
2671 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
2672
2673 class BetterTry(tzinfo):
2674 def __init__(self): pass
2675 def utcoffset(self, dt): pass
2676 b = BetterTry()
2677 t = cls(1, 1, 1, tzinfo=b)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002678 self.assertIs(t.tzinfo, b)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002679
2680 def test_utc_offset_out_of_bounds(self):
2681 class Edgy(tzinfo):
2682 def __init__(self, offset):
2683 self.offset = timedelta(minutes=offset)
2684 def utcoffset(self, dt):
2685 return self.offset
2686
2687 cls = self.theclass
2688 for offset, legit in ((-1440, False),
2689 (-1439, True),
2690 (1439, True),
2691 (1440, False)):
2692 if cls is time:
2693 t = cls(1, 2, 3, tzinfo=Edgy(offset))
2694 elif cls is datetime:
2695 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
2696 else:
2697 assert 0, "impossible"
2698 if legit:
2699 aofs = abs(offset)
2700 h, m = divmod(aofs, 60)
2701 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
2702 if isinstance(t, datetime):
2703 t = t.timetz()
2704 self.assertEqual(str(t), "01:02:03" + tag)
2705 else:
2706 self.assertRaises(ValueError, str, t)
2707
2708 def test_tzinfo_classes(self):
2709 cls = self.theclass
2710 class C1(tzinfo):
2711 def utcoffset(self, dt): return None
2712 def dst(self, dt): return None
2713 def tzname(self, dt): return None
2714 for t in (cls(1, 1, 1),
2715 cls(1, 1, 1, tzinfo=None),
2716 cls(1, 1, 1, tzinfo=C1())):
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002717 self.assertIsNone(t.utcoffset())
2718 self.assertIsNone(t.dst())
2719 self.assertIsNone(t.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002720
2721 class C3(tzinfo):
2722 def utcoffset(self, dt): return timedelta(minutes=-1439)
2723 def dst(self, dt): return timedelta(minutes=1439)
2724 def tzname(self, dt): return "aname"
2725 t = cls(1, 1, 1, tzinfo=C3())
2726 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
2727 self.assertEqual(t.dst(), timedelta(minutes=1439))
2728 self.assertEqual(t.tzname(), "aname")
2729
2730 # Wrong types.
2731 class C4(tzinfo):
2732 def utcoffset(self, dt): return "aname"
2733 def dst(self, dt): return 7
2734 def tzname(self, dt): return 0
2735 t = cls(1, 1, 1, tzinfo=C4())
2736 self.assertRaises(TypeError, t.utcoffset)
2737 self.assertRaises(TypeError, t.dst)
2738 self.assertRaises(TypeError, t.tzname)
2739
2740 # Offset out of range.
2741 class C6(tzinfo):
2742 def utcoffset(self, dt): return timedelta(hours=-24)
2743 def dst(self, dt): return timedelta(hours=24)
2744 t = cls(1, 1, 1, tzinfo=C6())
2745 self.assertRaises(ValueError, t.utcoffset)
2746 self.assertRaises(ValueError, t.dst)
2747
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002748 # Not a whole number of seconds.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002749 class C7(tzinfo):
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002750 def utcoffset(self, dt): return timedelta(microseconds=61)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002751 def dst(self, dt): return timedelta(microseconds=-81)
2752 t = cls(1, 1, 1, tzinfo=C7())
Alexander Belopolsky018d3532017-07-31 10:26:50 -04002753 self.assertEqual(t.utcoffset(), timedelta(microseconds=61))
2754 self.assertEqual(t.dst(), timedelta(microseconds=-81))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002755
2756 def test_aware_compare(self):
2757 cls = self.theclass
2758
2759 # Ensure that utcoffset() gets ignored if the comparands have
2760 # the same tzinfo member.
2761 class OperandDependentOffset(tzinfo):
2762 def utcoffset(self, t):
2763 if t.minute < 10:
2764 # d0 and d1 equal after adjustment
2765 return timedelta(minutes=t.minute)
2766 else:
2767 # d2 off in the weeds
2768 return timedelta(minutes=59)
2769
2770 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
2771 d0 = base.replace(minute=3)
2772 d1 = base.replace(minute=9)
2773 d2 = base.replace(minute=11)
2774 for x in d0, d1, d2:
2775 for y in d0, d1, d2:
2776 for op in lt, le, gt, ge, eq, ne:
2777 got = op(x, y)
2778 expected = op(x.minute, y.minute)
2779 self.assertEqual(got, expected)
2780
2781 # However, if they're different members, uctoffset is not ignored.
2782 # Note that a time can't actually have an operand-depedent offset,
2783 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
2784 # so skip this test for time.
2785 if cls is not time:
2786 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2787 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2788 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2789 for x in d0, d1, d2:
2790 for y in d0, d1, d2:
2791 got = (x > y) - (x < y)
2792 if (x is d0 or x is d1) and (y is d0 or y is d1):
2793 expected = 0
2794 elif x is y is d2:
2795 expected = 0
2796 elif x is d2:
2797 expected = -1
2798 else:
2799 assert y is d2
2800 expected = 1
2801 self.assertEqual(got, expected)
2802
2803
2804# Testing time objects with a non-None tzinfo.
2805class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
2806 theclass = time
2807
2808 def test_empty(self):
2809 t = self.theclass()
2810 self.assertEqual(t.hour, 0)
2811 self.assertEqual(t.minute, 0)
2812 self.assertEqual(t.second, 0)
2813 self.assertEqual(t.microsecond, 0)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002814 self.assertIsNone(t.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002815
2816 def test_zones(self):
2817 est = FixedOffset(-300, "EST", 1)
2818 utc = FixedOffset(0, "UTC", -2)
2819 met = FixedOffset(60, "MET", 3)
2820 t1 = time( 7, 47, tzinfo=est)
2821 t2 = time(12, 47, tzinfo=utc)
2822 t3 = time(13, 47, tzinfo=met)
2823 t4 = time(microsecond=40)
2824 t5 = time(microsecond=40, tzinfo=utc)
2825
2826 self.assertEqual(t1.tzinfo, est)
2827 self.assertEqual(t2.tzinfo, utc)
2828 self.assertEqual(t3.tzinfo, met)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002829 self.assertIsNone(t4.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002830 self.assertEqual(t5.tzinfo, utc)
2831
2832 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2833 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2834 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002835 self.assertIsNone(t4.utcoffset())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002836 self.assertRaises(TypeError, t1.utcoffset, "no args")
2837
2838 self.assertEqual(t1.tzname(), "EST")
2839 self.assertEqual(t2.tzname(), "UTC")
2840 self.assertEqual(t3.tzname(), "MET")
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002841 self.assertIsNone(t4.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002842 self.assertRaises(TypeError, t1.tzname, "no args")
2843
2844 self.assertEqual(t1.dst(), timedelta(minutes=1))
2845 self.assertEqual(t2.dst(), timedelta(minutes=-2))
2846 self.assertEqual(t3.dst(), timedelta(minutes=3))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002847 self.assertIsNone(t4.dst())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002848 self.assertRaises(TypeError, t1.dst, "no args")
2849
2850 self.assertEqual(hash(t1), hash(t2))
2851 self.assertEqual(hash(t1), hash(t3))
2852 self.assertEqual(hash(t2), hash(t3))
2853
2854 self.assertEqual(t1, t2)
2855 self.assertEqual(t1, t3)
2856 self.assertEqual(t2, t3)
Alexander Belopolsky08313822012-06-15 20:19:47 -04002857 self.assertNotEqual(t4, t5) # mixed tz-aware & naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002858 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
2859 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
2860
2861 self.assertEqual(str(t1), "07:47:00-05:00")
2862 self.assertEqual(str(t2), "12:47:00+00:00")
2863 self.assertEqual(str(t3), "13:47:00+01:00")
2864 self.assertEqual(str(t4), "00:00:00.000040")
2865 self.assertEqual(str(t5), "00:00:00.000040+00:00")
2866
2867 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
2868 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
2869 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
2870 self.assertEqual(t4.isoformat(), "00:00:00.000040")
2871 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
2872
2873 d = 'datetime.time'
2874 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
2875 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
2876 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
2877 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
2878 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
2879
2880 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
2881 "07:47:00 %Z=EST %z=-0500")
2882 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
2883 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
2884
2885 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
2886 t1 = time(23, 59, tzinfo=yuck)
2887 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
2888 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
2889
2890 # Check that an invalid tzname result raises an exception.
2891 class Badtzname(tzinfo):
Alexander Belopolskye239d232010-12-08 23:31:48 +00002892 tz = 42
2893 def tzname(self, dt): return self.tz
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002894 t = time(2, 3, 4, tzinfo=Badtzname())
2895 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
2896 self.assertRaises(TypeError, t.strftime, "%Z")
2897
Alexander Belopolskye239d232010-12-08 23:31:48 +00002898 # Issue #6697:
Utkarsh Upadhyay287c5592017-07-21 02:14:54 +02002899 if '_Fast' in self.__class__.__name__:
Alexander Belopolskye239d232010-12-08 23:31:48 +00002900 Badtzname.tz = '\ud800'
2901 self.assertRaises(ValueError, t.strftime, "%Z")
2902
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002903 def test_hash_edge_cases(self):
2904 # Offsets that overflow a basic time.
2905 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2906 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2907 self.assertEqual(hash(t1), hash(t2))
2908
2909 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2910 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2911 self.assertEqual(hash(t1), hash(t2))
2912
2913 def test_pickling(self):
2914 # Try one without a tzinfo.
2915 args = 20, 59, 16, 64**2
2916 orig = self.theclass(*args)
2917 for pickler, unpickler, proto in pickle_choices:
2918 green = pickler.dumps(orig, proto)
2919 derived = unpickler.loads(green)
2920 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02002921 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002922
2923 # Try one with a tzinfo.
2924 tinfo = PicklableFixedOffset(-300, 'cookie')
2925 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
2926 for pickler, unpickler, proto in pickle_choices:
2927 green = pickler.dumps(orig, proto)
2928 derived = unpickler.loads(green)
2929 self.assertEqual(orig, derived)
2930 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
2931 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2932 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02002933 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002934
2935 def test_more_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002936 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002937 cls = self.theclass
2938
2939 t = cls(0, tzinfo=FixedOffset(-300, ""))
2940 self.assertTrue(t)
2941
2942 t = cls(5, tzinfo=FixedOffset(-300, ""))
2943 self.assertTrue(t)
2944
2945 t = cls(5, tzinfo=FixedOffset(300, ""))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002946 self.assertTrue(t)
2947
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002948 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2949 self.assertTrue(t)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002950
2951 def test_replace(self):
2952 cls = self.theclass
2953 z100 = FixedOffset(100, "+100")
2954 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2955 args = [1, 2, 3, 4, z100]
2956 base = cls(*args)
2957 self.assertEqual(base, base.replace())
2958
2959 i = 0
2960 for name, newval in (("hour", 5),
2961 ("minute", 6),
2962 ("second", 7),
2963 ("microsecond", 8),
2964 ("tzinfo", zm200)):
2965 newargs = args[:]
2966 newargs[i] = newval
2967 expected = cls(*newargs)
2968 got = base.replace(**{name: newval})
2969 self.assertEqual(expected, got)
2970 i += 1
2971
2972 # Ensure we can get rid of a tzinfo.
2973 self.assertEqual(base.tzname(), "+100")
2974 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002975 self.assertIsNone(base2.tzinfo)
2976 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002977
2978 # Ensure we can add one.
2979 base3 = base2.replace(tzinfo=z100)
2980 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002981 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002982
2983 # Out of bounds.
2984 base = cls(1)
2985 self.assertRaises(ValueError, base.replace, hour=24)
2986 self.assertRaises(ValueError, base.replace, minute=-1)
2987 self.assertRaises(ValueError, base.replace, second=100)
2988 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2989
2990 def test_mixed_compare(self):
2991 t1 = time(1, 2, 3)
2992 t2 = time(1, 2, 3)
2993 self.assertEqual(t1, t2)
2994 t2 = t2.replace(tzinfo=None)
2995 self.assertEqual(t1, t2)
2996 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2997 self.assertEqual(t1, t2)
2998 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04002999 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003000
3001 # In time w/ identical tzinfo objects, utcoffset is ignored.
3002 class Varies(tzinfo):
3003 def __init__(self):
3004 self.offset = timedelta(minutes=22)
3005 def utcoffset(self, t):
3006 self.offset += timedelta(minutes=1)
3007 return self.offset
3008
3009 v = Varies()
3010 t1 = t2.replace(tzinfo=v)
3011 t2 = t2.replace(tzinfo=v)
3012 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3013 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3014 self.assertEqual(t1, t2)
3015
3016 # But if they're not identical, it isn't ignored.
3017 t2 = t2.replace(tzinfo=Varies())
3018 self.assertTrue(t1 < t2) # t1's offset counter still going up
3019
3020 def test_subclass_timetz(self):
3021
3022 class C(self.theclass):
3023 theAnswer = 42
3024
3025 def __new__(cls, *args, **kws):
3026 temp = kws.copy()
3027 extra = temp.pop('extra')
3028 result = self.theclass.__new__(cls, *args, **temp)
3029 result.extra = extra
3030 return result
3031
3032 def newmeth(self, start):
3033 return start + self.hour + self.second
3034
3035 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3036
3037 dt1 = self.theclass(*args)
3038 dt2 = C(*args, **{'extra': 7})
3039
3040 self.assertEqual(dt2.__class__, C)
3041 self.assertEqual(dt2.theAnswer, 42)
3042 self.assertEqual(dt2.extra, 7)
3043 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3044 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
3045
3046
3047# Testing datetime objects with a non-None tzinfo.
3048
3049class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
3050 theclass = datetime
3051
3052 def test_trivial(self):
3053 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
3054 self.assertEqual(dt.year, 1)
3055 self.assertEqual(dt.month, 2)
3056 self.assertEqual(dt.day, 3)
3057 self.assertEqual(dt.hour, 4)
3058 self.assertEqual(dt.minute, 5)
3059 self.assertEqual(dt.second, 6)
3060 self.assertEqual(dt.microsecond, 7)
3061 self.assertEqual(dt.tzinfo, None)
3062
3063 def test_even_more_compare(self):
3064 # The test_compare() and test_more_compare() inherited from TestDate
3065 # and TestDateTime covered non-tzinfo cases.
3066
3067 # Smallest possible after UTC adjustment.
3068 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
3069 # Largest possible after UTC adjustment.
3070 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3071 tzinfo=FixedOffset(-1439, ""))
3072
3073 # Make sure those compare correctly, and w/o overflow.
3074 self.assertTrue(t1 < t2)
3075 self.assertTrue(t1 != t2)
3076 self.assertTrue(t2 > t1)
3077
3078 self.assertEqual(t1, t1)
3079 self.assertEqual(t2, t2)
3080
3081 # Equal afer adjustment.
3082 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
3083 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
3084 self.assertEqual(t1, t2)
3085
3086 # Change t1 not to subtract a minute, and t1 should be larger.
3087 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
3088 self.assertTrue(t1 > t2)
3089
3090 # Change t1 to subtract 2 minutes, and t1 should be smaller.
3091 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
3092 self.assertTrue(t1 < t2)
3093
3094 # Back to the original t1, but make seconds resolve it.
3095 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3096 second=1)
3097 self.assertTrue(t1 > t2)
3098
3099 # Likewise, but make microseconds resolve it.
3100 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3101 microsecond=1)
3102 self.assertTrue(t1 > t2)
3103
Alexander Belopolsky08313822012-06-15 20:19:47 -04003104 # Make t2 naive and it should differ.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003105 t2 = self.theclass.min
Alexander Belopolsky08313822012-06-15 20:19:47 -04003106 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003107 self.assertEqual(t2, t2)
3108
3109 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
3110 class Naive(tzinfo):
3111 def utcoffset(self, dt): return None
3112 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
Alexander Belopolsky08313822012-06-15 20:19:47 -04003113 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003114 self.assertEqual(t2, t2)
3115
3116 # OTOH, it's OK to compare two of these mixing the two ways of being
3117 # naive.
3118 t1 = self.theclass(5, 6, 7)
3119 self.assertEqual(t1, t2)
3120
3121 # Try a bogus uctoffset.
3122 class Bogus(tzinfo):
3123 def utcoffset(self, dt):
3124 return timedelta(minutes=1440) # out of bounds
3125 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
3126 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
3127 self.assertRaises(ValueError, lambda: t1 == t2)
3128
3129 def test_pickling(self):
3130 # Try one without a tzinfo.
3131 args = 6, 7, 23, 20, 59, 1, 64**2
3132 orig = self.theclass(*args)
3133 for pickler, unpickler, proto in pickle_choices:
3134 green = pickler.dumps(orig, proto)
3135 derived = unpickler.loads(green)
3136 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003137 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003138
3139 # Try one with a tzinfo.
3140 tinfo = PicklableFixedOffset(-300, 'cookie')
3141 orig = self.theclass(*args, **{'tzinfo': tinfo})
3142 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
3143 for pickler, unpickler, proto in pickle_choices:
3144 green = pickler.dumps(orig, proto)
3145 derived = unpickler.loads(green)
3146 self.assertEqual(orig, derived)
3147 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3148 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3149 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003150 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003151
3152 def test_extreme_hashes(self):
3153 # If an attempt is made to hash these via subtracting the offset
3154 # then hashing a datetime object, OverflowError results. The
3155 # Python implementation used to blow up here.
3156 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
3157 hash(t)
3158 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3159 tzinfo=FixedOffset(-1439, ""))
3160 hash(t)
3161
3162 # OTOH, an OOB offset should blow up.
3163 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
3164 self.assertRaises(ValueError, hash, t)
3165
3166 def test_zones(self):
3167 est = FixedOffset(-300, "EST")
3168 utc = FixedOffset(0, "UTC")
3169 met = FixedOffset(60, "MET")
3170 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
3171 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
3172 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
3173 self.assertEqual(t1.tzinfo, est)
3174 self.assertEqual(t2.tzinfo, utc)
3175 self.assertEqual(t3.tzinfo, met)
3176 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
3177 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
3178 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
3179 self.assertEqual(t1.tzname(), "EST")
3180 self.assertEqual(t2.tzname(), "UTC")
3181 self.assertEqual(t3.tzname(), "MET")
3182 self.assertEqual(hash(t1), hash(t2))
3183 self.assertEqual(hash(t1), hash(t3))
3184 self.assertEqual(hash(t2), hash(t3))
3185 self.assertEqual(t1, t2)
3186 self.assertEqual(t1, t3)
3187 self.assertEqual(t2, t3)
3188 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
3189 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
3190 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
3191 d = 'datetime.datetime(2002, 3, 19, '
3192 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
3193 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
3194 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
3195
3196 def test_combine(self):
3197 met = FixedOffset(60, "MET")
3198 d = date(2002, 3, 4)
3199 tz = time(18, 45, 3, 1234, tzinfo=met)
3200 dt = datetime.combine(d, tz)
3201 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
3202 tzinfo=met))
3203
3204 def test_extract(self):
3205 met = FixedOffset(60, "MET")
3206 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
3207 self.assertEqual(dt.date(), date(2002, 3, 4))
3208 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
3209 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
3210
3211 def test_tz_aware_arithmetic(self):
3212 import random
3213
3214 now = self.theclass.now()
3215 tz55 = FixedOffset(-330, "west 5:30")
3216 timeaware = now.time().replace(tzinfo=tz55)
3217 nowaware = self.theclass.combine(now.date(), timeaware)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003218 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003219 self.assertEqual(nowaware.timetz(), timeaware)
3220
3221 # Can't mix aware and non-aware.
3222 self.assertRaises(TypeError, lambda: now - nowaware)
3223 self.assertRaises(TypeError, lambda: nowaware - now)
3224
3225 # And adding datetime's doesn't make sense, aware or not.
3226 self.assertRaises(TypeError, lambda: now + nowaware)
3227 self.assertRaises(TypeError, lambda: nowaware + now)
3228 self.assertRaises(TypeError, lambda: nowaware + nowaware)
3229
3230 # Subtracting should yield 0.
3231 self.assertEqual(now - now, timedelta(0))
3232 self.assertEqual(nowaware - nowaware, timedelta(0))
3233
3234 # Adding a delta should preserve tzinfo.
3235 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
3236 nowawareplus = nowaware + delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003237 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003238 nowawareplus2 = delta + nowaware
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003239 self.assertIs(nowawareplus2.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003240 self.assertEqual(nowawareplus, nowawareplus2)
3241
3242 # that - delta should be what we started with, and that - what we
3243 # started with should be delta.
3244 diff = nowawareplus - delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003245 self.assertIs(diff.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003246 self.assertEqual(nowaware, diff)
3247 self.assertRaises(TypeError, lambda: delta - nowawareplus)
3248 self.assertEqual(nowawareplus - nowaware, delta)
3249
3250 # Make up a random timezone.
3251 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
3252 # Attach it to nowawareplus.
3253 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003254 self.assertIs(nowawareplus.tzinfo, tzr)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003255 # Make sure the difference takes the timezone adjustments into account.
3256 got = nowaware - nowawareplus
3257 # Expected: (nowaware base - nowaware offset) -
3258 # (nowawareplus base - nowawareplus offset) =
3259 # (nowaware base - nowawareplus base) +
3260 # (nowawareplus offset - nowaware offset) =
3261 # -delta + nowawareplus offset - nowaware offset
3262 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
3263 self.assertEqual(got, expected)
3264
3265 # Try max possible difference.
3266 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
3267 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3268 tzinfo=FixedOffset(-1439, "max"))
3269 maxdiff = max - min
3270 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
3271 timedelta(minutes=2*1439))
3272 # Different tzinfo, but the same offset
3273 tza = timezone(HOUR, 'A')
3274 tzb = timezone(HOUR, 'B')
3275 delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
3276 self.assertEqual(delta, self.theclass.min - self.theclass.max)
3277
3278 def test_tzinfo_now(self):
3279 meth = self.theclass.now
3280 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3281 base = meth()
3282 # Try with and without naming the keyword.
3283 off42 = FixedOffset(42, "42")
3284 another = meth(off42)
3285 again = meth(tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003286 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003287 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3288 # Bad argument with and w/o naming the keyword.
3289 self.assertRaises(TypeError, meth, 16)
3290 self.assertRaises(TypeError, meth, tzinfo=16)
3291 # Bad keyword name.
3292 self.assertRaises(TypeError, meth, tinfo=off42)
3293 # Too many args.
3294 self.assertRaises(TypeError, meth, off42, off42)
3295
3296 # We don't know which time zone we're in, and don't have a tzinfo
3297 # class to represent it, so seeing whether a tz argument actually
3298 # does a conversion is tricky.
3299 utc = FixedOffset(0, "utc", 0)
3300 for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
3301 timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
3302 for dummy in range(3):
3303 now = datetime.now(weirdtz)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003304 self.assertIs(now.tzinfo, weirdtz)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003305 utcnow = datetime.utcnow().replace(tzinfo=utc)
3306 now2 = utcnow.astimezone(weirdtz)
3307 if abs(now - now2) < timedelta(seconds=30):
3308 break
3309 # Else the code is broken, or more than 30 seconds passed between
3310 # calls; assuming the latter, just try again.
3311 else:
3312 # Three strikes and we're out.
3313 self.fail("utcnow(), now(tz), or astimezone() may be broken")
3314
3315 def test_tzinfo_fromtimestamp(self):
3316 import time
3317 meth = self.theclass.fromtimestamp
3318 ts = time.time()
3319 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3320 base = meth(ts)
3321 # Try with and without naming the keyword.
3322 off42 = FixedOffset(42, "42")
3323 another = meth(ts, off42)
3324 again = meth(ts, tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003325 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003326 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3327 # Bad argument with and w/o naming the keyword.
3328 self.assertRaises(TypeError, meth, ts, 16)
3329 self.assertRaises(TypeError, meth, ts, tzinfo=16)
3330 # Bad keyword name.
3331 self.assertRaises(TypeError, meth, ts, tinfo=off42)
3332 # Too many args.
3333 self.assertRaises(TypeError, meth, ts, off42, off42)
3334 # Too few args.
3335 self.assertRaises(TypeError, meth)
3336
3337 # Try to make sure tz= actually does some conversion.
3338 timestamp = 1000000000
3339 utcdatetime = datetime.utcfromtimestamp(timestamp)
3340 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
3341 # But on some flavor of Mac, it's nowhere near that. So we can't have
3342 # any idea here what time that actually is, we can only test that
3343 # relative changes match.
3344 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
3345 tz = FixedOffset(utcoffset, "tz", 0)
3346 expected = utcdatetime + utcoffset
3347 got = datetime.fromtimestamp(timestamp, tz)
3348 self.assertEqual(expected, got.replace(tzinfo=None))
3349
3350 def test_tzinfo_utcnow(self):
3351 meth = self.theclass.utcnow
3352 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3353 base = meth()
3354 # Try with and without naming the keyword; for whatever reason,
3355 # utcnow() doesn't accept a tzinfo argument.
3356 off42 = FixedOffset(42, "42")
3357 self.assertRaises(TypeError, meth, off42)
3358 self.assertRaises(TypeError, meth, tzinfo=off42)
3359
3360 def test_tzinfo_utcfromtimestamp(self):
3361 import time
3362 meth = self.theclass.utcfromtimestamp
3363 ts = time.time()
3364 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3365 base = meth(ts)
3366 # Try with and without naming the keyword; for whatever reason,
3367 # utcfromtimestamp() doesn't accept a tzinfo argument.
3368 off42 = FixedOffset(42, "42")
3369 self.assertRaises(TypeError, meth, ts, off42)
3370 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
3371
3372 def test_tzinfo_timetuple(self):
3373 # TestDateTime tested most of this. datetime adds a twist to the
3374 # DST flag.
3375 class DST(tzinfo):
3376 def __init__(self, dstvalue):
3377 if isinstance(dstvalue, int):
3378 dstvalue = timedelta(minutes=dstvalue)
3379 self.dstvalue = dstvalue
3380 def dst(self, dt):
3381 return self.dstvalue
3382
3383 cls = self.theclass
3384 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
3385 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
3386 t = d.timetuple()
3387 self.assertEqual(1, t.tm_year)
3388 self.assertEqual(1, t.tm_mon)
3389 self.assertEqual(1, t.tm_mday)
3390 self.assertEqual(10, t.tm_hour)
3391 self.assertEqual(20, t.tm_min)
3392 self.assertEqual(30, t.tm_sec)
3393 self.assertEqual(0, t.tm_wday)
3394 self.assertEqual(1, t.tm_yday)
3395 self.assertEqual(flag, t.tm_isdst)
3396
3397 # dst() returns wrong type.
3398 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
3399
3400 # dst() at the edge.
3401 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
3402 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
3403
3404 # dst() out of range.
3405 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
3406 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
3407
3408 def test_utctimetuple(self):
3409 class DST(tzinfo):
3410 def __init__(self, dstvalue=0):
3411 if isinstance(dstvalue, int):
3412 dstvalue = timedelta(minutes=dstvalue)
3413 self.dstvalue = dstvalue
3414 def dst(self, dt):
3415 return self.dstvalue
3416
3417 cls = self.theclass
3418 # This can't work: DST didn't implement utcoffset.
3419 self.assertRaises(NotImplementedError,
3420 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
3421
3422 class UOFS(DST):
3423 def __init__(self, uofs, dofs=None):
3424 DST.__init__(self, dofs)
3425 self.uofs = timedelta(minutes=uofs)
3426 def utcoffset(self, dt):
3427 return self.uofs
3428
3429 for dstvalue in -33, 33, 0, None:
3430 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
3431 t = d.utctimetuple()
3432 self.assertEqual(d.year, t.tm_year)
3433 self.assertEqual(d.month, t.tm_mon)
3434 self.assertEqual(d.day, t.tm_mday)
3435 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
3436 self.assertEqual(13, t.tm_min)
3437 self.assertEqual(d.second, t.tm_sec)
3438 self.assertEqual(d.weekday(), t.tm_wday)
3439 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
3440 t.tm_yday)
3441 # Ensure tm_isdst is 0 regardless of what dst() says: DST
3442 # is never in effect for a UTC time.
3443 self.assertEqual(0, t.tm_isdst)
3444
3445 # For naive datetime, utctimetuple == timetuple except for isdst
3446 d = cls(1, 2, 3, 10, 20, 30, 40)
3447 t = d.utctimetuple()
3448 self.assertEqual(t[:-1], d.timetuple()[:-1])
3449 self.assertEqual(0, t.tm_isdst)
3450 # Same if utcoffset is None
3451 class NOFS(DST):
3452 def utcoffset(self, dt):
3453 return None
3454 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
3455 t = d.utctimetuple()
3456 self.assertEqual(t[:-1], d.timetuple()[:-1])
3457 self.assertEqual(0, t.tm_isdst)
3458 # Check that bad tzinfo is detected
3459 class BOFS(DST):
3460 def utcoffset(self, dt):
3461 return "EST"
3462 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
3463 self.assertRaises(TypeError, d.utctimetuple)
3464
3465 # Check that utctimetuple() is the same as
3466 # astimezone(utc).timetuple()
3467 d = cls(2010, 11, 13, 14, 15, 16, 171819)
3468 for tz in [timezone.min, timezone.utc, timezone.max]:
3469 dtz = d.replace(tzinfo=tz)
3470 self.assertEqual(dtz.utctimetuple()[:-1],
3471 dtz.astimezone(timezone.utc).timetuple()[:-1])
3472 # At the edges, UTC adjustment can produce years out-of-range
3473 # for a datetime object. Ensure that an OverflowError is
3474 # raised.
3475 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
3476 # That goes back 1 minute less than a full day.
3477 self.assertRaises(OverflowError, tiny.utctimetuple)
3478
3479 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
3480 # That goes forward 1 minute less than a full day.
3481 self.assertRaises(OverflowError, huge.utctimetuple)
3482 # More overflow cases
3483 tiny = cls.min.replace(tzinfo=timezone(MINUTE))
3484 self.assertRaises(OverflowError, tiny.utctimetuple)
3485 huge = cls.max.replace(tzinfo=timezone(-MINUTE))
3486 self.assertRaises(OverflowError, huge.utctimetuple)
3487
3488 def test_tzinfo_isoformat(self):
3489 zero = FixedOffset(0, "+00:00")
3490 plus = FixedOffset(220, "+03:40")
3491 minus = FixedOffset(-231, "-03:51")
3492 unknown = FixedOffset(None, "")
3493
3494 cls = self.theclass
3495 datestr = '0001-02-03'
3496 for ofs in None, zero, plus, minus, unknown:
3497 for us in 0, 987001:
3498 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
3499 timestr = '04:05:59' + (us and '.987001' or '')
3500 ofsstr = ofs is not None and d.tzname() or ''
3501 tailstr = timestr + ofsstr
3502 iso = d.isoformat()
3503 self.assertEqual(iso, datestr + 'T' + tailstr)
3504 self.assertEqual(iso, d.isoformat('T'))
3505 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
3506 self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
3507 self.assertEqual(str(d), datestr + ' ' + tailstr)
3508
3509 def test_replace(self):
3510 cls = self.theclass
3511 z100 = FixedOffset(100, "+100")
3512 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3513 args = [1, 2, 3, 4, 5, 6, 7, z100]
3514 base = cls(*args)
3515 self.assertEqual(base, base.replace())
3516
3517 i = 0
3518 for name, newval in (("year", 2),
3519 ("month", 3),
3520 ("day", 4),
3521 ("hour", 5),
3522 ("minute", 6),
3523 ("second", 7),
3524 ("microsecond", 8),
3525 ("tzinfo", zm200)):
3526 newargs = args[:]
3527 newargs[i] = newval
3528 expected = cls(*newargs)
3529 got = base.replace(**{name: newval})
3530 self.assertEqual(expected, got)
3531 i += 1
3532
3533 # Ensure we can get rid of a tzinfo.
3534 self.assertEqual(base.tzname(), "+100")
3535 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003536 self.assertIsNone(base2.tzinfo)
3537 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003538
3539 # Ensure we can add one.
3540 base3 = base2.replace(tzinfo=z100)
3541 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003542 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003543
3544 # Out of bounds.
3545 base = cls(2000, 2, 29)
3546 self.assertRaises(ValueError, base.replace, year=2001)
3547
3548 def test_more_astimezone(self):
3549 # The inherited test_astimezone covered some trivial and error cases.
3550 fnone = FixedOffset(None, "None")
3551 f44m = FixedOffset(44, "44")
3552 fm5h = FixedOffset(-timedelta(hours=5), "m300")
3553
3554 dt = self.theclass.now(tz=f44m)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003555 self.assertIs(dt.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003556 # Replacing with degenerate tzinfo raises an exception.
3557 self.assertRaises(ValueError, dt.astimezone, fnone)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003558 # Replacing with same tzinfo makes no change.
3559 x = dt.astimezone(dt.tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003560 self.assertIs(x.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003561 self.assertEqual(x.date(), dt.date())
3562 self.assertEqual(x.time(), dt.time())
3563
3564 # Replacing with different tzinfo does adjust.
3565 got = dt.astimezone(fm5h)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003566 self.assertIs(got.tzinfo, fm5h)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003567 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
3568 expected = dt - dt.utcoffset() # in effect, convert to UTC
3569 expected += fm5h.utcoffset(dt) # and from there to local time
3570 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
3571 self.assertEqual(got.date(), expected.date())
3572 self.assertEqual(got.time(), expected.time())
3573 self.assertEqual(got.timetz(), expected.timetz())
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003574 self.assertIs(got.tzinfo, expected.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003575 self.assertEqual(got, expected)
3576
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003577 @support.run_with_tz('UTC')
3578 def test_astimezone_default_utc(self):
3579 dt = self.theclass.now(timezone.utc)
3580 self.assertEqual(dt.astimezone(None), dt)
3581 self.assertEqual(dt.astimezone(), dt)
3582
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003583 # Note that offset in TZ variable has the opposite sign to that
3584 # produced by %z directive.
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003585 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
3586 def test_astimezone_default_eastern(self):
3587 dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
3588 local = dt.astimezone()
3589 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003590 self.assertEqual(local.strftime("%z %Z"), "-0500 EST")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003591 dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc)
3592 local = dt.astimezone()
3593 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003594 self.assertEqual(local.strftime("%z %Z"), "-0400 EDT")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003595
Alexander Belopolsky1dcf4f92016-03-25 15:42:59 -04003596 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
3597 def test_astimezone_default_near_fold(self):
3598 # Issue #26616.
3599 u = datetime(2015, 11, 1, 5, tzinfo=timezone.utc)
3600 t = u.astimezone()
3601 s = t.astimezone()
3602 self.assertEqual(t.tzinfo, s.tzinfo)
3603
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003604 def test_aware_subtract(self):
3605 cls = self.theclass
3606
3607 # Ensure that utcoffset() is ignored when the operands have the
3608 # same tzinfo member.
3609 class OperandDependentOffset(tzinfo):
3610 def utcoffset(self, t):
3611 if t.minute < 10:
3612 # d0 and d1 equal after adjustment
3613 return timedelta(minutes=t.minute)
3614 else:
3615 # d2 off in the weeds
3616 return timedelta(minutes=59)
3617
3618 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
3619 d0 = base.replace(minute=3)
3620 d1 = base.replace(minute=9)
3621 d2 = base.replace(minute=11)
3622 for x in d0, d1, d2:
3623 for y in d0, d1, d2:
3624 got = x - y
3625 expected = timedelta(minutes=x.minute - y.minute)
3626 self.assertEqual(got, expected)
3627
3628 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
3629 # ignored.
3630 base = cls(8, 9, 10, 11, 12, 13, 14)
3631 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
3632 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
3633 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
3634 for x in d0, d1, d2:
3635 for y in d0, d1, d2:
3636 got = x - y
3637 if (x is d0 or x is d1) and (y is d0 or y is d1):
3638 expected = timedelta(0)
3639 elif x is y is d2:
3640 expected = timedelta(0)
3641 elif x is d2:
3642 expected = timedelta(minutes=(11-59)-0)
3643 else:
3644 assert y is d2
3645 expected = timedelta(minutes=0-(11-59))
3646 self.assertEqual(got, expected)
3647
3648 def test_mixed_compare(self):
3649 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
3650 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
3651 self.assertEqual(t1, t2)
3652 t2 = t2.replace(tzinfo=None)
3653 self.assertEqual(t1, t2)
3654 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3655 self.assertEqual(t1, t2)
3656 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04003657 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003658
3659 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
3660 class Varies(tzinfo):
3661 def __init__(self):
3662 self.offset = timedelta(minutes=22)
3663 def utcoffset(self, t):
3664 self.offset += timedelta(minutes=1)
3665 return self.offset
3666
3667 v = Varies()
3668 t1 = t2.replace(tzinfo=v)
3669 t2 = t2.replace(tzinfo=v)
3670 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3671 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3672 self.assertEqual(t1, t2)
3673
3674 # But if they're not identical, it isn't ignored.
3675 t2 = t2.replace(tzinfo=Varies())
3676 self.assertTrue(t1 < t2) # t1's offset counter still going up
3677
3678 def test_subclass_datetimetz(self):
3679
3680 class C(self.theclass):
3681 theAnswer = 42
3682
3683 def __new__(cls, *args, **kws):
3684 temp = kws.copy()
3685 extra = temp.pop('extra')
3686 result = self.theclass.__new__(cls, *args, **temp)
3687 result.extra = extra
3688 return result
3689
3690 def newmeth(self, start):
3691 return start + self.hour + self.year
3692
3693 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3694
3695 dt1 = self.theclass(*args)
3696 dt2 = C(*args, **{'extra': 7})
3697
3698 self.assertEqual(dt2.__class__, C)
3699 self.assertEqual(dt2.theAnswer, 42)
3700 self.assertEqual(dt2.extra, 7)
3701 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3702 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
3703
3704# Pain to set up DST-aware tzinfo classes.
3705
3706def first_sunday_on_or_after(dt):
3707 days_to_go = 6 - dt.weekday()
3708 if days_to_go:
3709 dt += timedelta(days_to_go)
3710 return dt
3711
3712ZERO = timedelta(0)
3713MINUTE = timedelta(minutes=1)
3714HOUR = timedelta(hours=1)
3715DAY = timedelta(days=1)
3716# In the US, DST starts at 2am (standard time) on the first Sunday in April.
3717DSTSTART = datetime(1, 4, 1, 2)
3718# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
3719# which is the first Sunday on or after Oct 25. Because we view 1:MM as
3720# being standard time on that day, there is no spelling in local time of
3721# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
3722DSTEND = datetime(1, 10, 25, 1)
3723
3724class USTimeZone(tzinfo):
3725
3726 def __init__(self, hours, reprname, stdname, dstname):
3727 self.stdoffset = timedelta(hours=hours)
3728 self.reprname = reprname
3729 self.stdname = stdname
3730 self.dstname = dstname
3731
3732 def __repr__(self):
3733 return self.reprname
3734
3735 def tzname(self, dt):
3736 if self.dst(dt):
3737 return self.dstname
3738 else:
3739 return self.stdname
3740
3741 def utcoffset(self, dt):
3742 return self.stdoffset + self.dst(dt)
3743
3744 def dst(self, dt):
3745 if dt is None or dt.tzinfo is None:
3746 # An exception instead may be sensible here, in one or more of
3747 # the cases.
3748 return ZERO
3749 assert dt.tzinfo is self
3750
3751 # Find first Sunday in April.
3752 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
3753 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
3754
3755 # Find last Sunday in October.
3756 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
3757 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
3758
3759 # Can't compare naive to aware objects, so strip the timezone from
3760 # dt first.
3761 if start <= dt.replace(tzinfo=None) < end:
3762 return HOUR
3763 else:
3764 return ZERO
3765
3766Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
3767Central = USTimeZone(-6, "Central", "CST", "CDT")
3768Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
3769Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
3770utc_real = FixedOffset(0, "UTC", 0)
3771# For better test coverage, we want another flavor of UTC that's west of
3772# the Eastern and Pacific timezones.
3773utc_fake = FixedOffset(-12*60, "UTCfake", 0)
3774
3775class TestTimezoneConversions(unittest.TestCase):
3776 # The DST switch times for 2002, in std time.
3777 dston = datetime(2002, 4, 7, 2)
3778 dstoff = datetime(2002, 10, 27, 1)
3779
3780 theclass = datetime
3781
3782 # Check a time that's inside DST.
3783 def checkinside(self, dt, tz, utc, dston, dstoff):
3784 self.assertEqual(dt.dst(), HOUR)
3785
3786 # Conversion to our own timezone is always an identity.
3787 self.assertEqual(dt.astimezone(tz), dt)
3788
3789 asutc = dt.astimezone(utc)
3790 there_and_back = asutc.astimezone(tz)
3791
3792 # Conversion to UTC and back isn't always an identity here,
3793 # because there are redundant spellings (in local time) of
3794 # UTC time when DST begins: the clock jumps from 1:59:59
3795 # to 3:00:00, and a local time of 2:MM:SS doesn't really
3796 # make sense then. The classes above treat 2:MM:SS as
3797 # daylight time then (it's "after 2am"), really an alias
3798 # for 1:MM:SS standard time. The latter form is what
3799 # conversion back from UTC produces.
3800 if dt.date() == dston.date() and dt.hour == 2:
3801 # We're in the redundant hour, and coming back from
3802 # UTC gives the 1:MM:SS standard-time spelling.
3803 self.assertEqual(there_and_back + HOUR, dt)
3804 # Although during was considered to be in daylight
3805 # time, there_and_back is not.
3806 self.assertEqual(there_and_back.dst(), ZERO)
3807 # They're the same times in UTC.
3808 self.assertEqual(there_and_back.astimezone(utc),
3809 dt.astimezone(utc))
3810 else:
3811 # We're not in the redundant hour.
3812 self.assertEqual(dt, there_and_back)
3813
3814 # Because we have a redundant spelling when DST begins, there is
Ezio Melotti3b3499b2011-03-16 11:35:38 +02003815 # (unfortunately) an hour when DST ends that can't be spelled at all in
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003816 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
3817 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
3818 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
3819 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
3820 # expressed in local time. Nevertheless, we want conversion back
3821 # from UTC to mimic the local clock's "repeat an hour" behavior.
3822 nexthour_utc = asutc + HOUR
3823 nexthour_tz = nexthour_utc.astimezone(tz)
3824 if dt.date() == dstoff.date() and dt.hour == 0:
3825 # We're in the hour before the last DST hour. The last DST hour
3826 # is ineffable. We want the conversion back to repeat 1:MM.
3827 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3828 nexthour_utc += HOUR
3829 nexthour_tz = nexthour_utc.astimezone(tz)
3830 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3831 else:
3832 self.assertEqual(nexthour_tz - dt, HOUR)
3833
3834 # Check a time that's outside DST.
3835 def checkoutside(self, dt, tz, utc):
3836 self.assertEqual(dt.dst(), ZERO)
3837
3838 # Conversion to our own timezone is always an identity.
3839 self.assertEqual(dt.astimezone(tz), dt)
3840
3841 # Converting to UTC and back is an identity too.
3842 asutc = dt.astimezone(utc)
3843 there_and_back = asutc.astimezone(tz)
3844 self.assertEqual(dt, there_and_back)
3845
3846 def convert_between_tz_and_utc(self, tz, utc):
3847 dston = self.dston.replace(tzinfo=tz)
3848 # Because 1:MM on the day DST ends is taken as being standard time,
3849 # there is no spelling in tz for the last hour of daylight time.
3850 # For purposes of the test, the last hour of DST is 0:MM, which is
3851 # taken as being daylight time (and 1:MM is taken as being standard
3852 # time).
3853 dstoff = self.dstoff.replace(tzinfo=tz)
3854 for delta in (timedelta(weeks=13),
3855 DAY,
3856 HOUR,
3857 timedelta(minutes=1),
3858 timedelta(microseconds=1)):
3859
3860 self.checkinside(dston, tz, utc, dston, dstoff)
3861 for during in dston + delta, dstoff - delta:
3862 self.checkinside(during, tz, utc, dston, dstoff)
3863
3864 self.checkoutside(dstoff, tz, utc)
3865 for outside in dston - delta, dstoff + delta:
3866 self.checkoutside(outside, tz, utc)
3867
3868 def test_easy(self):
3869 # Despite the name of this test, the endcases are excruciating.
3870 self.convert_between_tz_and_utc(Eastern, utc_real)
3871 self.convert_between_tz_and_utc(Pacific, utc_real)
3872 self.convert_between_tz_and_utc(Eastern, utc_fake)
3873 self.convert_between_tz_and_utc(Pacific, utc_fake)
3874 # The next is really dancing near the edge. It works because
3875 # Pacific and Eastern are far enough apart that their "problem
3876 # hours" don't overlap.
3877 self.convert_between_tz_and_utc(Eastern, Pacific)
3878 self.convert_between_tz_and_utc(Pacific, Eastern)
3879 # OTOH, these fail! Don't enable them. The difficulty is that
3880 # the edge case tests assume that every hour is representable in
3881 # the "utc" class. This is always true for a fixed-offset tzinfo
3882 # class (lke utc_real and utc_fake), but not for Eastern or Central.
3883 # For these adjacent DST-aware time zones, the range of time offsets
3884 # tested ends up creating hours in the one that aren't representable
3885 # in the other. For the same reason, we would see failures in the
3886 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
3887 # offset deltas in convert_between_tz_and_utc().
3888 #
3889 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
3890 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
3891
3892 def test_tricky(self):
3893 # 22:00 on day before daylight starts.
3894 fourback = self.dston - timedelta(hours=4)
3895 ninewest = FixedOffset(-9*60, "-0900", 0)
3896 fourback = fourback.replace(tzinfo=ninewest)
3897 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
3898 # 2", we should get the 3 spelling.
3899 # If we plug 22:00 the day before into Eastern, it "looks like std
3900 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
3901 # to 22:00 lands on 2:00, which makes no sense in local time (the
3902 # local clock jumps from 1 to 3). The point here is to make sure we
3903 # get the 3 spelling.
3904 expected = self.dston.replace(hour=3)
3905 got = fourback.astimezone(Eastern).replace(tzinfo=None)
3906 self.assertEqual(expected, got)
3907
3908 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
3909 # case we want the 1:00 spelling.
3910 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
3911 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
3912 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
3913 # spelling.
3914 expected = self.dston.replace(hour=1)
3915 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
3916 self.assertEqual(expected, got)
3917
3918 # Now on the day DST ends, we want "repeat an hour" behavior.
3919 # UTC 4:MM 5:MM 6:MM 7:MM checking these
3920 # EST 23:MM 0:MM 1:MM 2:MM
3921 # EDT 0:MM 1:MM 2:MM 3:MM
3922 # wall 0:MM 1:MM 1:MM 2:MM against these
3923 for utc in utc_real, utc_fake:
3924 for tz in Eastern, Pacific:
3925 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
3926 # Convert that to UTC.
3927 first_std_hour -= tz.utcoffset(None)
3928 # Adjust for possibly fake UTC.
3929 asutc = first_std_hour + utc.utcoffset(None)
3930 # First UTC hour to convert; this is 4:00 when utc=utc_real &
3931 # tz=Eastern.
3932 asutcbase = asutc.replace(tzinfo=utc)
3933 for tzhour in (0, 1, 1, 2):
3934 expectedbase = self.dstoff.replace(hour=tzhour)
3935 for minute in 0, 30, 59:
3936 expected = expectedbase.replace(minute=minute)
3937 asutc = asutcbase.replace(minute=minute)
3938 astz = asutc.astimezone(tz)
3939 self.assertEqual(astz.replace(tzinfo=None), expected)
3940 asutcbase += HOUR
3941
3942
3943 def test_bogus_dst(self):
3944 class ok(tzinfo):
3945 def utcoffset(self, dt): return HOUR
3946 def dst(self, dt): return HOUR
3947
3948 now = self.theclass.now().replace(tzinfo=utc_real)
3949 # Doesn't blow up.
3950 now.astimezone(ok())
3951
3952 # Does blow up.
3953 class notok(ok):
3954 def dst(self, dt): return None
3955 self.assertRaises(ValueError, now.astimezone, notok())
3956
3957 # Sometimes blow up. In the following, tzinfo.dst()
3958 # implementation may return None or not None depending on
3959 # whether DST is assumed to be in effect. In this situation,
3960 # a ValueError should be raised by astimezone().
3961 class tricky_notok(ok):
3962 def dst(self, dt):
3963 if dt.year == 2000:
3964 return None
3965 else:
3966 return 10*HOUR
3967 dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
3968 self.assertRaises(ValueError, dt.astimezone, tricky_notok())
3969
3970 def test_fromutc(self):
3971 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
3972 now = datetime.utcnow().replace(tzinfo=utc_real)
3973 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3974 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
3975 enow = Eastern.fromutc(now) # doesn't blow up
3976 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
3977 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
3978 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
3979
3980 # Always converts UTC to standard time.
3981 class FauxUSTimeZone(USTimeZone):
3982 def fromutc(self, dt):
3983 return dt + self.stdoffset
3984 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
3985
3986 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
3987 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
3988 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
3989
3990 # Check around DST start.
3991 start = self.dston.replace(hour=4, tzinfo=Eastern)
3992 fstart = start.replace(tzinfo=FEastern)
3993 for wall in 23, 0, 1, 3, 4, 5:
3994 expected = start.replace(hour=wall)
3995 if wall == 23:
3996 expected -= timedelta(days=1)
3997 got = Eastern.fromutc(start)
3998 self.assertEqual(expected, got)
3999
4000 expected = fstart + FEastern.stdoffset
4001 got = FEastern.fromutc(fstart)
4002 self.assertEqual(expected, got)
4003
4004 # Ensure astimezone() calls fromutc() too.
4005 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
4006 self.assertEqual(expected, got)
4007
4008 start += HOUR
4009 fstart += HOUR
4010
4011 # Check around DST end.
4012 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
4013 fstart = start.replace(tzinfo=FEastern)
4014 for wall in 0, 1, 1, 2, 3, 4:
4015 expected = start.replace(hour=wall)
4016 got = Eastern.fromutc(start)
4017 self.assertEqual(expected, got)
4018
4019 expected = fstart + FEastern.stdoffset
4020 got = FEastern.fromutc(fstart)
4021 self.assertEqual(expected, got)
4022
4023 # Ensure astimezone() calls fromutc() too.
4024 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
4025 self.assertEqual(expected, got)
4026
4027 start += HOUR
4028 fstart += HOUR
4029
4030
4031#############################################################################
4032# oddballs
4033
4034class Oddballs(unittest.TestCase):
4035
4036 def test_bug_1028306(self):
4037 # Trying to compare a date to a datetime should act like a mixed-
4038 # type comparison, despite that datetime is a subclass of date.
4039 as_date = date.today()
4040 as_datetime = datetime.combine(as_date, time())
4041 self.assertTrue(as_date != as_datetime)
4042 self.assertTrue(as_datetime != as_date)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004043 self.assertFalse(as_date == as_datetime)
4044 self.assertFalse(as_datetime == as_date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004045 self.assertRaises(TypeError, lambda: as_date < as_datetime)
4046 self.assertRaises(TypeError, lambda: as_datetime < as_date)
4047 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
4048 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
4049 self.assertRaises(TypeError, lambda: as_date > as_datetime)
4050 self.assertRaises(TypeError, lambda: as_datetime > as_date)
4051 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
4052 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
4053
Raymond Hettinger15f44ab2016-08-30 10:47:49 -07004054 # Nevertheless, comparison should work with the base-class (date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004055 # projection if use of a date method is forced.
4056 self.assertEqual(as_date.__eq__(as_datetime), True)
4057 different_day = (as_date.day + 1) % 20 + 1
4058 as_different = as_datetime.replace(day= different_day)
4059 self.assertEqual(as_date.__eq__(as_different), False)
4060
4061 # And date should compare with other subclasses of date. If a
4062 # subclass wants to stop this, it's up to the subclass to do so.
4063 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
4064 self.assertEqual(as_date, date_sc)
4065 self.assertEqual(date_sc, as_date)
4066
4067 # Ditto for datetimes.
4068 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
4069 as_date.day, 0, 0, 0)
4070 self.assertEqual(as_datetime, datetime_sc)
4071 self.assertEqual(datetime_sc, as_datetime)
4072
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004073 def test_extra_attributes(self):
4074 for x in [date.today(),
4075 time(),
4076 datetime.utcnow(),
4077 timedelta(),
4078 tzinfo(),
4079 timezone(timedelta())]:
4080 with self.assertRaises(AttributeError):
4081 x.abc = 1
4082
4083 def test_check_arg_types(self):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004084 class Number:
4085 def __init__(self, value):
4086 self.value = value
4087 def __int__(self):
4088 return self.value
4089
4090 for xx in [decimal.Decimal(10),
4091 decimal.Decimal('10.9'),
4092 Number(10)]:
4093 self.assertEqual(datetime(10, 10, 10, 10, 10, 10, 10),
4094 datetime(xx, xx, xx, xx, xx, xx, xx))
4095
4096 with self.assertRaisesRegex(TypeError, '^an integer is required '
R David Murray44b548d2016-09-08 13:59:53 -04004097 r'\(got type str\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004098 datetime(10, 10, '10')
4099
4100 f10 = Number(10.9)
4101 with self.assertRaisesRegex(TypeError, '^__int__ returned non-int '
R David Murray44b548d2016-09-08 13:59:53 -04004102 r'\(type float\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004103 datetime(10, 10, f10)
4104
4105 class Float(float):
4106 pass
4107 s10 = Float(10.9)
4108 with self.assertRaisesRegex(TypeError, '^integer argument expected, '
4109 'got float$'):
4110 datetime(10, 10, s10)
4111
4112 with self.assertRaises(TypeError):
4113 datetime(10., 10, 10)
4114 with self.assertRaises(TypeError):
4115 datetime(10, 10., 10)
4116 with self.assertRaises(TypeError):
4117 datetime(10, 10, 10.)
4118 with self.assertRaises(TypeError):
4119 datetime(10, 10, 10, 10.)
4120 with self.assertRaises(TypeError):
4121 datetime(10, 10, 10, 10, 10.)
4122 with self.assertRaises(TypeError):
4123 datetime(10, 10, 10, 10, 10, 10.)
4124 with self.assertRaises(TypeError):
4125 datetime(10, 10, 10, 10, 10, 10, 10.)
4126
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004127#############################################################################
4128# Local Time Disambiguation
4129
4130# An experimental reimplementation of fromutc that respects the "fold" flag.
4131
4132class tzinfo2(tzinfo):
4133
4134 def fromutc(self, dt):
4135 "datetime in UTC -> datetime in local time."
4136
4137 if not isinstance(dt, datetime):
4138 raise TypeError("fromutc() requires a datetime argument")
4139 if dt.tzinfo is not self:
4140 raise ValueError("dt.tzinfo is not self")
4141 # Returned value satisfies
4142 # dt + ldt.utcoffset() = ldt
4143 off0 = dt.replace(fold=0).utcoffset()
4144 off1 = dt.replace(fold=1).utcoffset()
4145 if off0 is None or off1 is None or dt.dst() is None:
4146 raise ValueError
4147 if off0 == off1:
4148 ldt = dt + off0
4149 off1 = ldt.utcoffset()
4150 if off0 == off1:
4151 return ldt
4152 # Now, we discovered both possible offsets, so
4153 # we can just try four possible solutions:
4154 for off in [off0, off1]:
4155 ldt = dt + off
4156 if ldt.utcoffset() == off:
4157 return ldt
4158 ldt = ldt.replace(fold=1)
4159 if ldt.utcoffset() == off:
4160 return ldt
4161
4162 raise ValueError("No suitable local time found")
4163
4164# Reimplementing simplified US timezones to respect the "fold" flag:
4165
4166class USTimeZone2(tzinfo2):
4167
4168 def __init__(self, hours, reprname, stdname, dstname):
4169 self.stdoffset = timedelta(hours=hours)
4170 self.reprname = reprname
4171 self.stdname = stdname
4172 self.dstname = dstname
4173
4174 def __repr__(self):
4175 return self.reprname
4176
4177 def tzname(self, dt):
4178 if self.dst(dt):
4179 return self.dstname
4180 else:
4181 return self.stdname
4182
4183 def utcoffset(self, dt):
4184 return self.stdoffset + self.dst(dt)
4185
4186 def dst(self, dt):
4187 if dt is None or dt.tzinfo is None:
4188 # An exception instead may be sensible here, in one or more of
4189 # the cases.
4190 return ZERO
4191 assert dt.tzinfo is self
4192
4193 # Find first Sunday in April.
4194 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
4195 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
4196
4197 # Find last Sunday in October.
4198 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
4199 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
4200
4201 # Can't compare naive to aware objects, so strip the timezone from
4202 # dt first.
4203 dt = dt.replace(tzinfo=None)
4204 if start + HOUR <= dt < end:
4205 # DST is in effect.
4206 return HOUR
4207 elif end <= dt < end + HOUR:
4208 # Fold (an ambiguous hour): use dt.fold to disambiguate.
4209 return ZERO if dt.fold else HOUR
4210 elif start <= dt < start + HOUR:
4211 # Gap (a non-existent hour): reverse the fold rule.
4212 return HOUR if dt.fold else ZERO
4213 else:
4214 # DST is off.
4215 return ZERO
4216
4217Eastern2 = USTimeZone2(-5, "Eastern2", "EST", "EDT")
4218Central2 = USTimeZone2(-6, "Central2", "CST", "CDT")
4219Mountain2 = USTimeZone2(-7, "Mountain2", "MST", "MDT")
4220Pacific2 = USTimeZone2(-8, "Pacific2", "PST", "PDT")
4221
4222# Europe_Vilnius_1941 tzinfo implementation reproduces the following
4223# 1941 transition from Olson's tzdist:
4224#
4225# Zone NAME GMTOFF RULES FORMAT [UNTIL]
4226# ZoneEurope/Vilnius 1:00 - CET 1940 Aug 3
4227# 3:00 - MSK 1941 Jun 24
4228# 1:00 C-Eur CE%sT 1944 Aug
4229#
4230# $ zdump -v Europe/Vilnius | grep 1941
4231# Europe/Vilnius Mon Jun 23 20:59:59 1941 UTC = Mon Jun 23 23:59:59 1941 MSK isdst=0 gmtoff=10800
4232# Europe/Vilnius Mon Jun 23 21:00:00 1941 UTC = Mon Jun 23 23:00:00 1941 CEST isdst=1 gmtoff=7200
4233
4234class Europe_Vilnius_1941(tzinfo):
4235 def _utc_fold(self):
4236 return [datetime(1941, 6, 23, 21, tzinfo=self), # Mon Jun 23 21:00:00 1941 UTC
4237 datetime(1941, 6, 23, 22, tzinfo=self)] # Mon Jun 23 22:00:00 1941 UTC
4238
4239 def _loc_fold(self):
4240 return [datetime(1941, 6, 23, 23, tzinfo=self), # Mon Jun 23 23:00:00 1941 MSK / CEST
4241 datetime(1941, 6, 24, 0, tzinfo=self)] # Mon Jun 24 00:00:00 1941 CEST
4242
4243 def utcoffset(self, dt):
4244 fold_start, fold_stop = self._loc_fold()
4245 if dt < fold_start:
4246 return 3 * HOUR
4247 if dt < fold_stop:
4248 return (2 if dt.fold else 3) * HOUR
4249 # if dt >= fold_stop
4250 return 2 * HOUR
4251
4252 def dst(self, dt):
4253 fold_start, fold_stop = self._loc_fold()
4254 if dt < fold_start:
4255 return 0 * HOUR
4256 if dt < fold_stop:
4257 return (1 if dt.fold else 0) * HOUR
4258 # if dt >= fold_stop
4259 return 1 * HOUR
4260
4261 def tzname(self, dt):
4262 fold_start, fold_stop = self._loc_fold()
4263 if dt < fold_start:
4264 return 'MSK'
4265 if dt < fold_stop:
4266 return ('MSK', 'CEST')[dt.fold]
4267 # if dt >= fold_stop
4268 return 'CEST'
4269
4270 def fromutc(self, dt):
4271 assert dt.fold == 0
4272 assert dt.tzinfo is self
4273 if dt.year != 1941:
4274 raise NotImplementedError
4275 fold_start, fold_stop = self._utc_fold()
4276 if dt < fold_start:
4277 return dt + 3 * HOUR
4278 if dt < fold_stop:
4279 return (dt + 2 * HOUR).replace(fold=1)
4280 # if dt >= fold_stop
4281 return dt + 2 * HOUR
4282
4283
4284class TestLocalTimeDisambiguation(unittest.TestCase):
4285
4286 def test_vilnius_1941_fromutc(self):
4287 Vilnius = Europe_Vilnius_1941()
4288
4289 gdt = datetime(1941, 6, 23, 20, 59, 59, tzinfo=timezone.utc)
4290 ldt = gdt.astimezone(Vilnius)
4291 self.assertEqual(ldt.strftime("%c %Z%z"),
4292 'Mon Jun 23 23:59:59 1941 MSK+0300')
4293 self.assertEqual(ldt.fold, 0)
4294 self.assertFalse(ldt.dst())
4295
4296 gdt = datetime(1941, 6, 23, 21, tzinfo=timezone.utc)
4297 ldt = gdt.astimezone(Vilnius)
4298 self.assertEqual(ldt.strftime("%c %Z%z"),
4299 'Mon Jun 23 23:00:00 1941 CEST+0200')
4300 self.assertEqual(ldt.fold, 1)
4301 self.assertTrue(ldt.dst())
4302
4303 gdt = datetime(1941, 6, 23, 22, tzinfo=timezone.utc)
4304 ldt = gdt.astimezone(Vilnius)
4305 self.assertEqual(ldt.strftime("%c %Z%z"),
4306 'Tue Jun 24 00:00:00 1941 CEST+0200')
4307 self.assertEqual(ldt.fold, 0)
4308 self.assertTrue(ldt.dst())
4309
4310 def test_vilnius_1941_toutc(self):
4311 Vilnius = Europe_Vilnius_1941()
4312
4313 ldt = datetime(1941, 6, 23, 22, 59, 59, tzinfo=Vilnius)
4314 gdt = ldt.astimezone(timezone.utc)
4315 self.assertEqual(gdt.strftime("%c %Z"),
4316 'Mon Jun 23 19:59:59 1941 UTC')
4317
4318 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius)
4319 gdt = ldt.astimezone(timezone.utc)
4320 self.assertEqual(gdt.strftime("%c %Z"),
4321 'Mon Jun 23 20:59:59 1941 UTC')
4322
4323 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius, fold=1)
4324 gdt = ldt.astimezone(timezone.utc)
4325 self.assertEqual(gdt.strftime("%c %Z"),
4326 'Mon Jun 23 21:59:59 1941 UTC')
4327
4328 ldt = datetime(1941, 6, 24, 0, tzinfo=Vilnius)
4329 gdt = ldt.astimezone(timezone.utc)
4330 self.assertEqual(gdt.strftime("%c %Z"),
4331 'Mon Jun 23 22:00:00 1941 UTC')
4332
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004333 def test_constructors(self):
4334 t = time(0, fold=1)
4335 dt = datetime(1, 1, 1, fold=1)
4336 self.assertEqual(t.fold, 1)
4337 self.assertEqual(dt.fold, 1)
4338 with self.assertRaises(TypeError):
4339 time(0, 0, 0, 0, None, 0)
4340
4341 def test_member(self):
4342 dt = datetime(1, 1, 1, fold=1)
4343 t = dt.time()
4344 self.assertEqual(t.fold, 1)
4345 t = dt.timetz()
4346 self.assertEqual(t.fold, 1)
4347
4348 def test_replace(self):
4349 t = time(0)
4350 dt = datetime(1, 1, 1)
4351 self.assertEqual(t.replace(fold=1).fold, 1)
4352 self.assertEqual(dt.replace(fold=1).fold, 1)
4353 self.assertEqual(t.replace(fold=0).fold, 0)
4354 self.assertEqual(dt.replace(fold=0).fold, 0)
4355 # Check that replacement of other fields does not change "fold".
4356 t = t.replace(fold=1, tzinfo=Eastern)
4357 dt = dt.replace(fold=1, tzinfo=Eastern)
4358 self.assertEqual(t.replace(tzinfo=None).fold, 1)
4359 self.assertEqual(dt.replace(tzinfo=None).fold, 1)
Serhiy Storchaka314d6fc2017-03-31 22:48:16 +03004360 # Out of bounds.
4361 with self.assertRaises(ValueError):
4362 t.replace(fold=2)
4363 with self.assertRaises(ValueError):
4364 dt.replace(fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004365 # Check that fold is a keyword-only argument
4366 with self.assertRaises(TypeError):
4367 t.replace(1, 1, 1, None, 1)
4368 with self.assertRaises(TypeError):
4369 dt.replace(1, 1, 1, 1, 1, 1, 1, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04004370
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004371 def test_comparison(self):
4372 t = time(0)
4373 dt = datetime(1, 1, 1)
4374 self.assertEqual(t, t.replace(fold=1))
4375 self.assertEqual(dt, dt.replace(fold=1))
4376
4377 def test_hash(self):
4378 t = time(0)
4379 dt = datetime(1, 1, 1)
4380 self.assertEqual(hash(t), hash(t.replace(fold=1)))
4381 self.assertEqual(hash(dt), hash(dt.replace(fold=1)))
4382
4383 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4384 def test_fromtimestamp(self):
4385 s = 1414906200
4386 dt0 = datetime.fromtimestamp(s)
4387 dt1 = datetime.fromtimestamp(s + 3600)
4388 self.assertEqual(dt0.fold, 0)
4389 self.assertEqual(dt1.fold, 1)
4390
4391 @support.run_with_tz('Australia/Lord_Howe')
4392 def test_fromtimestamp_lord_howe(self):
4393 tm = _time.localtime(1.4e9)
4394 if _time.strftime('%Z%z', tm) != 'LHST+1030':
4395 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
4396 # $ TZ=Australia/Lord_Howe date -r 1428158700
4397 # Sun Apr 5 01:45:00 LHDT 2015
4398 # $ TZ=Australia/Lord_Howe date -r 1428160500
4399 # Sun Apr 5 01:45:00 LHST 2015
4400 s = 1428158700
4401 t0 = datetime.fromtimestamp(s)
4402 t1 = datetime.fromtimestamp(s + 1800)
4403 self.assertEqual(t0, t1)
4404 self.assertEqual(t0.fold, 0)
4405 self.assertEqual(t1.fold, 1)
4406
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004407 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4408 def test_timestamp(self):
4409 dt0 = datetime(2014, 11, 2, 1, 30)
4410 dt1 = dt0.replace(fold=1)
4411 self.assertEqual(dt0.timestamp() + 3600,
4412 dt1.timestamp())
4413
4414 @support.run_with_tz('Australia/Lord_Howe')
4415 def test_timestamp_lord_howe(self):
4416 tm = _time.localtime(1.4e9)
4417 if _time.strftime('%Z%z', tm) != 'LHST+1030':
4418 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
4419 t = datetime(2015, 4, 5, 1, 45)
4420 s0 = t.replace(fold=0).timestamp()
4421 s1 = t.replace(fold=1).timestamp()
4422 self.assertEqual(s0 + 1800, s1)
4423
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004424 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4425 def test_astimezone(self):
4426 dt0 = datetime(2014, 11, 2, 1, 30)
4427 dt1 = dt0.replace(fold=1)
4428 # Convert both naive instances to aware.
4429 adt0 = dt0.astimezone()
4430 adt1 = dt1.astimezone()
4431 # Check that the first instance in DST zone and the second in STD
4432 self.assertEqual(adt0.tzname(), 'EDT')
4433 self.assertEqual(adt1.tzname(), 'EST')
4434 self.assertEqual(adt0 + HOUR, adt1)
4435 # Aware instances with fixed offset tzinfo's always have fold=0
4436 self.assertEqual(adt0.fold, 0)
4437 self.assertEqual(adt1.fold, 0)
4438
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004439 def test_pickle_fold(self):
4440 t = time(fold=1)
4441 dt = datetime(1, 1, 1, fold=1)
4442 for pickler, unpickler, proto in pickle_choices:
4443 for x in [t, dt]:
4444 s = pickler.dumps(x, proto)
4445 y = unpickler.loads(s)
4446 self.assertEqual(x, y)
4447 self.assertEqual((0 if proto < 4 else x.fold), y.fold)
4448
4449 def test_repr(self):
4450 t = time(fold=1)
4451 dt = datetime(1, 1, 1, fold=1)
4452 self.assertEqual(repr(t), 'datetime.time(0, 0, fold=1)')
4453 self.assertEqual(repr(dt),
4454 'datetime.datetime(1, 1, 1, 0, 0, fold=1)')
4455
4456 def test_dst(self):
4457 # Let's first establish that things work in regular times.
4458 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
4459 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
4460 self.assertEqual(dt_summer.dst(), HOUR)
4461 self.assertEqual(dt_winter.dst(), ZERO)
4462 # The disambiguation flag is ignored
4463 self.assertEqual(dt_summer.replace(fold=1).dst(), HOUR)
4464 self.assertEqual(dt_winter.replace(fold=1).dst(), ZERO)
4465
4466 # Pick local time in the fold.
4467 for minute in [0, 30, 59]:
4468 dt = datetime(2002, 10, 27, 1, minute, tzinfo=Eastern2)
4469 # With fold=0 (the default) it is in DST.
4470 self.assertEqual(dt.dst(), HOUR)
4471 # With fold=1 it is in STD.
4472 self.assertEqual(dt.replace(fold=1).dst(), ZERO)
4473
4474 # Pick local time in the gap.
4475 for minute in [0, 30, 59]:
4476 dt = datetime(2002, 4, 7, 2, minute, tzinfo=Eastern2)
4477 # With fold=0 (the default) it is in STD.
4478 self.assertEqual(dt.dst(), ZERO)
4479 # With fold=1 it is in DST.
4480 self.assertEqual(dt.replace(fold=1).dst(), HOUR)
4481
4482
4483 def test_utcoffset(self):
4484 # Let's first establish that things work in regular times.
4485 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
4486 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
4487 self.assertEqual(dt_summer.utcoffset(), -4 * HOUR)
4488 self.assertEqual(dt_winter.utcoffset(), -5 * HOUR)
4489 # The disambiguation flag is ignored
4490 self.assertEqual(dt_summer.replace(fold=1).utcoffset(), -4 * HOUR)
4491 self.assertEqual(dt_winter.replace(fold=1).utcoffset(), -5 * HOUR)
4492
4493 def test_fromutc(self):
4494 # Let's first establish that things work in regular times.
4495 u_summer = datetime(2002, 10, 27, 6, tzinfo=Eastern2) - timedelta.resolution
4496 u_winter = datetime(2002, 10, 27, 7, tzinfo=Eastern2)
4497 t_summer = Eastern2.fromutc(u_summer)
4498 t_winter = Eastern2.fromutc(u_winter)
4499 self.assertEqual(t_summer, u_summer - 4 * HOUR)
4500 self.assertEqual(t_winter, u_winter - 5 * HOUR)
4501 self.assertEqual(t_summer.fold, 0)
4502 self.assertEqual(t_winter.fold, 0)
4503
4504 # What happens in the fall-back fold?
4505 u = datetime(2002, 10, 27, 5, 30, tzinfo=Eastern2)
4506 t0 = Eastern2.fromutc(u)
4507 u += HOUR
4508 t1 = Eastern2.fromutc(u)
4509 self.assertEqual(t0, t1)
4510 self.assertEqual(t0.fold, 0)
4511 self.assertEqual(t1.fold, 1)
4512 # The tricky part is when u is in the local fold:
4513 u = datetime(2002, 10, 27, 1, 30, tzinfo=Eastern2)
4514 t = Eastern2.fromutc(u)
4515 self.assertEqual((t.day, t.hour), (26, 21))
4516 # .. or gets into the local fold after a standard time adjustment
4517 u = datetime(2002, 10, 27, 6, 30, tzinfo=Eastern2)
4518 t = Eastern2.fromutc(u)
4519 self.assertEqual((t.day, t.hour), (27, 1))
4520
4521 # What happens in the spring-forward gap?
4522 u = datetime(2002, 4, 7, 2, 0, tzinfo=Eastern2)
4523 t = Eastern2.fromutc(u)
4524 self.assertEqual((t.day, t.hour), (6, 21))
4525
4526 def test_mixed_compare_regular(self):
4527 t = datetime(2000, 1, 1, tzinfo=Eastern2)
4528 self.assertEqual(t, t.astimezone(timezone.utc))
4529 t = datetime(2000, 6, 1, tzinfo=Eastern2)
4530 self.assertEqual(t, t.astimezone(timezone.utc))
4531
4532 def test_mixed_compare_fold(self):
4533 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
4534 t_fold_utc = t_fold.astimezone(timezone.utc)
4535 self.assertNotEqual(t_fold, t_fold_utc)
4536
4537 def test_mixed_compare_gap(self):
4538 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
4539 t_gap_utc = t_gap.astimezone(timezone.utc)
4540 self.assertNotEqual(t_gap, t_gap_utc)
4541
4542 def test_hash_aware(self):
4543 t = datetime(2000, 1, 1, tzinfo=Eastern2)
4544 self.assertEqual(hash(t), hash(t.replace(fold=1)))
4545 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
4546 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
4547 self.assertEqual(hash(t_fold), hash(t_fold.replace(fold=1)))
4548 self.assertEqual(hash(t_gap), hash(t_gap.replace(fold=1)))
4549
4550SEC = timedelta(0, 1)
4551
4552def pairs(iterable):
4553 a, b = itertools.tee(iterable)
4554 next(b, None)
4555 return zip(a, b)
4556
4557class ZoneInfo(tzinfo):
4558 zoneroot = '/usr/share/zoneinfo'
4559 def __init__(self, ut, ti):
4560 """
4561
4562 :param ut: array
4563 Array of transition point timestamps
4564 :param ti: list
4565 A list of (offset, isdst, abbr) tuples
4566 :return: None
4567 """
4568 self.ut = ut
4569 self.ti = ti
4570 self.lt = self.invert(ut, ti)
4571
4572 @staticmethod
4573 def invert(ut, ti):
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04004574 lt = (array('q', ut), array('q', ut))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004575 if ut:
4576 offset = ti[0][0] // SEC
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04004577 lt[0][0] += offset
4578 lt[1][0] += offset
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004579 for i in range(1, len(ut)):
4580 lt[0][i] += ti[i-1][0] // SEC
4581 lt[1][i] += ti[i][0] // SEC
4582 return lt
4583
4584 @classmethod
4585 def fromfile(cls, fileobj):
4586 if fileobj.read(4).decode() != "TZif":
4587 raise ValueError("not a zoneinfo file")
4588 fileobj.seek(32)
4589 counts = array('i')
4590 counts.fromfile(fileobj, 3)
4591 if sys.byteorder != 'big':
4592 counts.byteswap()
4593
4594 ut = array('i')
4595 ut.fromfile(fileobj, counts[0])
4596 if sys.byteorder != 'big':
4597 ut.byteswap()
4598
4599 type_indices = array('B')
4600 type_indices.fromfile(fileobj, counts[0])
4601
4602 ttis = []
4603 for i in range(counts[1]):
4604 ttis.append(struct.unpack(">lbb", fileobj.read(6)))
4605
4606 abbrs = fileobj.read(counts[2])
4607
4608 # Convert ttis
4609 for i, (gmtoff, isdst, abbrind) in enumerate(ttis):
4610 abbr = abbrs[abbrind:abbrs.find(0, abbrind)].decode()
4611 ttis[i] = (timedelta(0, gmtoff), isdst, abbr)
4612
4613 ti = [None] * len(ut)
4614 for i, idx in enumerate(type_indices):
4615 ti[i] = ttis[idx]
4616
4617 self = cls(ut, ti)
4618
4619 return self
4620
4621 @classmethod
4622 def fromname(cls, name):
4623 path = os.path.join(cls.zoneroot, name)
4624 with open(path, 'rb') as f:
4625 return cls.fromfile(f)
4626
4627 EPOCHORDINAL = date(1970, 1, 1).toordinal()
4628
4629 def fromutc(self, dt):
4630 """datetime in UTC -> datetime in local time."""
4631
4632 if not isinstance(dt, datetime):
4633 raise TypeError("fromutc() requires a datetime argument")
4634 if dt.tzinfo is not self:
4635 raise ValueError("dt.tzinfo is not self")
4636
4637 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
4638 + dt.hour * 3600
4639 + dt.minute * 60
4640 + dt.second)
4641
4642 if timestamp < self.ut[1]:
4643 tti = self.ti[0]
4644 fold = 0
4645 else:
4646 idx = bisect.bisect_right(self.ut, timestamp)
4647 assert self.ut[idx-1] <= timestamp
4648 assert idx == len(self.ut) or timestamp < self.ut[idx]
4649 tti_prev, tti = self.ti[idx-2:idx]
4650 # Detect fold
4651 shift = tti_prev[0] - tti[0]
4652 fold = (shift > timedelta(0, timestamp - self.ut[idx-1]))
4653 dt += tti[0]
4654 if fold:
4655 return dt.replace(fold=1)
4656 else:
4657 return dt
4658
4659 def _find_ti(self, dt, i):
4660 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
4661 + dt.hour * 3600
4662 + dt.minute * 60
4663 + dt.second)
4664 lt = self.lt[dt.fold]
4665 idx = bisect.bisect_right(lt, timestamp)
4666
4667 return self.ti[max(0, idx - 1)][i]
4668
4669 def utcoffset(self, dt):
4670 return self._find_ti(dt, 0)
4671
4672 def dst(self, dt):
4673 isdst = self._find_ti(dt, 1)
4674 # XXX: We cannot accurately determine the "save" value,
4675 # so let's return 1h whenever DST is in effect. Since
4676 # we don't use dst() in fromutc(), it is unlikely that
4677 # it will be needed for anything more than bool(dst()).
4678 return ZERO if isdst else HOUR
4679
4680 def tzname(self, dt):
4681 return self._find_ti(dt, 2)
4682
4683 @classmethod
4684 def zonenames(cls, zonedir=None):
4685 if zonedir is None:
4686 zonedir = cls.zoneroot
Alexander Belopolsky1b8f26c2016-08-11 11:01:45 -04004687 zone_tab = os.path.join(zonedir, 'zone.tab')
4688 try:
4689 f = open(zone_tab)
4690 except OSError:
4691 return
4692 with f:
4693 for line in f:
4694 line = line.strip()
4695 if line and not line.startswith('#'):
4696 yield line.split()[2]
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004697
4698 @classmethod
4699 def stats(cls, start_year=1):
4700 count = gap_count = fold_count = zeros_count = 0
4701 min_gap = min_fold = timedelta.max
4702 max_gap = max_fold = ZERO
4703 min_gap_datetime = max_gap_datetime = datetime.min
4704 min_gap_zone = max_gap_zone = None
4705 min_fold_datetime = max_fold_datetime = datetime.min
4706 min_fold_zone = max_fold_zone = None
4707 stats_since = datetime(start_year, 1, 1) # Starting from 1970 eliminates a lot of noise
4708 for zonename in cls.zonenames():
4709 count += 1
4710 tz = cls.fromname(zonename)
4711 for dt, shift in tz.transitions():
4712 if dt < stats_since:
4713 continue
4714 if shift > ZERO:
4715 gap_count += 1
4716 if (shift, dt) > (max_gap, max_gap_datetime):
4717 max_gap = shift
4718 max_gap_zone = zonename
4719 max_gap_datetime = dt
4720 if (shift, datetime.max - dt) < (min_gap, datetime.max - min_gap_datetime):
4721 min_gap = shift
4722 min_gap_zone = zonename
4723 min_gap_datetime = dt
4724 elif shift < ZERO:
4725 fold_count += 1
4726 shift = -shift
4727 if (shift, dt) > (max_fold, max_fold_datetime):
4728 max_fold = shift
4729 max_fold_zone = zonename
4730 max_fold_datetime = dt
4731 if (shift, datetime.max - dt) < (min_fold, datetime.max - min_fold_datetime):
4732 min_fold = shift
4733 min_fold_zone = zonename
4734 min_fold_datetime = dt
4735 else:
4736 zeros_count += 1
4737 trans_counts = (gap_count, fold_count, zeros_count)
4738 print("Number of zones: %5d" % count)
4739 print("Number of transitions: %5d = %d (gaps) + %d (folds) + %d (zeros)" %
4740 ((sum(trans_counts),) + trans_counts))
4741 print("Min gap: %16s at %s in %s" % (min_gap, min_gap_datetime, min_gap_zone))
4742 print("Max gap: %16s at %s in %s" % (max_gap, max_gap_datetime, max_gap_zone))
4743 print("Min fold: %16s at %s in %s" % (min_fold, min_fold_datetime, min_fold_zone))
4744 print("Max fold: %16s at %s in %s" % (max_fold, max_fold_datetime, max_fold_zone))
4745
4746
4747 def transitions(self):
4748 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
4749 shift = ti[0] - prev_ti[0]
4750 yield datetime.utcfromtimestamp(t), shift
4751
4752 def nondst_folds(self):
4753 """Find all folds with the same value of isdst on both sides of the transition."""
4754 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
4755 shift = ti[0] - prev_ti[0]
4756 if shift < ZERO and ti[1] == prev_ti[1]:
4757 yield datetime.utcfromtimestamp(t), -shift, prev_ti[2], ti[2]
4758
4759 @classmethod
4760 def print_all_nondst_folds(cls, same_abbr=False, start_year=1):
4761 count = 0
4762 for zonename in cls.zonenames():
4763 tz = cls.fromname(zonename)
4764 for dt, shift, prev_abbr, abbr in tz.nondst_folds():
4765 if dt.year < start_year or same_abbr and prev_abbr != abbr:
4766 continue
4767 count += 1
4768 print("%3d) %-30s %s %10s %5s -> %s" %
4769 (count, zonename, dt, shift, prev_abbr, abbr))
4770
4771 def folds(self):
4772 for t, shift in self.transitions():
4773 if shift < ZERO:
4774 yield t, -shift
4775
4776 def gaps(self):
4777 for t, shift in self.transitions():
4778 if shift > ZERO:
4779 yield t, shift
4780
4781 def zeros(self):
4782 for t, shift in self.transitions():
4783 if not shift:
4784 yield t
4785
4786
4787class ZoneInfoTest(unittest.TestCase):
4788 zonename = 'America/New_York'
4789
4790 def setUp(self):
4791 if sys.platform == "win32":
4792 self.skipTest("Skipping zoneinfo tests on Windows")
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04004793 try:
4794 self.tz = ZoneInfo.fromname(self.zonename)
4795 except FileNotFoundError as err:
4796 self.skipTest("Skipping %s: %s" % (self.zonename, err))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004797
4798 def assertEquivDatetimes(self, a, b):
4799 self.assertEqual((a.replace(tzinfo=None), a.fold, id(a.tzinfo)),
4800 (b.replace(tzinfo=None), b.fold, id(b.tzinfo)))
4801
4802 def test_folds(self):
4803 tz = self.tz
4804 for dt, shift in tz.folds():
4805 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
4806 udt = dt + x
4807 ldt = tz.fromutc(udt.replace(tzinfo=tz))
4808 self.assertEqual(ldt.fold, 1)
4809 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
4810 self.assertEquivDatetimes(adt, ldt)
4811 utcoffset = ldt.utcoffset()
4812 self.assertEqual(ldt.replace(tzinfo=None), udt + utcoffset)
4813 # Round trip
4814 self.assertEquivDatetimes(ldt.astimezone(timezone.utc),
4815 udt.replace(tzinfo=timezone.utc))
4816
4817
4818 for x in [-timedelta.resolution, shift]:
4819 udt = dt + x
4820 udt = udt.replace(tzinfo=tz)
4821 ldt = tz.fromutc(udt)
4822 self.assertEqual(ldt.fold, 0)
4823
4824 def test_gaps(self):
4825 tz = self.tz
4826 for dt, shift in tz.gaps():
4827 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
4828 udt = dt + x
4829 udt = udt.replace(tzinfo=tz)
4830 ldt = tz.fromutc(udt)
4831 self.assertEqual(ldt.fold, 0)
4832 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
4833 self.assertEquivDatetimes(adt, ldt)
4834 utcoffset = ldt.utcoffset()
4835 self.assertEqual(ldt.replace(tzinfo=None), udt.replace(tzinfo=None) + utcoffset)
4836 # Create a local time inside the gap
4837 ldt = tz.fromutc(dt.replace(tzinfo=tz)) - shift + x
4838 self.assertLess(ldt.replace(fold=1).utcoffset(),
4839 ldt.replace(fold=0).utcoffset(),
4840 "At %s." % ldt)
4841
4842 for x in [-timedelta.resolution, shift]:
4843 udt = dt + x
4844 ldt = tz.fromutc(udt.replace(tzinfo=tz))
4845 self.assertEqual(ldt.fold, 0)
4846
4847 def test_system_transitions(self):
4848 if ('Riyadh8' in self.zonename or
4849 # From tzdata NEWS file:
4850 # The files solar87, solar88, and solar89 are no longer distributed.
4851 # They were a negative experiment - that is, a demonstration that
4852 # tz data can represent solar time only with some difficulty and error.
4853 # Their presence in the distribution caused confusion, as Riyadh
4854 # civil time was generally not solar time in those years.
4855 self.zonename.startswith('right/')):
4856 self.skipTest("Skipping %s" % self.zonename)
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04004857 tz = self.tz
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004858 TZ = os.environ.get('TZ')
4859 os.environ['TZ'] = self.zonename
4860 try:
4861 _time.tzset()
4862 for udt, shift in tz.transitions():
Alexander Belopolsky10c2dd22016-08-12 19:08:15 -04004863 if udt.year >= 2037:
4864 # System support for times around the end of 32-bit time_t
4865 # and later is flaky on many systems.
4866 break
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004867 s0 = (udt - datetime(1970, 1, 1)) // SEC
4868 ss = shift // SEC # shift seconds
4869 for x in [-40 * 3600, -20*3600, -1, 0,
4870 ss - 1, ss + 20 * 3600, ss + 40 * 3600]:
4871 s = s0 + x
4872 sdt = datetime.fromtimestamp(s)
4873 tzdt = datetime.fromtimestamp(s, tz).replace(tzinfo=None)
4874 self.assertEquivDatetimes(sdt, tzdt)
4875 s1 = sdt.timestamp()
4876 self.assertEqual(s, s1)
4877 if ss > 0: # gap
4878 # Create local time inside the gap
4879 dt = datetime.fromtimestamp(s0) - shift / 2
4880 ts0 = dt.timestamp()
4881 ts1 = dt.replace(fold=1).timestamp()
4882 self.assertEqual(ts0, s0 + ss / 2)
4883 self.assertEqual(ts1, s0 - ss / 2)
4884 finally:
4885 if TZ is None:
4886 del os.environ['TZ']
4887 else:
4888 os.environ['TZ'] = TZ
4889 _time.tzset()
4890
4891
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04004892class ZoneInfoCompleteTest(unittest.TestSuite):
4893 def __init__(self):
4894 tests = []
4895 if is_resource_enabled('tzdata'):
4896 for name in ZoneInfo.zonenames():
4897 Test = type('ZoneInfoTest[%s]' % name, (ZoneInfoTest,), {})
4898 Test.zonename = name
4899 for method in dir(Test):
4900 if method.startswith('test_'):
4901 tests.append(Test(method))
4902 super().__init__(tests)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004903
4904# Iran had a sub-minute UTC offset before 1946.
4905class IranTest(ZoneInfoTest):
Alexander Belopolsky07e2a0a2016-07-30 11:41:02 -04004906 zonename = 'Asia/Tehran'
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004907
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04004908def load_tests(loader, standard_tests, pattern):
4909 standard_tests.addTest(ZoneInfoCompleteTest())
4910 return standard_tests
4911
4912
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004913if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -05004914 unittest.main()