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