blob: e71f3aa9b46500733a4155baab40464e40013cba [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 Belopolsky5d0c5982016-07-22 18:47:04 -04001727 # Positional fold:
1728 self.assertRaises(TypeError, self.theclass,
1729 2000, 1, 31, 23, 59, 59, 0, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04001730
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001731 def test_hash_equality(self):
1732 d = self.theclass(2000, 12, 31, 23, 30, 17)
1733 e = self.theclass(2000, 12, 31, 23, 30, 17)
1734 self.assertEqual(d, e)
1735 self.assertEqual(hash(d), hash(e))
1736
1737 dic = {d: 1}
1738 dic[e] = 2
1739 self.assertEqual(len(dic), 1)
1740 self.assertEqual(dic[d], 2)
1741 self.assertEqual(dic[e], 2)
1742
1743 d = self.theclass(2001, 1, 1, 0, 5, 17)
1744 e = self.theclass(2001, 1, 1, 0, 5, 17)
1745 self.assertEqual(d, e)
1746 self.assertEqual(hash(d), hash(e))
1747
1748 dic = {d: 1}
1749 dic[e] = 2
1750 self.assertEqual(len(dic), 1)
1751 self.assertEqual(dic[d], 2)
1752 self.assertEqual(dic[e], 2)
1753
1754 def test_computations(self):
1755 a = self.theclass(2002, 1, 31)
1756 b = self.theclass(1956, 1, 31)
1757 diff = a-b
1758 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1759 self.assertEqual(diff.seconds, 0)
1760 self.assertEqual(diff.microseconds, 0)
1761 a = self.theclass(2002, 3, 2, 17, 6)
1762 millisec = timedelta(0, 0, 1000)
1763 hour = timedelta(0, 3600)
1764 day = timedelta(1)
1765 week = timedelta(7)
1766 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1767 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1768 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1769 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1770 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1771 self.assertEqual(a - hour, a + -hour)
1772 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1773 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1774 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1775 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1776 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1777 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1778 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1779 self.assertEqual((a + week) - a, week)
1780 self.assertEqual((a + day) - a, day)
1781 self.assertEqual((a + hour) - a, hour)
1782 self.assertEqual((a + millisec) - a, millisec)
1783 self.assertEqual((a - week) - a, -week)
1784 self.assertEqual((a - day) - a, -day)
1785 self.assertEqual((a - hour) - a, -hour)
1786 self.assertEqual((a - millisec) - a, -millisec)
1787 self.assertEqual(a - (a + week), -week)
1788 self.assertEqual(a - (a + day), -day)
1789 self.assertEqual(a - (a + hour), -hour)
1790 self.assertEqual(a - (a + millisec), -millisec)
1791 self.assertEqual(a - (a - week), week)
1792 self.assertEqual(a - (a - day), day)
1793 self.assertEqual(a - (a - hour), hour)
1794 self.assertEqual(a - (a - millisec), millisec)
1795 self.assertEqual(a + (week + day + hour + millisec),
1796 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1797 self.assertEqual(a + (week + day + hour + millisec),
1798 (((a + week) + day) + hour) + millisec)
1799 self.assertEqual(a - (week + day + hour + millisec),
1800 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1801 self.assertEqual(a - (week + day + hour + millisec),
1802 (((a - week) - day) - hour) - millisec)
1803 # Add/sub ints or floats should be illegal
1804 for i in 1, 1.0:
1805 self.assertRaises(TypeError, lambda: a+i)
1806 self.assertRaises(TypeError, lambda: a-i)
1807 self.assertRaises(TypeError, lambda: i+a)
1808 self.assertRaises(TypeError, lambda: i-a)
1809
1810 # delta - datetime is senseless.
1811 self.assertRaises(TypeError, lambda: day - a)
1812 # mixing datetime and (delta or datetime) via * or // is senseless
1813 self.assertRaises(TypeError, lambda: day * a)
1814 self.assertRaises(TypeError, lambda: a * day)
1815 self.assertRaises(TypeError, lambda: day // a)
1816 self.assertRaises(TypeError, lambda: a // day)
1817 self.assertRaises(TypeError, lambda: a * a)
1818 self.assertRaises(TypeError, lambda: a // a)
1819 # datetime + datetime is senseless
1820 self.assertRaises(TypeError, lambda: a + a)
1821
1822 def test_pickling(self):
1823 args = 6, 7, 23, 20, 59, 1, 64**2
1824 orig = self.theclass(*args)
1825 for pickler, unpickler, proto in pickle_choices:
1826 green = pickler.dumps(orig, proto)
1827 derived = unpickler.loads(green)
1828 self.assertEqual(orig, derived)
1829
1830 def test_more_pickling(self):
1831 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
Serhiy Storchakabad12572014-12-15 14:03:42 +02001832 for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1833 s = pickle.dumps(a, proto)
1834 b = pickle.loads(s)
1835 self.assertEqual(b.year, 2003)
1836 self.assertEqual(b.month, 2)
1837 self.assertEqual(b.day, 7)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001838
1839 def test_pickling_subclass_datetime(self):
1840 args = 6, 7, 23, 20, 59, 1, 64**2
1841 orig = SubclassDatetime(*args)
1842 for pickler, unpickler, proto in pickle_choices:
1843 green = pickler.dumps(orig, proto)
1844 derived = unpickler.loads(green)
1845 self.assertEqual(orig, derived)
1846
1847 def test_more_compare(self):
1848 # The test_compare() inherited from TestDate covers the error cases.
1849 # We just want to test lexicographic ordering on the members datetime
1850 # has that date lacks.
1851 args = [2000, 11, 29, 20, 58, 16, 999998]
1852 t1 = self.theclass(*args)
1853 t2 = self.theclass(*args)
1854 self.assertEqual(t1, t2)
1855 self.assertTrue(t1 <= t2)
1856 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001857 self.assertFalse(t1 != t2)
1858 self.assertFalse(t1 < t2)
1859 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001860
1861 for i in range(len(args)):
1862 newargs = args[:]
1863 newargs[i] = args[i] + 1
1864 t2 = self.theclass(*newargs) # this is larger than t1
1865 self.assertTrue(t1 < t2)
1866 self.assertTrue(t2 > t1)
1867 self.assertTrue(t1 <= t2)
1868 self.assertTrue(t2 >= t1)
1869 self.assertTrue(t1 != t2)
1870 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001871 self.assertFalse(t1 == t2)
1872 self.assertFalse(t2 == t1)
1873 self.assertFalse(t1 > t2)
1874 self.assertFalse(t2 < t1)
1875 self.assertFalse(t1 >= t2)
1876 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001877
1878
1879 # A helper for timestamp constructor tests.
1880 def verify_field_equality(self, expected, got):
1881 self.assertEqual(expected.tm_year, got.year)
1882 self.assertEqual(expected.tm_mon, got.month)
1883 self.assertEqual(expected.tm_mday, got.day)
1884 self.assertEqual(expected.tm_hour, got.hour)
1885 self.assertEqual(expected.tm_min, got.minute)
1886 self.assertEqual(expected.tm_sec, got.second)
1887
1888 def test_fromtimestamp(self):
1889 import time
1890
1891 ts = time.time()
1892 expected = time.localtime(ts)
1893 got = self.theclass.fromtimestamp(ts)
1894 self.verify_field_equality(expected, got)
1895
1896 def test_utcfromtimestamp(self):
1897 import time
1898
1899 ts = time.time()
1900 expected = time.gmtime(ts)
1901 got = self.theclass.utcfromtimestamp(ts)
1902 self.verify_field_equality(expected, got)
1903
Alexander Belopolskya4415142012-06-08 12:33:09 -04001904 # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in
1905 # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0).
1906 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
1907 def test_timestamp_naive(self):
1908 t = self.theclass(1970, 1, 1)
1909 self.assertEqual(t.timestamp(), 18000.0)
1910 t = self.theclass(1970, 1, 1, 1, 2, 3, 4)
1911 self.assertEqual(t.timestamp(),
1912 18000.0 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001913 # Missing hour
1914 t0 = self.theclass(2012, 3, 11, 2, 30)
1915 t1 = t0.replace(fold=1)
1916 self.assertEqual(self.theclass.fromtimestamp(t1.timestamp()),
1917 t0 - timedelta(hours=1))
1918 self.assertEqual(self.theclass.fromtimestamp(t0.timestamp()),
1919 t1 + timedelta(hours=1))
Alexander Belopolskya4415142012-06-08 12:33:09 -04001920 # Ambiguous hour defaults to DST
1921 t = self.theclass(2012, 11, 4, 1, 30)
1922 self.assertEqual(self.theclass.fromtimestamp(t.timestamp()), t)
1923
1924 # Timestamp may raise an overflow error on some platforms
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001925 # XXX: Do we care to support the first and last year?
1926 for t in [self.theclass(2,1,1), self.theclass(9998,12,12)]:
Alexander Belopolskya4415142012-06-08 12:33:09 -04001927 try:
1928 s = t.timestamp()
1929 except OverflowError:
1930 pass
1931 else:
1932 self.assertEqual(self.theclass.fromtimestamp(s), t)
1933
1934 def test_timestamp_aware(self):
1935 t = self.theclass(1970, 1, 1, tzinfo=timezone.utc)
1936 self.assertEqual(t.timestamp(), 0.0)
1937 t = self.theclass(1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone.utc)
1938 self.assertEqual(t.timestamp(),
1939 3600 + 2*60 + 3 + 4*1e-6)
1940 t = self.theclass(1970, 1, 1, 1, 2, 3, 4,
1941 tzinfo=timezone(timedelta(hours=-5), 'EST'))
1942 self.assertEqual(t.timestamp(),
1943 18000 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001944
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001945 @support.run_with_tz('MSK-03') # Something east of Greenwich
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001946 def test_microsecond_rounding(self):
Victor Stinner5ebfe422015-09-18 14:52:15 +02001947 for fts in [self.theclass.fromtimestamp,
1948 self.theclass.utcfromtimestamp]:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001949 zero = fts(0)
1950 self.assertEqual(zero.second, 0)
1951 self.assertEqual(zero.microsecond, 0)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02001952 one = fts(1e-6)
Victor Stinner8050ca92012-03-14 00:17:05 +01001953 try:
1954 minus_one = fts(-1e-6)
1955 except OSError:
1956 # localtime(-1) and gmtime(-1) is not supported on Windows
1957 pass
1958 else:
1959 self.assertEqual(minus_one.second, 59)
1960 self.assertEqual(minus_one.microsecond, 999999)
Victor Stinner5d272cc2012-03-13 13:35:55 +01001961
Victor Stinner8050ca92012-03-14 00:17:05 +01001962 t = fts(-1e-8)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02001963 self.assertEqual(t, zero)
Victor Stinner8050ca92012-03-14 00:17:05 +01001964 t = fts(-9e-7)
1965 self.assertEqual(t, minus_one)
1966 t = fts(-1e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02001967 self.assertEqual(t, zero)
Victor Stinner8820a352015-09-05 10:50:20 +02001968 t = fts(-1/2**7)
1969 self.assertEqual(t.second, 59)
Victor Stinner7667f582015-09-09 01:02:23 +02001970 self.assertEqual(t.microsecond, 992188)
Victor Stinner5d272cc2012-03-13 13:35:55 +01001971
1972 t = fts(1e-7)
1973 self.assertEqual(t, zero)
1974 t = fts(9e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02001975 self.assertEqual(t, one)
Victor Stinner5d272cc2012-03-13 13:35:55 +01001976 t = fts(0.99999949)
1977 self.assertEqual(t.second, 0)
1978 self.assertEqual(t.microsecond, 999999)
1979 t = fts(0.9999999)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02001980 self.assertEqual(t.second, 1)
1981 self.assertEqual(t.microsecond, 0)
Victor Stinneradfefa52015-09-04 23:57:25 +02001982 t = fts(1/2**7)
1983 self.assertEqual(t.second, 0)
Victor Stinner7667f582015-09-09 01:02:23 +02001984 self.assertEqual(t.microsecond, 7812)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001985
1986 def test_insane_fromtimestamp(self):
1987 # It's possible that some platform maps time_t to double,
1988 # and that this test will fail there. This test should
1989 # exempt such platforms (provided they return reasonable
1990 # results!).
1991 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001992 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001993 insane)
1994
1995 def test_insane_utcfromtimestamp(self):
1996 # It's possible that some platform maps time_t to double,
1997 # and that this test will fail there. This test should
1998 # exempt such platforms (provided they return reasonable
1999 # results!).
2000 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002001 self.assertRaises(OverflowError, self.theclass.utcfromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002002 insane)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002003
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002004 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2005 def test_negative_float_fromtimestamp(self):
2006 # The result is tz-dependent; at least test that this doesn't
2007 # fail (like it did before bug 1646728 was fixed).
2008 self.theclass.fromtimestamp(-1.05)
2009
2010 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2011 def test_negative_float_utcfromtimestamp(self):
2012 d = self.theclass.utcfromtimestamp(-1.05)
2013 self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
2014
2015 def test_utcnow(self):
2016 import time
2017
2018 # Call it a success if utcnow() and utcfromtimestamp() are within
2019 # a second of each other.
2020 tolerance = timedelta(seconds=1)
2021 for dummy in range(3):
2022 from_now = self.theclass.utcnow()
2023 from_timestamp = self.theclass.utcfromtimestamp(time.time())
2024 if abs(from_timestamp - from_now) <= tolerance:
2025 break
2026 # Else try again a few times.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002027 self.assertLessEqual(abs(from_timestamp - from_now), tolerance)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002028
2029 def test_strptime(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002030 string = '2004-12-01 13:02:47.197'
2031 format = '%Y-%m-%d %H:%M:%S.%f'
2032 expected = _strptime._strptime_datetime(self.theclass, string, format)
2033 got = self.theclass.strptime(string, format)
2034 self.assertEqual(expected, got)
2035 self.assertIs(type(expected), self.theclass)
2036 self.assertIs(type(got), self.theclass)
2037
2038 strptime = self.theclass.strptime
2039 self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
2040 self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
2041 # Only local timezone and UTC are supported
2042 for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
2043 (-_time.timezone, _time.tzname[0])):
2044 if tzseconds < 0:
2045 sign = '-'
2046 seconds = -tzseconds
2047 else:
2048 sign ='+'
2049 seconds = tzseconds
2050 hours, minutes = divmod(seconds//60, 60)
2051 dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
Martin Panterfca22322015-11-16 09:22:19 +00002052 dt = strptime(dtstr, "%z %Z")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002053 self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
2054 self.assertEqual(dt.tzname(), tzname)
2055 # Can produce inconsistent datetime
2056 dtstr, fmt = "+1234 UTC", "%z %Z"
2057 dt = strptime(dtstr, fmt)
2058 self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE)
2059 self.assertEqual(dt.tzname(), 'UTC')
2060 # yet will roundtrip
2061 self.assertEqual(dt.strftime(fmt), dtstr)
2062
2063 # Produce naive datetime if no %z is provided
2064 self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
2065
2066 with self.assertRaises(ValueError): strptime("-2400", "%z")
2067 with self.assertRaises(ValueError): strptime("-000", "%z")
2068
2069 def test_more_timetuple(self):
2070 # This tests fields beyond those tested by the TestDate.test_timetuple.
2071 t = self.theclass(2004, 12, 31, 6, 22, 33)
2072 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
2073 self.assertEqual(t.timetuple(),
2074 (t.year, t.month, t.day,
2075 t.hour, t.minute, t.second,
2076 t.weekday(),
2077 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
2078 -1))
2079 tt = t.timetuple()
2080 self.assertEqual(tt.tm_year, t.year)
2081 self.assertEqual(tt.tm_mon, t.month)
2082 self.assertEqual(tt.tm_mday, t.day)
2083 self.assertEqual(tt.tm_hour, t.hour)
2084 self.assertEqual(tt.tm_min, t.minute)
2085 self.assertEqual(tt.tm_sec, t.second)
2086 self.assertEqual(tt.tm_wday, t.weekday())
2087 self.assertEqual(tt.tm_yday, t.toordinal() -
2088 date(t.year, 1, 1).toordinal() + 1)
2089 self.assertEqual(tt.tm_isdst, -1)
2090
2091 def test_more_strftime(self):
2092 # This tests fields beyond those tested by the TestDate.test_strftime.
2093 t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
2094 self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
2095 "12 31 04 000047 33 22 06 366")
2096
2097 def test_extract(self):
2098 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2099 self.assertEqual(dt.date(), date(2002, 3, 4))
2100 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2101
2102 def test_combine(self):
2103 d = date(2002, 3, 4)
2104 t = time(18, 45, 3, 1234)
2105 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2106 combine = self.theclass.combine
2107 dt = combine(d, t)
2108 self.assertEqual(dt, expected)
2109
2110 dt = combine(time=t, date=d)
2111 self.assertEqual(dt, expected)
2112
2113 self.assertEqual(d, dt.date())
2114 self.assertEqual(t, dt.time())
2115 self.assertEqual(dt, combine(dt.date(), dt.time()))
2116
2117 self.assertRaises(TypeError, combine) # need an arg
2118 self.assertRaises(TypeError, combine, d) # need two args
2119 self.assertRaises(TypeError, combine, t, d) # args reversed
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002120 self.assertRaises(TypeError, combine, d, t, 1) # wrong tzinfo type
2121 self.assertRaises(TypeError, combine, d, t, 1, 2) # too many args
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002122 self.assertRaises(TypeError, combine, "date", "time") # wrong types
2123 self.assertRaises(TypeError, combine, d, "time") # wrong type
2124 self.assertRaises(TypeError, combine, "date", t) # wrong type
2125
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002126 # tzinfo= argument
2127 dt = combine(d, t, timezone.utc)
2128 self.assertIs(dt.tzinfo, timezone.utc)
2129 dt = combine(d, t, tzinfo=timezone.utc)
2130 self.assertIs(dt.tzinfo, timezone.utc)
2131 t = time()
2132 dt = combine(dt, t)
2133 self.assertEqual(dt.date(), d)
2134 self.assertEqual(dt.time(), t)
2135
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002136 def test_replace(self):
2137 cls = self.theclass
2138 args = [1, 2, 3, 4, 5, 6, 7]
2139 base = cls(*args)
2140 self.assertEqual(base, base.replace())
2141
2142 i = 0
2143 for name, newval in (("year", 2),
2144 ("month", 3),
2145 ("day", 4),
2146 ("hour", 5),
2147 ("minute", 6),
2148 ("second", 7),
2149 ("microsecond", 8)):
2150 newargs = args[:]
2151 newargs[i] = newval
2152 expected = cls(*newargs)
2153 got = base.replace(**{name: newval})
2154 self.assertEqual(expected, got)
2155 i += 1
2156
2157 # Out of bounds.
2158 base = cls(2000, 2, 29)
2159 self.assertRaises(ValueError, base.replace, year=2001)
2160
2161 def test_astimezone(self):
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002162 return # The rest is no longer applicable
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002163 # Pretty boring! The TZ test is more interesting here. astimezone()
2164 # simply can't be applied to a naive object.
2165 dt = self.theclass.now()
2166 f = FixedOffset(44, "")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04002167 self.assertRaises(ValueError, dt.astimezone) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002168 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
2169 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
2170 self.assertRaises(ValueError, dt.astimezone, f) # naive
2171 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
2172
2173 class Bogus(tzinfo):
2174 def utcoffset(self, dt): return None
2175 def dst(self, dt): return timedelta(0)
2176 bog = Bogus()
2177 self.assertRaises(ValueError, dt.astimezone, bog) # naive
2178 self.assertRaises(ValueError,
2179 dt.replace(tzinfo=bog).astimezone, f)
2180
2181 class AlsoBogus(tzinfo):
2182 def utcoffset(self, dt): return timedelta(0)
2183 def dst(self, dt): return None
2184 alsobog = AlsoBogus()
2185 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
2186
2187 def test_subclass_datetime(self):
2188
2189 class C(self.theclass):
2190 theAnswer = 42
2191
2192 def __new__(cls, *args, **kws):
2193 temp = kws.copy()
2194 extra = temp.pop('extra')
2195 result = self.theclass.__new__(cls, *args, **temp)
2196 result.extra = extra
2197 return result
2198
2199 def newmeth(self, start):
2200 return start + self.year + self.month + self.second
2201
2202 args = 2003, 4, 14, 12, 13, 41
2203
2204 dt1 = self.theclass(*args)
2205 dt2 = C(*args, **{'extra': 7})
2206
2207 self.assertEqual(dt2.__class__, C)
2208 self.assertEqual(dt2.theAnswer, 42)
2209 self.assertEqual(dt2.extra, 7)
2210 self.assertEqual(dt1.toordinal(), dt2.toordinal())
2211 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
2212 dt1.second - 7)
2213
2214class TestSubclassDateTime(TestDateTime):
2215 theclass = SubclassDatetime
2216 # Override tests not designed for subclass
Zachary Ware9fe6d862013-12-08 00:20:35 -06002217 @unittest.skip('not appropriate for subclasses')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002218 def test_roundtrip(self):
2219 pass
2220
2221class SubclassTime(time):
2222 sub_var = 1
2223
2224class TestTime(HarmlessMixedComparison, unittest.TestCase):
2225
2226 theclass = time
2227
2228 def test_basic_attributes(self):
2229 t = self.theclass(12, 0)
2230 self.assertEqual(t.hour, 12)
2231 self.assertEqual(t.minute, 0)
2232 self.assertEqual(t.second, 0)
2233 self.assertEqual(t.microsecond, 0)
2234
2235 def test_basic_attributes_nonzero(self):
2236 # Make sure all attributes are non-zero so bugs in
2237 # bit-shifting access show up.
2238 t = self.theclass(12, 59, 59, 8000)
2239 self.assertEqual(t.hour, 12)
2240 self.assertEqual(t.minute, 59)
2241 self.assertEqual(t.second, 59)
2242 self.assertEqual(t.microsecond, 8000)
2243
2244 def test_roundtrip(self):
2245 t = self.theclass(1, 2, 3, 4)
2246
2247 # Verify t -> string -> time identity.
2248 s = repr(t)
2249 self.assertTrue(s.startswith('datetime.'))
2250 s = s[9:]
2251 t2 = eval(s)
2252 self.assertEqual(t, t2)
2253
2254 # Verify identity via reconstructing from pieces.
2255 t2 = self.theclass(t.hour, t.minute, t.second,
2256 t.microsecond)
2257 self.assertEqual(t, t2)
2258
2259 def test_comparing(self):
2260 args = [1, 2, 3, 4]
2261 t1 = self.theclass(*args)
2262 t2 = self.theclass(*args)
2263 self.assertEqual(t1, t2)
2264 self.assertTrue(t1 <= t2)
2265 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002266 self.assertFalse(t1 != t2)
2267 self.assertFalse(t1 < t2)
2268 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002269
2270 for i in range(len(args)):
2271 newargs = args[:]
2272 newargs[i] = args[i] + 1
2273 t2 = self.theclass(*newargs) # this is larger than t1
2274 self.assertTrue(t1 < t2)
2275 self.assertTrue(t2 > t1)
2276 self.assertTrue(t1 <= t2)
2277 self.assertTrue(t2 >= t1)
2278 self.assertTrue(t1 != t2)
2279 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002280 self.assertFalse(t1 == t2)
2281 self.assertFalse(t2 == t1)
2282 self.assertFalse(t1 > t2)
2283 self.assertFalse(t2 < t1)
2284 self.assertFalse(t1 >= t2)
2285 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002286
2287 for badarg in OTHERSTUFF:
2288 self.assertEqual(t1 == badarg, False)
2289 self.assertEqual(t1 != badarg, True)
2290 self.assertEqual(badarg == t1, False)
2291 self.assertEqual(badarg != t1, True)
2292
2293 self.assertRaises(TypeError, lambda: t1 <= badarg)
2294 self.assertRaises(TypeError, lambda: t1 < badarg)
2295 self.assertRaises(TypeError, lambda: t1 > badarg)
2296 self.assertRaises(TypeError, lambda: t1 >= badarg)
2297 self.assertRaises(TypeError, lambda: badarg <= t1)
2298 self.assertRaises(TypeError, lambda: badarg < t1)
2299 self.assertRaises(TypeError, lambda: badarg > t1)
2300 self.assertRaises(TypeError, lambda: badarg >= t1)
2301
2302 def test_bad_constructor_arguments(self):
2303 # bad hours
2304 self.theclass(0, 0) # no exception
2305 self.theclass(23, 0) # no exception
2306 self.assertRaises(ValueError, self.theclass, -1, 0)
2307 self.assertRaises(ValueError, self.theclass, 24, 0)
2308 # bad minutes
2309 self.theclass(23, 0) # no exception
2310 self.theclass(23, 59) # no exception
2311 self.assertRaises(ValueError, self.theclass, 23, -1)
2312 self.assertRaises(ValueError, self.theclass, 23, 60)
2313 # bad seconds
2314 self.theclass(23, 59, 0) # no exception
2315 self.theclass(23, 59, 59) # no exception
2316 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
2317 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
2318 # bad microseconds
2319 self.theclass(23, 59, 59, 0) # no exception
2320 self.theclass(23, 59, 59, 999999) # no exception
2321 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
2322 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
2323
2324 def test_hash_equality(self):
2325 d = self.theclass(23, 30, 17)
2326 e = self.theclass(23, 30, 17)
2327 self.assertEqual(d, e)
2328 self.assertEqual(hash(d), hash(e))
2329
2330 dic = {d: 1}
2331 dic[e] = 2
2332 self.assertEqual(len(dic), 1)
2333 self.assertEqual(dic[d], 2)
2334 self.assertEqual(dic[e], 2)
2335
2336 d = self.theclass(0, 5, 17)
2337 e = self.theclass(0, 5, 17)
2338 self.assertEqual(d, e)
2339 self.assertEqual(hash(d), hash(e))
2340
2341 dic = {d: 1}
2342 dic[e] = 2
2343 self.assertEqual(len(dic), 1)
2344 self.assertEqual(dic[d], 2)
2345 self.assertEqual(dic[e], 2)
2346
2347 def test_isoformat(self):
2348 t = self.theclass(4, 5, 1, 123)
2349 self.assertEqual(t.isoformat(), "04:05:01.000123")
2350 self.assertEqual(t.isoformat(), str(t))
2351
2352 t = self.theclass()
2353 self.assertEqual(t.isoformat(), "00:00:00")
2354 self.assertEqual(t.isoformat(), str(t))
2355
2356 t = self.theclass(microsecond=1)
2357 self.assertEqual(t.isoformat(), "00:00:00.000001")
2358 self.assertEqual(t.isoformat(), str(t))
2359
2360 t = self.theclass(microsecond=10)
2361 self.assertEqual(t.isoformat(), "00:00:00.000010")
2362 self.assertEqual(t.isoformat(), str(t))
2363
2364 t = self.theclass(microsecond=100)
2365 self.assertEqual(t.isoformat(), "00:00:00.000100")
2366 self.assertEqual(t.isoformat(), str(t))
2367
2368 t = self.theclass(microsecond=1000)
2369 self.assertEqual(t.isoformat(), "00:00:00.001000")
2370 self.assertEqual(t.isoformat(), str(t))
2371
2372 t = self.theclass(microsecond=10000)
2373 self.assertEqual(t.isoformat(), "00:00:00.010000")
2374 self.assertEqual(t.isoformat(), str(t))
2375
2376 t = self.theclass(microsecond=100000)
2377 self.assertEqual(t.isoformat(), "00:00:00.100000")
2378 self.assertEqual(t.isoformat(), str(t))
2379
Alexander Belopolskya2998a62016-03-06 14:58:43 -05002380 t = self.theclass(hour=12, minute=34, second=56, microsecond=123456)
2381 self.assertEqual(t.isoformat(timespec='hours'), "12")
2382 self.assertEqual(t.isoformat(timespec='minutes'), "12:34")
2383 self.assertEqual(t.isoformat(timespec='seconds'), "12:34:56")
2384 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.123")
2385 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.123456")
2386 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56.123456")
2387 self.assertRaises(ValueError, t.isoformat, timespec='monkey')
2388
2389 t = self.theclass(hour=12, minute=34, second=56, microsecond=999500)
2390 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.999")
2391
2392 t = self.theclass(hour=12, minute=34, second=56, microsecond=0)
2393 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.000")
2394 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.000000")
2395 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56")
2396
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002397 def test_1653736(self):
2398 # verify it doesn't accept extra keyword arguments
2399 t = self.theclass(second=1)
2400 self.assertRaises(TypeError, t.isoformat, foo=3)
2401
2402 def test_strftime(self):
2403 t = self.theclass(1, 2, 3, 4)
2404 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
2405 # A naive object replaces %z and %Z with empty strings.
2406 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
2407
2408 def test_format(self):
2409 t = self.theclass(1, 2, 3, 4)
2410 self.assertEqual(t.__format__(''), str(t))
2411
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02002412 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002413 t.__format__(123)
2414
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002415 # check that a derived class's __str__() gets called
2416 class A(self.theclass):
2417 def __str__(self):
2418 return 'A'
2419 a = A(1, 2, 3, 4)
2420 self.assertEqual(a.__format__(''), 'A')
2421
2422 # check that a derived class's strftime gets called
2423 class B(self.theclass):
2424 def strftime(self, format_spec):
2425 return 'B'
2426 b = B(1, 2, 3, 4)
2427 self.assertEqual(b.__format__(''), str(t))
2428
2429 for fmt in ['%H %M %S',
2430 ]:
2431 self.assertEqual(t.__format__(fmt), t.strftime(fmt))
2432 self.assertEqual(a.__format__(fmt), t.strftime(fmt))
2433 self.assertEqual(b.__format__(fmt), 'B')
2434
2435 def test_str(self):
2436 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
2437 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
2438 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
2439 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
2440 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
2441
2442 def test_repr(self):
2443 name = 'datetime.' + self.theclass.__name__
2444 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
2445 "%s(1, 2, 3, 4)" % name)
2446 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
2447 "%s(10, 2, 3, 4000)" % name)
2448 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
2449 "%s(0, 2, 3, 400000)" % name)
2450 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
2451 "%s(12, 2, 3)" % name)
2452 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
2453 "%s(23, 15)" % name)
2454
2455 def test_resolution_info(self):
2456 self.assertIsInstance(self.theclass.min, self.theclass)
2457 self.assertIsInstance(self.theclass.max, self.theclass)
2458 self.assertIsInstance(self.theclass.resolution, timedelta)
2459 self.assertTrue(self.theclass.max > self.theclass.min)
2460
2461 def test_pickling(self):
2462 args = 20, 59, 16, 64**2
2463 orig = self.theclass(*args)
2464 for pickler, unpickler, proto in pickle_choices:
2465 green = pickler.dumps(orig, proto)
2466 derived = unpickler.loads(green)
2467 self.assertEqual(orig, derived)
2468
2469 def test_pickling_subclass_time(self):
2470 args = 20, 59, 16, 64**2
2471 orig = SubclassTime(*args)
2472 for pickler, unpickler, proto in pickle_choices:
2473 green = pickler.dumps(orig, proto)
2474 derived = unpickler.loads(green)
2475 self.assertEqual(orig, derived)
2476
2477 def test_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002478 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002479 cls = self.theclass
2480 self.assertTrue(cls(1))
2481 self.assertTrue(cls(0, 1))
2482 self.assertTrue(cls(0, 0, 1))
2483 self.assertTrue(cls(0, 0, 0, 1))
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002484 self.assertTrue(cls(0))
2485 self.assertTrue(cls())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002486
2487 def test_replace(self):
2488 cls = self.theclass
2489 args = [1, 2, 3, 4]
2490 base = cls(*args)
2491 self.assertEqual(base, base.replace())
2492
2493 i = 0
2494 for name, newval in (("hour", 5),
2495 ("minute", 6),
2496 ("second", 7),
2497 ("microsecond", 8)):
2498 newargs = args[:]
2499 newargs[i] = newval
2500 expected = cls(*newargs)
2501 got = base.replace(**{name: newval})
2502 self.assertEqual(expected, got)
2503 i += 1
2504
2505 # Out of bounds.
2506 base = cls(1)
2507 self.assertRaises(ValueError, base.replace, hour=24)
2508 self.assertRaises(ValueError, base.replace, minute=-1)
2509 self.assertRaises(ValueError, base.replace, second=100)
2510 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2511
2512 def test_subclass_time(self):
2513
2514 class C(self.theclass):
2515 theAnswer = 42
2516
2517 def __new__(cls, *args, **kws):
2518 temp = kws.copy()
2519 extra = temp.pop('extra')
2520 result = self.theclass.__new__(cls, *args, **temp)
2521 result.extra = extra
2522 return result
2523
2524 def newmeth(self, start):
2525 return start + self.hour + self.second
2526
2527 args = 4, 5, 6
2528
2529 dt1 = self.theclass(*args)
2530 dt2 = C(*args, **{'extra': 7})
2531
2532 self.assertEqual(dt2.__class__, C)
2533 self.assertEqual(dt2.theAnswer, 42)
2534 self.assertEqual(dt2.extra, 7)
2535 self.assertEqual(dt1.isoformat(), dt2.isoformat())
2536 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2537
2538 def test_backdoor_resistance(self):
2539 # see TestDate.test_backdoor_resistance().
2540 base = '2:59.0'
2541 for hour_byte in ' ', '9', chr(24), '\xff':
2542 self.assertRaises(TypeError, self.theclass,
2543 hour_byte + base[1:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002544 # Good bytes, but bad tzinfo:
2545 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
2546 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002547
2548# A mixin for classes with a tzinfo= argument. Subclasses must define
Martin Panter46f50722016-05-26 05:35:26 +00002549# theclass as a class attribute, and theclass(1, 1, 1, tzinfo=whatever)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002550# must be legit (which is true for time and datetime).
2551class TZInfoBase:
2552
2553 def test_argument_passing(self):
2554 cls = self.theclass
2555 # A datetime passes itself on, a time passes None.
2556 class introspective(tzinfo):
2557 def tzname(self, dt): return dt and "real" or "none"
2558 def utcoffset(self, dt):
2559 return timedelta(minutes = dt and 42 or -42)
2560 dst = utcoffset
2561
2562 obj = cls(1, 2, 3, tzinfo=introspective())
2563
2564 expected = cls is time and "none" or "real"
2565 self.assertEqual(obj.tzname(), expected)
2566
2567 expected = timedelta(minutes=(cls is time and -42 or 42))
2568 self.assertEqual(obj.utcoffset(), expected)
2569 self.assertEqual(obj.dst(), expected)
2570
2571 def test_bad_tzinfo_classes(self):
2572 cls = self.theclass
2573 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
2574
2575 class NiceTry(object):
2576 def __init__(self): pass
2577 def utcoffset(self, dt): pass
2578 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
2579
2580 class BetterTry(tzinfo):
2581 def __init__(self): pass
2582 def utcoffset(self, dt): pass
2583 b = BetterTry()
2584 t = cls(1, 1, 1, tzinfo=b)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002585 self.assertIs(t.tzinfo, b)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002586
2587 def test_utc_offset_out_of_bounds(self):
2588 class Edgy(tzinfo):
2589 def __init__(self, offset):
2590 self.offset = timedelta(minutes=offset)
2591 def utcoffset(self, dt):
2592 return self.offset
2593
2594 cls = self.theclass
2595 for offset, legit in ((-1440, False),
2596 (-1439, True),
2597 (1439, True),
2598 (1440, False)):
2599 if cls is time:
2600 t = cls(1, 2, 3, tzinfo=Edgy(offset))
2601 elif cls is datetime:
2602 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
2603 else:
2604 assert 0, "impossible"
2605 if legit:
2606 aofs = abs(offset)
2607 h, m = divmod(aofs, 60)
2608 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
2609 if isinstance(t, datetime):
2610 t = t.timetz()
2611 self.assertEqual(str(t), "01:02:03" + tag)
2612 else:
2613 self.assertRaises(ValueError, str, t)
2614
2615 def test_tzinfo_classes(self):
2616 cls = self.theclass
2617 class C1(tzinfo):
2618 def utcoffset(self, dt): return None
2619 def dst(self, dt): return None
2620 def tzname(self, dt): return None
2621 for t in (cls(1, 1, 1),
2622 cls(1, 1, 1, tzinfo=None),
2623 cls(1, 1, 1, tzinfo=C1())):
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002624 self.assertIsNone(t.utcoffset())
2625 self.assertIsNone(t.dst())
2626 self.assertIsNone(t.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002627
2628 class C3(tzinfo):
2629 def utcoffset(self, dt): return timedelta(minutes=-1439)
2630 def dst(self, dt): return timedelta(minutes=1439)
2631 def tzname(self, dt): return "aname"
2632 t = cls(1, 1, 1, tzinfo=C3())
2633 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
2634 self.assertEqual(t.dst(), timedelta(minutes=1439))
2635 self.assertEqual(t.tzname(), "aname")
2636
2637 # Wrong types.
2638 class C4(tzinfo):
2639 def utcoffset(self, dt): return "aname"
2640 def dst(self, dt): return 7
2641 def tzname(self, dt): return 0
2642 t = cls(1, 1, 1, tzinfo=C4())
2643 self.assertRaises(TypeError, t.utcoffset)
2644 self.assertRaises(TypeError, t.dst)
2645 self.assertRaises(TypeError, t.tzname)
2646
2647 # Offset out of range.
2648 class C6(tzinfo):
2649 def utcoffset(self, dt): return timedelta(hours=-24)
2650 def dst(self, dt): return timedelta(hours=24)
2651 t = cls(1, 1, 1, tzinfo=C6())
2652 self.assertRaises(ValueError, t.utcoffset)
2653 self.assertRaises(ValueError, t.dst)
2654
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002655 # Not a whole number of seconds.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002656 class C7(tzinfo):
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002657 def utcoffset(self, dt): return timedelta(microseconds=61)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002658 def dst(self, dt): return timedelta(microseconds=-81)
2659 t = cls(1, 1, 1, tzinfo=C7())
2660 self.assertRaises(ValueError, t.utcoffset)
2661 self.assertRaises(ValueError, t.dst)
2662
2663 def test_aware_compare(self):
2664 cls = self.theclass
2665
2666 # Ensure that utcoffset() gets ignored if the comparands have
2667 # the same tzinfo member.
2668 class OperandDependentOffset(tzinfo):
2669 def utcoffset(self, t):
2670 if t.minute < 10:
2671 # d0 and d1 equal after adjustment
2672 return timedelta(minutes=t.minute)
2673 else:
2674 # d2 off in the weeds
2675 return timedelta(minutes=59)
2676
2677 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
2678 d0 = base.replace(minute=3)
2679 d1 = base.replace(minute=9)
2680 d2 = base.replace(minute=11)
2681 for x in d0, d1, d2:
2682 for y in d0, d1, d2:
2683 for op in lt, le, gt, ge, eq, ne:
2684 got = op(x, y)
2685 expected = op(x.minute, y.minute)
2686 self.assertEqual(got, expected)
2687
2688 # However, if they're different members, uctoffset is not ignored.
2689 # Note that a time can't actually have an operand-depedent offset,
2690 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
2691 # so skip this test for time.
2692 if cls is not time:
2693 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2694 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2695 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2696 for x in d0, d1, d2:
2697 for y in d0, d1, d2:
2698 got = (x > y) - (x < y)
2699 if (x is d0 or x is d1) and (y is d0 or y is d1):
2700 expected = 0
2701 elif x is y is d2:
2702 expected = 0
2703 elif x is d2:
2704 expected = -1
2705 else:
2706 assert y is d2
2707 expected = 1
2708 self.assertEqual(got, expected)
2709
2710
2711# Testing time objects with a non-None tzinfo.
2712class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
2713 theclass = time
2714
2715 def test_empty(self):
2716 t = self.theclass()
2717 self.assertEqual(t.hour, 0)
2718 self.assertEqual(t.minute, 0)
2719 self.assertEqual(t.second, 0)
2720 self.assertEqual(t.microsecond, 0)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002721 self.assertIsNone(t.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002722
2723 def test_zones(self):
2724 est = FixedOffset(-300, "EST", 1)
2725 utc = FixedOffset(0, "UTC", -2)
2726 met = FixedOffset(60, "MET", 3)
2727 t1 = time( 7, 47, tzinfo=est)
2728 t2 = time(12, 47, tzinfo=utc)
2729 t3 = time(13, 47, tzinfo=met)
2730 t4 = time(microsecond=40)
2731 t5 = time(microsecond=40, tzinfo=utc)
2732
2733 self.assertEqual(t1.tzinfo, est)
2734 self.assertEqual(t2.tzinfo, utc)
2735 self.assertEqual(t3.tzinfo, met)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002736 self.assertIsNone(t4.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002737 self.assertEqual(t5.tzinfo, utc)
2738
2739 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2740 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2741 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002742 self.assertIsNone(t4.utcoffset())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002743 self.assertRaises(TypeError, t1.utcoffset, "no args")
2744
2745 self.assertEqual(t1.tzname(), "EST")
2746 self.assertEqual(t2.tzname(), "UTC")
2747 self.assertEqual(t3.tzname(), "MET")
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002748 self.assertIsNone(t4.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002749 self.assertRaises(TypeError, t1.tzname, "no args")
2750
2751 self.assertEqual(t1.dst(), timedelta(minutes=1))
2752 self.assertEqual(t2.dst(), timedelta(minutes=-2))
2753 self.assertEqual(t3.dst(), timedelta(minutes=3))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002754 self.assertIsNone(t4.dst())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002755 self.assertRaises(TypeError, t1.dst, "no args")
2756
2757 self.assertEqual(hash(t1), hash(t2))
2758 self.assertEqual(hash(t1), hash(t3))
2759 self.assertEqual(hash(t2), hash(t3))
2760
2761 self.assertEqual(t1, t2)
2762 self.assertEqual(t1, t3)
2763 self.assertEqual(t2, t3)
Alexander Belopolsky08313822012-06-15 20:19:47 -04002764 self.assertNotEqual(t4, t5) # mixed tz-aware & naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002765 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
2766 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
2767
2768 self.assertEqual(str(t1), "07:47:00-05:00")
2769 self.assertEqual(str(t2), "12:47:00+00:00")
2770 self.assertEqual(str(t3), "13:47:00+01:00")
2771 self.assertEqual(str(t4), "00:00:00.000040")
2772 self.assertEqual(str(t5), "00:00:00.000040+00:00")
2773
2774 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
2775 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
2776 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
2777 self.assertEqual(t4.isoformat(), "00:00:00.000040")
2778 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
2779
2780 d = 'datetime.time'
2781 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
2782 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
2783 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
2784 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
2785 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
2786
2787 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
2788 "07:47:00 %Z=EST %z=-0500")
2789 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
2790 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
2791
2792 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
2793 t1 = time(23, 59, tzinfo=yuck)
2794 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
2795 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
2796
2797 # Check that an invalid tzname result raises an exception.
2798 class Badtzname(tzinfo):
Alexander Belopolskye239d232010-12-08 23:31:48 +00002799 tz = 42
2800 def tzname(self, dt): return self.tz
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002801 t = time(2, 3, 4, tzinfo=Badtzname())
2802 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
2803 self.assertRaises(TypeError, t.strftime, "%Z")
2804
Alexander Belopolskye239d232010-12-08 23:31:48 +00002805 # Issue #6697:
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002806 if '_Fast' in str(self):
Alexander Belopolskye239d232010-12-08 23:31:48 +00002807 Badtzname.tz = '\ud800'
2808 self.assertRaises(ValueError, t.strftime, "%Z")
2809
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002810 def test_hash_edge_cases(self):
2811 # Offsets that overflow a basic time.
2812 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2813 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2814 self.assertEqual(hash(t1), hash(t2))
2815
2816 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2817 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2818 self.assertEqual(hash(t1), hash(t2))
2819
2820 def test_pickling(self):
2821 # Try one without a tzinfo.
2822 args = 20, 59, 16, 64**2
2823 orig = self.theclass(*args)
2824 for pickler, unpickler, proto in pickle_choices:
2825 green = pickler.dumps(orig, proto)
2826 derived = unpickler.loads(green)
2827 self.assertEqual(orig, derived)
2828
2829 # Try one with a tzinfo.
2830 tinfo = PicklableFixedOffset(-300, 'cookie')
2831 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
2832 for pickler, unpickler, proto in pickle_choices:
2833 green = pickler.dumps(orig, proto)
2834 derived = unpickler.loads(green)
2835 self.assertEqual(orig, derived)
2836 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
2837 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2838 self.assertEqual(derived.tzname(), 'cookie')
2839
2840 def test_more_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002841 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002842 cls = self.theclass
2843
2844 t = cls(0, tzinfo=FixedOffset(-300, ""))
2845 self.assertTrue(t)
2846
2847 t = cls(5, tzinfo=FixedOffset(-300, ""))
2848 self.assertTrue(t)
2849
2850 t = cls(5, tzinfo=FixedOffset(300, ""))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002851 self.assertTrue(t)
2852
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002853 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2854 self.assertTrue(t)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002855
2856 def test_replace(self):
2857 cls = self.theclass
2858 z100 = FixedOffset(100, "+100")
2859 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2860 args = [1, 2, 3, 4, z100]
2861 base = cls(*args)
2862 self.assertEqual(base, base.replace())
2863
2864 i = 0
2865 for name, newval in (("hour", 5),
2866 ("minute", 6),
2867 ("second", 7),
2868 ("microsecond", 8),
2869 ("tzinfo", zm200)):
2870 newargs = args[:]
2871 newargs[i] = newval
2872 expected = cls(*newargs)
2873 got = base.replace(**{name: newval})
2874 self.assertEqual(expected, got)
2875 i += 1
2876
2877 # Ensure we can get rid of a tzinfo.
2878 self.assertEqual(base.tzname(), "+100")
2879 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002880 self.assertIsNone(base2.tzinfo)
2881 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002882
2883 # Ensure we can add one.
2884 base3 = base2.replace(tzinfo=z100)
2885 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002886 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002887
2888 # Out of bounds.
2889 base = cls(1)
2890 self.assertRaises(ValueError, base.replace, hour=24)
2891 self.assertRaises(ValueError, base.replace, minute=-1)
2892 self.assertRaises(ValueError, base.replace, second=100)
2893 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2894
2895 def test_mixed_compare(self):
2896 t1 = time(1, 2, 3)
2897 t2 = time(1, 2, 3)
2898 self.assertEqual(t1, t2)
2899 t2 = t2.replace(tzinfo=None)
2900 self.assertEqual(t1, t2)
2901 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2902 self.assertEqual(t1, t2)
2903 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04002904 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002905
2906 # In time w/ identical tzinfo objects, utcoffset is ignored.
2907 class Varies(tzinfo):
2908 def __init__(self):
2909 self.offset = timedelta(minutes=22)
2910 def utcoffset(self, t):
2911 self.offset += timedelta(minutes=1)
2912 return self.offset
2913
2914 v = Varies()
2915 t1 = t2.replace(tzinfo=v)
2916 t2 = t2.replace(tzinfo=v)
2917 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2918 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2919 self.assertEqual(t1, t2)
2920
2921 # But if they're not identical, it isn't ignored.
2922 t2 = t2.replace(tzinfo=Varies())
2923 self.assertTrue(t1 < t2) # t1's offset counter still going up
2924
2925 def test_subclass_timetz(self):
2926
2927 class C(self.theclass):
2928 theAnswer = 42
2929
2930 def __new__(cls, *args, **kws):
2931 temp = kws.copy()
2932 extra = temp.pop('extra')
2933 result = self.theclass.__new__(cls, *args, **temp)
2934 result.extra = extra
2935 return result
2936
2937 def newmeth(self, start):
2938 return start + self.hour + self.second
2939
2940 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2941
2942 dt1 = self.theclass(*args)
2943 dt2 = C(*args, **{'extra': 7})
2944
2945 self.assertEqual(dt2.__class__, C)
2946 self.assertEqual(dt2.theAnswer, 42)
2947 self.assertEqual(dt2.extra, 7)
2948 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2949 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2950
2951
2952# Testing datetime objects with a non-None tzinfo.
2953
2954class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
2955 theclass = datetime
2956
2957 def test_trivial(self):
2958 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2959 self.assertEqual(dt.year, 1)
2960 self.assertEqual(dt.month, 2)
2961 self.assertEqual(dt.day, 3)
2962 self.assertEqual(dt.hour, 4)
2963 self.assertEqual(dt.minute, 5)
2964 self.assertEqual(dt.second, 6)
2965 self.assertEqual(dt.microsecond, 7)
2966 self.assertEqual(dt.tzinfo, None)
2967
2968 def test_even_more_compare(self):
2969 # The test_compare() and test_more_compare() inherited from TestDate
2970 # and TestDateTime covered non-tzinfo cases.
2971
2972 # Smallest possible after UTC adjustment.
2973 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2974 # Largest possible after UTC adjustment.
2975 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2976 tzinfo=FixedOffset(-1439, ""))
2977
2978 # Make sure those compare correctly, and w/o overflow.
2979 self.assertTrue(t1 < t2)
2980 self.assertTrue(t1 != t2)
2981 self.assertTrue(t2 > t1)
2982
2983 self.assertEqual(t1, t1)
2984 self.assertEqual(t2, t2)
2985
2986 # Equal afer adjustment.
2987 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2988 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2989 self.assertEqual(t1, t2)
2990
2991 # Change t1 not to subtract a minute, and t1 should be larger.
2992 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2993 self.assertTrue(t1 > t2)
2994
2995 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2996 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2997 self.assertTrue(t1 < t2)
2998
2999 # Back to the original t1, but make seconds resolve it.
3000 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3001 second=1)
3002 self.assertTrue(t1 > t2)
3003
3004 # Likewise, but make microseconds resolve it.
3005 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3006 microsecond=1)
3007 self.assertTrue(t1 > t2)
3008
Alexander Belopolsky08313822012-06-15 20:19:47 -04003009 # Make t2 naive and it should differ.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003010 t2 = self.theclass.min
Alexander Belopolsky08313822012-06-15 20:19:47 -04003011 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003012 self.assertEqual(t2, t2)
3013
3014 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
3015 class Naive(tzinfo):
3016 def utcoffset(self, dt): return None
3017 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
Alexander Belopolsky08313822012-06-15 20:19:47 -04003018 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003019 self.assertEqual(t2, t2)
3020
3021 # OTOH, it's OK to compare two of these mixing the two ways of being
3022 # naive.
3023 t1 = self.theclass(5, 6, 7)
3024 self.assertEqual(t1, t2)
3025
3026 # Try a bogus uctoffset.
3027 class Bogus(tzinfo):
3028 def utcoffset(self, dt):
3029 return timedelta(minutes=1440) # out of bounds
3030 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
3031 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
3032 self.assertRaises(ValueError, lambda: t1 == t2)
3033
3034 def test_pickling(self):
3035 # Try one without a tzinfo.
3036 args = 6, 7, 23, 20, 59, 1, 64**2
3037 orig = self.theclass(*args)
3038 for pickler, unpickler, proto in pickle_choices:
3039 green = pickler.dumps(orig, proto)
3040 derived = unpickler.loads(green)
3041 self.assertEqual(orig, derived)
3042
3043 # Try one with a tzinfo.
3044 tinfo = PicklableFixedOffset(-300, 'cookie')
3045 orig = self.theclass(*args, **{'tzinfo': tinfo})
3046 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
3047 for pickler, unpickler, proto in pickle_choices:
3048 green = pickler.dumps(orig, proto)
3049 derived = unpickler.loads(green)
3050 self.assertEqual(orig, derived)
3051 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3052 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3053 self.assertEqual(derived.tzname(), 'cookie')
3054
3055 def test_extreme_hashes(self):
3056 # If an attempt is made to hash these via subtracting the offset
3057 # then hashing a datetime object, OverflowError results. The
3058 # Python implementation used to blow up here.
3059 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
3060 hash(t)
3061 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3062 tzinfo=FixedOffset(-1439, ""))
3063 hash(t)
3064
3065 # OTOH, an OOB offset should blow up.
3066 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
3067 self.assertRaises(ValueError, hash, t)
3068
3069 def test_zones(self):
3070 est = FixedOffset(-300, "EST")
3071 utc = FixedOffset(0, "UTC")
3072 met = FixedOffset(60, "MET")
3073 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
3074 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
3075 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
3076 self.assertEqual(t1.tzinfo, est)
3077 self.assertEqual(t2.tzinfo, utc)
3078 self.assertEqual(t3.tzinfo, met)
3079 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
3080 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
3081 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
3082 self.assertEqual(t1.tzname(), "EST")
3083 self.assertEqual(t2.tzname(), "UTC")
3084 self.assertEqual(t3.tzname(), "MET")
3085 self.assertEqual(hash(t1), hash(t2))
3086 self.assertEqual(hash(t1), hash(t3))
3087 self.assertEqual(hash(t2), hash(t3))
3088 self.assertEqual(t1, t2)
3089 self.assertEqual(t1, t3)
3090 self.assertEqual(t2, t3)
3091 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
3092 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
3093 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
3094 d = 'datetime.datetime(2002, 3, 19, '
3095 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
3096 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
3097 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
3098
3099 def test_combine(self):
3100 met = FixedOffset(60, "MET")
3101 d = date(2002, 3, 4)
3102 tz = time(18, 45, 3, 1234, tzinfo=met)
3103 dt = datetime.combine(d, tz)
3104 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
3105 tzinfo=met))
3106
3107 def test_extract(self):
3108 met = FixedOffset(60, "MET")
3109 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
3110 self.assertEqual(dt.date(), date(2002, 3, 4))
3111 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
3112 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
3113
3114 def test_tz_aware_arithmetic(self):
3115 import random
3116
3117 now = self.theclass.now()
3118 tz55 = FixedOffset(-330, "west 5:30")
3119 timeaware = now.time().replace(tzinfo=tz55)
3120 nowaware = self.theclass.combine(now.date(), timeaware)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003121 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003122 self.assertEqual(nowaware.timetz(), timeaware)
3123
3124 # Can't mix aware and non-aware.
3125 self.assertRaises(TypeError, lambda: now - nowaware)
3126 self.assertRaises(TypeError, lambda: nowaware - now)
3127
3128 # And adding datetime's doesn't make sense, aware or not.
3129 self.assertRaises(TypeError, lambda: now + nowaware)
3130 self.assertRaises(TypeError, lambda: nowaware + now)
3131 self.assertRaises(TypeError, lambda: nowaware + nowaware)
3132
3133 # Subtracting should yield 0.
3134 self.assertEqual(now - now, timedelta(0))
3135 self.assertEqual(nowaware - nowaware, timedelta(0))
3136
3137 # Adding a delta should preserve tzinfo.
3138 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
3139 nowawareplus = nowaware + delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003140 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003141 nowawareplus2 = delta + nowaware
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003142 self.assertIs(nowawareplus2.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003143 self.assertEqual(nowawareplus, nowawareplus2)
3144
3145 # that - delta should be what we started with, and that - what we
3146 # started with should be delta.
3147 diff = nowawareplus - delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003148 self.assertIs(diff.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003149 self.assertEqual(nowaware, diff)
3150 self.assertRaises(TypeError, lambda: delta - nowawareplus)
3151 self.assertEqual(nowawareplus - nowaware, delta)
3152
3153 # Make up a random timezone.
3154 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
3155 # Attach it to nowawareplus.
3156 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003157 self.assertIs(nowawareplus.tzinfo, tzr)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003158 # Make sure the difference takes the timezone adjustments into account.
3159 got = nowaware - nowawareplus
3160 # Expected: (nowaware base - nowaware offset) -
3161 # (nowawareplus base - nowawareplus offset) =
3162 # (nowaware base - nowawareplus base) +
3163 # (nowawareplus offset - nowaware offset) =
3164 # -delta + nowawareplus offset - nowaware offset
3165 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
3166 self.assertEqual(got, expected)
3167
3168 # Try max possible difference.
3169 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
3170 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3171 tzinfo=FixedOffset(-1439, "max"))
3172 maxdiff = max - min
3173 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
3174 timedelta(minutes=2*1439))
3175 # Different tzinfo, but the same offset
3176 tza = timezone(HOUR, 'A')
3177 tzb = timezone(HOUR, 'B')
3178 delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
3179 self.assertEqual(delta, self.theclass.min - self.theclass.max)
3180
3181 def test_tzinfo_now(self):
3182 meth = self.theclass.now
3183 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3184 base = meth()
3185 # Try with and without naming the keyword.
3186 off42 = FixedOffset(42, "42")
3187 another = meth(off42)
3188 again = meth(tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003189 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003190 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3191 # Bad argument with and w/o naming the keyword.
3192 self.assertRaises(TypeError, meth, 16)
3193 self.assertRaises(TypeError, meth, tzinfo=16)
3194 # Bad keyword name.
3195 self.assertRaises(TypeError, meth, tinfo=off42)
3196 # Too many args.
3197 self.assertRaises(TypeError, meth, off42, off42)
3198
3199 # We don't know which time zone we're in, and don't have a tzinfo
3200 # class to represent it, so seeing whether a tz argument actually
3201 # does a conversion is tricky.
3202 utc = FixedOffset(0, "utc", 0)
3203 for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
3204 timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
3205 for dummy in range(3):
3206 now = datetime.now(weirdtz)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003207 self.assertIs(now.tzinfo, weirdtz)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003208 utcnow = datetime.utcnow().replace(tzinfo=utc)
3209 now2 = utcnow.astimezone(weirdtz)
3210 if abs(now - now2) < timedelta(seconds=30):
3211 break
3212 # Else the code is broken, or more than 30 seconds passed between
3213 # calls; assuming the latter, just try again.
3214 else:
3215 # Three strikes and we're out.
3216 self.fail("utcnow(), now(tz), or astimezone() may be broken")
3217
3218 def test_tzinfo_fromtimestamp(self):
3219 import time
3220 meth = self.theclass.fromtimestamp
3221 ts = time.time()
3222 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3223 base = meth(ts)
3224 # Try with and without naming the keyword.
3225 off42 = FixedOffset(42, "42")
3226 another = meth(ts, off42)
3227 again = meth(ts, tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003228 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003229 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3230 # Bad argument with and w/o naming the keyword.
3231 self.assertRaises(TypeError, meth, ts, 16)
3232 self.assertRaises(TypeError, meth, ts, tzinfo=16)
3233 # Bad keyword name.
3234 self.assertRaises(TypeError, meth, ts, tinfo=off42)
3235 # Too many args.
3236 self.assertRaises(TypeError, meth, ts, off42, off42)
3237 # Too few args.
3238 self.assertRaises(TypeError, meth)
3239
3240 # Try to make sure tz= actually does some conversion.
3241 timestamp = 1000000000
3242 utcdatetime = datetime.utcfromtimestamp(timestamp)
3243 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
3244 # But on some flavor of Mac, it's nowhere near that. So we can't have
3245 # any idea here what time that actually is, we can only test that
3246 # relative changes match.
3247 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
3248 tz = FixedOffset(utcoffset, "tz", 0)
3249 expected = utcdatetime + utcoffset
3250 got = datetime.fromtimestamp(timestamp, tz)
3251 self.assertEqual(expected, got.replace(tzinfo=None))
3252
3253 def test_tzinfo_utcnow(self):
3254 meth = self.theclass.utcnow
3255 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3256 base = meth()
3257 # Try with and without naming the keyword; for whatever reason,
3258 # utcnow() doesn't accept a tzinfo argument.
3259 off42 = FixedOffset(42, "42")
3260 self.assertRaises(TypeError, meth, off42)
3261 self.assertRaises(TypeError, meth, tzinfo=off42)
3262
3263 def test_tzinfo_utcfromtimestamp(self):
3264 import time
3265 meth = self.theclass.utcfromtimestamp
3266 ts = time.time()
3267 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3268 base = meth(ts)
3269 # Try with and without naming the keyword; for whatever reason,
3270 # utcfromtimestamp() doesn't accept a tzinfo argument.
3271 off42 = FixedOffset(42, "42")
3272 self.assertRaises(TypeError, meth, ts, off42)
3273 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
3274
3275 def test_tzinfo_timetuple(self):
3276 # TestDateTime tested most of this. datetime adds a twist to the
3277 # DST flag.
3278 class DST(tzinfo):
3279 def __init__(self, dstvalue):
3280 if isinstance(dstvalue, int):
3281 dstvalue = timedelta(minutes=dstvalue)
3282 self.dstvalue = dstvalue
3283 def dst(self, dt):
3284 return self.dstvalue
3285
3286 cls = self.theclass
3287 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
3288 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
3289 t = d.timetuple()
3290 self.assertEqual(1, t.tm_year)
3291 self.assertEqual(1, t.tm_mon)
3292 self.assertEqual(1, t.tm_mday)
3293 self.assertEqual(10, t.tm_hour)
3294 self.assertEqual(20, t.tm_min)
3295 self.assertEqual(30, t.tm_sec)
3296 self.assertEqual(0, t.tm_wday)
3297 self.assertEqual(1, t.tm_yday)
3298 self.assertEqual(flag, t.tm_isdst)
3299
3300 # dst() returns wrong type.
3301 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
3302
3303 # dst() at the edge.
3304 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
3305 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
3306
3307 # dst() out of range.
3308 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
3309 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
3310
3311 def test_utctimetuple(self):
3312 class DST(tzinfo):
3313 def __init__(self, dstvalue=0):
3314 if isinstance(dstvalue, int):
3315 dstvalue = timedelta(minutes=dstvalue)
3316 self.dstvalue = dstvalue
3317 def dst(self, dt):
3318 return self.dstvalue
3319
3320 cls = self.theclass
3321 # This can't work: DST didn't implement utcoffset.
3322 self.assertRaises(NotImplementedError,
3323 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
3324
3325 class UOFS(DST):
3326 def __init__(self, uofs, dofs=None):
3327 DST.__init__(self, dofs)
3328 self.uofs = timedelta(minutes=uofs)
3329 def utcoffset(self, dt):
3330 return self.uofs
3331
3332 for dstvalue in -33, 33, 0, None:
3333 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
3334 t = d.utctimetuple()
3335 self.assertEqual(d.year, t.tm_year)
3336 self.assertEqual(d.month, t.tm_mon)
3337 self.assertEqual(d.day, t.tm_mday)
3338 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
3339 self.assertEqual(13, t.tm_min)
3340 self.assertEqual(d.second, t.tm_sec)
3341 self.assertEqual(d.weekday(), t.tm_wday)
3342 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
3343 t.tm_yday)
3344 # Ensure tm_isdst is 0 regardless of what dst() says: DST
3345 # is never in effect for a UTC time.
3346 self.assertEqual(0, t.tm_isdst)
3347
3348 # For naive datetime, utctimetuple == timetuple except for isdst
3349 d = cls(1, 2, 3, 10, 20, 30, 40)
3350 t = d.utctimetuple()
3351 self.assertEqual(t[:-1], d.timetuple()[:-1])
3352 self.assertEqual(0, t.tm_isdst)
3353 # Same if utcoffset is None
3354 class NOFS(DST):
3355 def utcoffset(self, dt):
3356 return None
3357 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
3358 t = d.utctimetuple()
3359 self.assertEqual(t[:-1], d.timetuple()[:-1])
3360 self.assertEqual(0, t.tm_isdst)
3361 # Check that bad tzinfo is detected
3362 class BOFS(DST):
3363 def utcoffset(self, dt):
3364 return "EST"
3365 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
3366 self.assertRaises(TypeError, d.utctimetuple)
3367
3368 # Check that utctimetuple() is the same as
3369 # astimezone(utc).timetuple()
3370 d = cls(2010, 11, 13, 14, 15, 16, 171819)
3371 for tz in [timezone.min, timezone.utc, timezone.max]:
3372 dtz = d.replace(tzinfo=tz)
3373 self.assertEqual(dtz.utctimetuple()[:-1],
3374 dtz.astimezone(timezone.utc).timetuple()[:-1])
3375 # At the edges, UTC adjustment can produce years out-of-range
3376 # for a datetime object. Ensure that an OverflowError is
3377 # raised.
3378 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
3379 # That goes back 1 minute less than a full day.
3380 self.assertRaises(OverflowError, tiny.utctimetuple)
3381
3382 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
3383 # That goes forward 1 minute less than a full day.
3384 self.assertRaises(OverflowError, huge.utctimetuple)
3385 # More overflow cases
3386 tiny = cls.min.replace(tzinfo=timezone(MINUTE))
3387 self.assertRaises(OverflowError, tiny.utctimetuple)
3388 huge = cls.max.replace(tzinfo=timezone(-MINUTE))
3389 self.assertRaises(OverflowError, huge.utctimetuple)
3390
3391 def test_tzinfo_isoformat(self):
3392 zero = FixedOffset(0, "+00:00")
3393 plus = FixedOffset(220, "+03:40")
3394 minus = FixedOffset(-231, "-03:51")
3395 unknown = FixedOffset(None, "")
3396
3397 cls = self.theclass
3398 datestr = '0001-02-03'
3399 for ofs in None, zero, plus, minus, unknown:
3400 for us in 0, 987001:
3401 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
3402 timestr = '04:05:59' + (us and '.987001' or '')
3403 ofsstr = ofs is not None and d.tzname() or ''
3404 tailstr = timestr + ofsstr
3405 iso = d.isoformat()
3406 self.assertEqual(iso, datestr + 'T' + tailstr)
3407 self.assertEqual(iso, d.isoformat('T'))
3408 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
3409 self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
3410 self.assertEqual(str(d), datestr + ' ' + tailstr)
3411
3412 def test_replace(self):
3413 cls = self.theclass
3414 z100 = FixedOffset(100, "+100")
3415 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3416 args = [1, 2, 3, 4, 5, 6, 7, z100]
3417 base = cls(*args)
3418 self.assertEqual(base, base.replace())
3419
3420 i = 0
3421 for name, newval in (("year", 2),
3422 ("month", 3),
3423 ("day", 4),
3424 ("hour", 5),
3425 ("minute", 6),
3426 ("second", 7),
3427 ("microsecond", 8),
3428 ("tzinfo", zm200)):
3429 newargs = args[:]
3430 newargs[i] = newval
3431 expected = cls(*newargs)
3432 got = base.replace(**{name: newval})
3433 self.assertEqual(expected, got)
3434 i += 1
3435
3436 # Ensure we can get rid of a tzinfo.
3437 self.assertEqual(base.tzname(), "+100")
3438 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003439 self.assertIsNone(base2.tzinfo)
3440 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003441
3442 # Ensure we can add one.
3443 base3 = base2.replace(tzinfo=z100)
3444 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003445 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003446
3447 # Out of bounds.
3448 base = cls(2000, 2, 29)
3449 self.assertRaises(ValueError, base.replace, year=2001)
3450
3451 def test_more_astimezone(self):
3452 # The inherited test_astimezone covered some trivial and error cases.
3453 fnone = FixedOffset(None, "None")
3454 f44m = FixedOffset(44, "44")
3455 fm5h = FixedOffset(-timedelta(hours=5), "m300")
3456
3457 dt = self.theclass.now(tz=f44m)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003458 self.assertIs(dt.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003459 # Replacing with degenerate tzinfo raises an exception.
3460 self.assertRaises(ValueError, dt.astimezone, fnone)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003461 # Replacing with same tzinfo makes no change.
3462 x = dt.astimezone(dt.tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003463 self.assertIs(x.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003464 self.assertEqual(x.date(), dt.date())
3465 self.assertEqual(x.time(), dt.time())
3466
3467 # Replacing with different tzinfo does adjust.
3468 got = dt.astimezone(fm5h)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003469 self.assertIs(got.tzinfo, fm5h)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003470 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
3471 expected = dt - dt.utcoffset() # in effect, convert to UTC
3472 expected += fm5h.utcoffset(dt) # and from there to local time
3473 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
3474 self.assertEqual(got.date(), expected.date())
3475 self.assertEqual(got.time(), expected.time())
3476 self.assertEqual(got.timetz(), expected.timetz())
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003477 self.assertIs(got.tzinfo, expected.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003478 self.assertEqual(got, expected)
3479
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003480 @support.run_with_tz('UTC')
3481 def test_astimezone_default_utc(self):
3482 dt = self.theclass.now(timezone.utc)
3483 self.assertEqual(dt.astimezone(None), dt)
3484 self.assertEqual(dt.astimezone(), dt)
3485
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003486 # Note that offset in TZ variable has the opposite sign to that
3487 # produced by %z directive.
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003488 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
3489 def test_astimezone_default_eastern(self):
3490 dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
3491 local = dt.astimezone()
3492 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003493 self.assertEqual(local.strftime("%z %Z"), "-0500 EST")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003494 dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc)
3495 local = dt.astimezone()
3496 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003497 self.assertEqual(local.strftime("%z %Z"), "-0400 EDT")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003498
Alexander Belopolsky1dcf4f92016-03-25 15:42:59 -04003499 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
3500 def test_astimezone_default_near_fold(self):
3501 # Issue #26616.
3502 u = datetime(2015, 11, 1, 5, tzinfo=timezone.utc)
3503 t = u.astimezone()
3504 s = t.astimezone()
3505 self.assertEqual(t.tzinfo, s.tzinfo)
3506
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003507 def test_aware_subtract(self):
3508 cls = self.theclass
3509
3510 # Ensure that utcoffset() is ignored when the operands have the
3511 # same tzinfo member.
3512 class OperandDependentOffset(tzinfo):
3513 def utcoffset(self, t):
3514 if t.minute < 10:
3515 # d0 and d1 equal after adjustment
3516 return timedelta(minutes=t.minute)
3517 else:
3518 # d2 off in the weeds
3519 return timedelta(minutes=59)
3520
3521 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
3522 d0 = base.replace(minute=3)
3523 d1 = base.replace(minute=9)
3524 d2 = base.replace(minute=11)
3525 for x in d0, d1, d2:
3526 for y in d0, d1, d2:
3527 got = x - y
3528 expected = timedelta(minutes=x.minute - y.minute)
3529 self.assertEqual(got, expected)
3530
3531 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
3532 # ignored.
3533 base = cls(8, 9, 10, 11, 12, 13, 14)
3534 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
3535 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
3536 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
3537 for x in d0, d1, d2:
3538 for y in d0, d1, d2:
3539 got = x - y
3540 if (x is d0 or x is d1) and (y is d0 or y is d1):
3541 expected = timedelta(0)
3542 elif x is y is d2:
3543 expected = timedelta(0)
3544 elif x is d2:
3545 expected = timedelta(minutes=(11-59)-0)
3546 else:
3547 assert y is d2
3548 expected = timedelta(minutes=0-(11-59))
3549 self.assertEqual(got, expected)
3550
3551 def test_mixed_compare(self):
3552 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
3553 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
3554 self.assertEqual(t1, t2)
3555 t2 = t2.replace(tzinfo=None)
3556 self.assertEqual(t1, t2)
3557 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3558 self.assertEqual(t1, t2)
3559 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04003560 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003561
3562 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
3563 class Varies(tzinfo):
3564 def __init__(self):
3565 self.offset = timedelta(minutes=22)
3566 def utcoffset(self, t):
3567 self.offset += timedelta(minutes=1)
3568 return self.offset
3569
3570 v = Varies()
3571 t1 = t2.replace(tzinfo=v)
3572 t2 = t2.replace(tzinfo=v)
3573 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3574 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3575 self.assertEqual(t1, t2)
3576
3577 # But if they're not identical, it isn't ignored.
3578 t2 = t2.replace(tzinfo=Varies())
3579 self.assertTrue(t1 < t2) # t1's offset counter still going up
3580
3581 def test_subclass_datetimetz(self):
3582
3583 class C(self.theclass):
3584 theAnswer = 42
3585
3586 def __new__(cls, *args, **kws):
3587 temp = kws.copy()
3588 extra = temp.pop('extra')
3589 result = self.theclass.__new__(cls, *args, **temp)
3590 result.extra = extra
3591 return result
3592
3593 def newmeth(self, start):
3594 return start + self.hour + self.year
3595
3596 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3597
3598 dt1 = self.theclass(*args)
3599 dt2 = C(*args, **{'extra': 7})
3600
3601 self.assertEqual(dt2.__class__, C)
3602 self.assertEqual(dt2.theAnswer, 42)
3603 self.assertEqual(dt2.extra, 7)
3604 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3605 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
3606
3607# Pain to set up DST-aware tzinfo classes.
3608
3609def first_sunday_on_or_after(dt):
3610 days_to_go = 6 - dt.weekday()
3611 if days_to_go:
3612 dt += timedelta(days_to_go)
3613 return dt
3614
3615ZERO = timedelta(0)
3616MINUTE = timedelta(minutes=1)
3617HOUR = timedelta(hours=1)
3618DAY = timedelta(days=1)
3619# In the US, DST starts at 2am (standard time) on the first Sunday in April.
3620DSTSTART = datetime(1, 4, 1, 2)
3621# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
3622# which is the first Sunday on or after Oct 25. Because we view 1:MM as
3623# being standard time on that day, there is no spelling in local time of
3624# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
3625DSTEND = datetime(1, 10, 25, 1)
3626
3627class USTimeZone(tzinfo):
3628
3629 def __init__(self, hours, reprname, stdname, dstname):
3630 self.stdoffset = timedelta(hours=hours)
3631 self.reprname = reprname
3632 self.stdname = stdname
3633 self.dstname = dstname
3634
3635 def __repr__(self):
3636 return self.reprname
3637
3638 def tzname(self, dt):
3639 if self.dst(dt):
3640 return self.dstname
3641 else:
3642 return self.stdname
3643
3644 def utcoffset(self, dt):
3645 return self.stdoffset + self.dst(dt)
3646
3647 def dst(self, dt):
3648 if dt is None or dt.tzinfo is None:
3649 # An exception instead may be sensible here, in one or more of
3650 # the cases.
3651 return ZERO
3652 assert dt.tzinfo is self
3653
3654 # Find first Sunday in April.
3655 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
3656 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
3657
3658 # Find last Sunday in October.
3659 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
3660 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
3661
3662 # Can't compare naive to aware objects, so strip the timezone from
3663 # dt first.
3664 if start <= dt.replace(tzinfo=None) < end:
3665 return HOUR
3666 else:
3667 return ZERO
3668
3669Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
3670Central = USTimeZone(-6, "Central", "CST", "CDT")
3671Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
3672Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
3673utc_real = FixedOffset(0, "UTC", 0)
3674# For better test coverage, we want another flavor of UTC that's west of
3675# the Eastern and Pacific timezones.
3676utc_fake = FixedOffset(-12*60, "UTCfake", 0)
3677
3678class TestTimezoneConversions(unittest.TestCase):
3679 # The DST switch times for 2002, in std time.
3680 dston = datetime(2002, 4, 7, 2)
3681 dstoff = datetime(2002, 10, 27, 1)
3682
3683 theclass = datetime
3684
3685 # Check a time that's inside DST.
3686 def checkinside(self, dt, tz, utc, dston, dstoff):
3687 self.assertEqual(dt.dst(), HOUR)
3688
3689 # Conversion to our own timezone is always an identity.
3690 self.assertEqual(dt.astimezone(tz), dt)
3691
3692 asutc = dt.astimezone(utc)
3693 there_and_back = asutc.astimezone(tz)
3694
3695 # Conversion to UTC and back isn't always an identity here,
3696 # because there are redundant spellings (in local time) of
3697 # UTC time when DST begins: the clock jumps from 1:59:59
3698 # to 3:00:00, and a local time of 2:MM:SS doesn't really
3699 # make sense then. The classes above treat 2:MM:SS as
3700 # daylight time then (it's "after 2am"), really an alias
3701 # for 1:MM:SS standard time. The latter form is what
3702 # conversion back from UTC produces.
3703 if dt.date() == dston.date() and dt.hour == 2:
3704 # We're in the redundant hour, and coming back from
3705 # UTC gives the 1:MM:SS standard-time spelling.
3706 self.assertEqual(there_and_back + HOUR, dt)
3707 # Although during was considered to be in daylight
3708 # time, there_and_back is not.
3709 self.assertEqual(there_and_back.dst(), ZERO)
3710 # They're the same times in UTC.
3711 self.assertEqual(there_and_back.astimezone(utc),
3712 dt.astimezone(utc))
3713 else:
3714 # We're not in the redundant hour.
3715 self.assertEqual(dt, there_and_back)
3716
3717 # Because we have a redundant spelling when DST begins, there is
Ezio Melotti3b3499b2011-03-16 11:35:38 +02003718 # (unfortunately) an hour when DST ends that can't be spelled at all in
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003719 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
3720 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
3721 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
3722 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
3723 # expressed in local time. Nevertheless, we want conversion back
3724 # from UTC to mimic the local clock's "repeat an hour" behavior.
3725 nexthour_utc = asutc + HOUR
3726 nexthour_tz = nexthour_utc.astimezone(tz)
3727 if dt.date() == dstoff.date() and dt.hour == 0:
3728 # We're in the hour before the last DST hour. The last DST hour
3729 # is ineffable. We want the conversion back to repeat 1:MM.
3730 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3731 nexthour_utc += HOUR
3732 nexthour_tz = nexthour_utc.astimezone(tz)
3733 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3734 else:
3735 self.assertEqual(nexthour_tz - dt, HOUR)
3736
3737 # Check a time that's outside DST.
3738 def checkoutside(self, dt, tz, utc):
3739 self.assertEqual(dt.dst(), ZERO)
3740
3741 # Conversion to our own timezone is always an identity.
3742 self.assertEqual(dt.astimezone(tz), dt)
3743
3744 # Converting to UTC and back is an identity too.
3745 asutc = dt.astimezone(utc)
3746 there_and_back = asutc.astimezone(tz)
3747 self.assertEqual(dt, there_and_back)
3748
3749 def convert_between_tz_and_utc(self, tz, utc):
3750 dston = self.dston.replace(tzinfo=tz)
3751 # Because 1:MM on the day DST ends is taken as being standard time,
3752 # there is no spelling in tz for the last hour of daylight time.
3753 # For purposes of the test, the last hour of DST is 0:MM, which is
3754 # taken as being daylight time (and 1:MM is taken as being standard
3755 # time).
3756 dstoff = self.dstoff.replace(tzinfo=tz)
3757 for delta in (timedelta(weeks=13),
3758 DAY,
3759 HOUR,
3760 timedelta(minutes=1),
3761 timedelta(microseconds=1)):
3762
3763 self.checkinside(dston, tz, utc, dston, dstoff)
3764 for during in dston + delta, dstoff - delta:
3765 self.checkinside(during, tz, utc, dston, dstoff)
3766
3767 self.checkoutside(dstoff, tz, utc)
3768 for outside in dston - delta, dstoff + delta:
3769 self.checkoutside(outside, tz, utc)
3770
3771 def test_easy(self):
3772 # Despite the name of this test, the endcases are excruciating.
3773 self.convert_between_tz_and_utc(Eastern, utc_real)
3774 self.convert_between_tz_and_utc(Pacific, utc_real)
3775 self.convert_between_tz_and_utc(Eastern, utc_fake)
3776 self.convert_between_tz_and_utc(Pacific, utc_fake)
3777 # The next is really dancing near the edge. It works because
3778 # Pacific and Eastern are far enough apart that their "problem
3779 # hours" don't overlap.
3780 self.convert_between_tz_and_utc(Eastern, Pacific)
3781 self.convert_between_tz_and_utc(Pacific, Eastern)
3782 # OTOH, these fail! Don't enable them. The difficulty is that
3783 # the edge case tests assume that every hour is representable in
3784 # the "utc" class. This is always true for a fixed-offset tzinfo
3785 # class (lke utc_real and utc_fake), but not for Eastern or Central.
3786 # For these adjacent DST-aware time zones, the range of time offsets
3787 # tested ends up creating hours in the one that aren't representable
3788 # in the other. For the same reason, we would see failures in the
3789 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
3790 # offset deltas in convert_between_tz_and_utc().
3791 #
3792 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
3793 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
3794
3795 def test_tricky(self):
3796 # 22:00 on day before daylight starts.
3797 fourback = self.dston - timedelta(hours=4)
3798 ninewest = FixedOffset(-9*60, "-0900", 0)
3799 fourback = fourback.replace(tzinfo=ninewest)
3800 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
3801 # 2", we should get the 3 spelling.
3802 # If we plug 22:00 the day before into Eastern, it "looks like std
3803 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
3804 # to 22:00 lands on 2:00, which makes no sense in local time (the
3805 # local clock jumps from 1 to 3). The point here is to make sure we
3806 # get the 3 spelling.
3807 expected = self.dston.replace(hour=3)
3808 got = fourback.astimezone(Eastern).replace(tzinfo=None)
3809 self.assertEqual(expected, got)
3810
3811 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
3812 # case we want the 1:00 spelling.
3813 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
3814 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
3815 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
3816 # spelling.
3817 expected = self.dston.replace(hour=1)
3818 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
3819 self.assertEqual(expected, got)
3820
3821 # Now on the day DST ends, we want "repeat an hour" behavior.
3822 # UTC 4:MM 5:MM 6:MM 7:MM checking these
3823 # EST 23:MM 0:MM 1:MM 2:MM
3824 # EDT 0:MM 1:MM 2:MM 3:MM
3825 # wall 0:MM 1:MM 1:MM 2:MM against these
3826 for utc in utc_real, utc_fake:
3827 for tz in Eastern, Pacific:
3828 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
3829 # Convert that to UTC.
3830 first_std_hour -= tz.utcoffset(None)
3831 # Adjust for possibly fake UTC.
3832 asutc = first_std_hour + utc.utcoffset(None)
3833 # First UTC hour to convert; this is 4:00 when utc=utc_real &
3834 # tz=Eastern.
3835 asutcbase = asutc.replace(tzinfo=utc)
3836 for tzhour in (0, 1, 1, 2):
3837 expectedbase = self.dstoff.replace(hour=tzhour)
3838 for minute in 0, 30, 59:
3839 expected = expectedbase.replace(minute=minute)
3840 asutc = asutcbase.replace(minute=minute)
3841 astz = asutc.astimezone(tz)
3842 self.assertEqual(astz.replace(tzinfo=None), expected)
3843 asutcbase += HOUR
3844
3845
3846 def test_bogus_dst(self):
3847 class ok(tzinfo):
3848 def utcoffset(self, dt): return HOUR
3849 def dst(self, dt): return HOUR
3850
3851 now = self.theclass.now().replace(tzinfo=utc_real)
3852 # Doesn't blow up.
3853 now.astimezone(ok())
3854
3855 # Does blow up.
3856 class notok(ok):
3857 def dst(self, dt): return None
3858 self.assertRaises(ValueError, now.astimezone, notok())
3859
3860 # Sometimes blow up. In the following, tzinfo.dst()
3861 # implementation may return None or not None depending on
3862 # whether DST is assumed to be in effect. In this situation,
3863 # a ValueError should be raised by astimezone().
3864 class tricky_notok(ok):
3865 def dst(self, dt):
3866 if dt.year == 2000:
3867 return None
3868 else:
3869 return 10*HOUR
3870 dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
3871 self.assertRaises(ValueError, dt.astimezone, tricky_notok())
3872
3873 def test_fromutc(self):
3874 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
3875 now = datetime.utcnow().replace(tzinfo=utc_real)
3876 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3877 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
3878 enow = Eastern.fromutc(now) # doesn't blow up
3879 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
3880 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
3881 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
3882
3883 # Always converts UTC to standard time.
3884 class FauxUSTimeZone(USTimeZone):
3885 def fromutc(self, dt):
3886 return dt + self.stdoffset
3887 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
3888
3889 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
3890 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
3891 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
3892
3893 # Check around DST start.
3894 start = self.dston.replace(hour=4, tzinfo=Eastern)
3895 fstart = start.replace(tzinfo=FEastern)
3896 for wall in 23, 0, 1, 3, 4, 5:
3897 expected = start.replace(hour=wall)
3898 if wall == 23:
3899 expected -= timedelta(days=1)
3900 got = Eastern.fromutc(start)
3901 self.assertEqual(expected, got)
3902
3903 expected = fstart + FEastern.stdoffset
3904 got = FEastern.fromutc(fstart)
3905 self.assertEqual(expected, got)
3906
3907 # Ensure astimezone() calls fromutc() too.
3908 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3909 self.assertEqual(expected, got)
3910
3911 start += HOUR
3912 fstart += HOUR
3913
3914 # Check around DST end.
3915 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
3916 fstart = start.replace(tzinfo=FEastern)
3917 for wall in 0, 1, 1, 2, 3, 4:
3918 expected = start.replace(hour=wall)
3919 got = Eastern.fromutc(start)
3920 self.assertEqual(expected, got)
3921
3922 expected = fstart + FEastern.stdoffset
3923 got = FEastern.fromutc(fstart)
3924 self.assertEqual(expected, got)
3925
3926 # Ensure astimezone() calls fromutc() too.
3927 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3928 self.assertEqual(expected, got)
3929
3930 start += HOUR
3931 fstart += HOUR
3932
3933
3934#############################################################################
3935# oddballs
3936
3937class Oddballs(unittest.TestCase):
3938
3939 def test_bug_1028306(self):
3940 # Trying to compare a date to a datetime should act like a mixed-
3941 # type comparison, despite that datetime is a subclass of date.
3942 as_date = date.today()
3943 as_datetime = datetime.combine(as_date, time())
3944 self.assertTrue(as_date != as_datetime)
3945 self.assertTrue(as_datetime != as_date)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003946 self.assertFalse(as_date == as_datetime)
3947 self.assertFalse(as_datetime == as_date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003948 self.assertRaises(TypeError, lambda: as_date < as_datetime)
3949 self.assertRaises(TypeError, lambda: as_datetime < as_date)
3950 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
3951 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
3952 self.assertRaises(TypeError, lambda: as_date > as_datetime)
3953 self.assertRaises(TypeError, lambda: as_datetime > as_date)
3954 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
3955 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
3956
3957 # Neverthelss, comparison should work with the base-class (date)
3958 # projection if use of a date method is forced.
3959 self.assertEqual(as_date.__eq__(as_datetime), True)
3960 different_day = (as_date.day + 1) % 20 + 1
3961 as_different = as_datetime.replace(day= different_day)
3962 self.assertEqual(as_date.__eq__(as_different), False)
3963
3964 # And date should compare with other subclasses of date. If a
3965 # subclass wants to stop this, it's up to the subclass to do so.
3966 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
3967 self.assertEqual(as_date, date_sc)
3968 self.assertEqual(date_sc, as_date)
3969
3970 # Ditto for datetimes.
3971 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
3972 as_date.day, 0, 0, 0)
3973 self.assertEqual(as_datetime, datetime_sc)
3974 self.assertEqual(datetime_sc, as_datetime)
3975
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04003976 def test_extra_attributes(self):
3977 for x in [date.today(),
3978 time(),
3979 datetime.utcnow(),
3980 timedelta(),
3981 tzinfo(),
3982 timezone(timedelta())]:
3983 with self.assertRaises(AttributeError):
3984 x.abc = 1
3985
3986 def test_check_arg_types(self):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04003987 class Number:
3988 def __init__(self, value):
3989 self.value = value
3990 def __int__(self):
3991 return self.value
3992
3993 for xx in [decimal.Decimal(10),
3994 decimal.Decimal('10.9'),
3995 Number(10)]:
3996 self.assertEqual(datetime(10, 10, 10, 10, 10, 10, 10),
3997 datetime(xx, xx, xx, xx, xx, xx, xx))
3998
3999 with self.assertRaisesRegex(TypeError, '^an integer is required '
4000 '\(got type str\)$'):
4001 datetime(10, 10, '10')
4002
4003 f10 = Number(10.9)
4004 with self.assertRaisesRegex(TypeError, '^__int__ returned non-int '
4005 '\(type float\)$'):
4006 datetime(10, 10, f10)
4007
4008 class Float(float):
4009 pass
4010 s10 = Float(10.9)
4011 with self.assertRaisesRegex(TypeError, '^integer argument expected, '
4012 'got float$'):
4013 datetime(10, 10, s10)
4014
4015 with self.assertRaises(TypeError):
4016 datetime(10., 10, 10)
4017 with self.assertRaises(TypeError):
4018 datetime(10, 10., 10)
4019 with self.assertRaises(TypeError):
4020 datetime(10, 10, 10.)
4021 with self.assertRaises(TypeError):
4022 datetime(10, 10, 10, 10.)
4023 with self.assertRaises(TypeError):
4024 datetime(10, 10, 10, 10, 10.)
4025 with self.assertRaises(TypeError):
4026 datetime(10, 10, 10, 10, 10, 10.)
4027 with self.assertRaises(TypeError):
4028 datetime(10, 10, 10, 10, 10, 10, 10.)
4029
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004030#############################################################################
4031# Local Time Disambiguation
4032
4033# An experimental reimplementation of fromutc that respects the "fold" flag.
4034
4035class tzinfo2(tzinfo):
4036
4037 def fromutc(self, dt):
4038 "datetime in UTC -> datetime in local time."
4039
4040 if not isinstance(dt, datetime):
4041 raise TypeError("fromutc() requires a datetime argument")
4042 if dt.tzinfo is not self:
4043 raise ValueError("dt.tzinfo is not self")
4044 # Returned value satisfies
4045 # dt + ldt.utcoffset() = ldt
4046 off0 = dt.replace(fold=0).utcoffset()
4047 off1 = dt.replace(fold=1).utcoffset()
4048 if off0 is None or off1 is None or dt.dst() is None:
4049 raise ValueError
4050 if off0 == off1:
4051 ldt = dt + off0
4052 off1 = ldt.utcoffset()
4053 if off0 == off1:
4054 return ldt
4055 # Now, we discovered both possible offsets, so
4056 # we can just try four possible solutions:
4057 for off in [off0, off1]:
4058 ldt = dt + off
4059 if ldt.utcoffset() == off:
4060 return ldt
4061 ldt = ldt.replace(fold=1)
4062 if ldt.utcoffset() == off:
4063 return ldt
4064
4065 raise ValueError("No suitable local time found")
4066
4067# Reimplementing simplified US timezones to respect the "fold" flag:
4068
4069class USTimeZone2(tzinfo2):
4070
4071 def __init__(self, hours, reprname, stdname, dstname):
4072 self.stdoffset = timedelta(hours=hours)
4073 self.reprname = reprname
4074 self.stdname = stdname
4075 self.dstname = dstname
4076
4077 def __repr__(self):
4078 return self.reprname
4079
4080 def tzname(self, dt):
4081 if self.dst(dt):
4082 return self.dstname
4083 else:
4084 return self.stdname
4085
4086 def utcoffset(self, dt):
4087 return self.stdoffset + self.dst(dt)
4088
4089 def dst(self, dt):
4090 if dt is None or dt.tzinfo is None:
4091 # An exception instead may be sensible here, in one or more of
4092 # the cases.
4093 return ZERO
4094 assert dt.tzinfo is self
4095
4096 # Find first Sunday in April.
4097 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
4098 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
4099
4100 # Find last Sunday in October.
4101 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
4102 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
4103
4104 # Can't compare naive to aware objects, so strip the timezone from
4105 # dt first.
4106 dt = dt.replace(tzinfo=None)
4107 if start + HOUR <= dt < end:
4108 # DST is in effect.
4109 return HOUR
4110 elif end <= dt < end + HOUR:
4111 # Fold (an ambiguous hour): use dt.fold to disambiguate.
4112 return ZERO if dt.fold else HOUR
4113 elif start <= dt < start + HOUR:
4114 # Gap (a non-existent hour): reverse the fold rule.
4115 return HOUR if dt.fold else ZERO
4116 else:
4117 # DST is off.
4118 return ZERO
4119
4120Eastern2 = USTimeZone2(-5, "Eastern2", "EST", "EDT")
4121Central2 = USTimeZone2(-6, "Central2", "CST", "CDT")
4122Mountain2 = USTimeZone2(-7, "Mountain2", "MST", "MDT")
4123Pacific2 = USTimeZone2(-8, "Pacific2", "PST", "PDT")
4124
4125# Europe_Vilnius_1941 tzinfo implementation reproduces the following
4126# 1941 transition from Olson's tzdist:
4127#
4128# Zone NAME GMTOFF RULES FORMAT [UNTIL]
4129# ZoneEurope/Vilnius 1:00 - CET 1940 Aug 3
4130# 3:00 - MSK 1941 Jun 24
4131# 1:00 C-Eur CE%sT 1944 Aug
4132#
4133# $ zdump -v Europe/Vilnius | grep 1941
4134# Europe/Vilnius Mon Jun 23 20:59:59 1941 UTC = Mon Jun 23 23:59:59 1941 MSK isdst=0 gmtoff=10800
4135# Europe/Vilnius Mon Jun 23 21:00:00 1941 UTC = Mon Jun 23 23:00:00 1941 CEST isdst=1 gmtoff=7200
4136
4137class Europe_Vilnius_1941(tzinfo):
4138 def _utc_fold(self):
4139 return [datetime(1941, 6, 23, 21, tzinfo=self), # Mon Jun 23 21:00:00 1941 UTC
4140 datetime(1941, 6, 23, 22, tzinfo=self)] # Mon Jun 23 22:00:00 1941 UTC
4141
4142 def _loc_fold(self):
4143 return [datetime(1941, 6, 23, 23, tzinfo=self), # Mon Jun 23 23:00:00 1941 MSK / CEST
4144 datetime(1941, 6, 24, 0, tzinfo=self)] # Mon Jun 24 00:00:00 1941 CEST
4145
4146 def utcoffset(self, dt):
4147 fold_start, fold_stop = self._loc_fold()
4148 if dt < fold_start:
4149 return 3 * HOUR
4150 if dt < fold_stop:
4151 return (2 if dt.fold else 3) * HOUR
4152 # if dt >= fold_stop
4153 return 2 * HOUR
4154
4155 def dst(self, dt):
4156 fold_start, fold_stop = self._loc_fold()
4157 if dt < fold_start:
4158 return 0 * HOUR
4159 if dt < fold_stop:
4160 return (1 if dt.fold else 0) * HOUR
4161 # if dt >= fold_stop
4162 return 1 * HOUR
4163
4164 def tzname(self, dt):
4165 fold_start, fold_stop = self._loc_fold()
4166 if dt < fold_start:
4167 return 'MSK'
4168 if dt < fold_stop:
4169 return ('MSK', 'CEST')[dt.fold]
4170 # if dt >= fold_stop
4171 return 'CEST'
4172
4173 def fromutc(self, dt):
4174 assert dt.fold == 0
4175 assert dt.tzinfo is self
4176 if dt.year != 1941:
4177 raise NotImplementedError
4178 fold_start, fold_stop = self._utc_fold()
4179 if dt < fold_start:
4180 return dt + 3 * HOUR
4181 if dt < fold_stop:
4182 return (dt + 2 * HOUR).replace(fold=1)
4183 # if dt >= fold_stop
4184 return dt + 2 * HOUR
4185
4186
4187class TestLocalTimeDisambiguation(unittest.TestCase):
4188
4189 def test_vilnius_1941_fromutc(self):
4190 Vilnius = Europe_Vilnius_1941()
4191
4192 gdt = datetime(1941, 6, 23, 20, 59, 59, tzinfo=timezone.utc)
4193 ldt = gdt.astimezone(Vilnius)
4194 self.assertEqual(ldt.strftime("%c %Z%z"),
4195 'Mon Jun 23 23:59:59 1941 MSK+0300')
4196 self.assertEqual(ldt.fold, 0)
4197 self.assertFalse(ldt.dst())
4198
4199 gdt = datetime(1941, 6, 23, 21, tzinfo=timezone.utc)
4200 ldt = gdt.astimezone(Vilnius)
4201 self.assertEqual(ldt.strftime("%c %Z%z"),
4202 'Mon Jun 23 23:00:00 1941 CEST+0200')
4203 self.assertEqual(ldt.fold, 1)
4204 self.assertTrue(ldt.dst())
4205
4206 gdt = datetime(1941, 6, 23, 22, tzinfo=timezone.utc)
4207 ldt = gdt.astimezone(Vilnius)
4208 self.assertEqual(ldt.strftime("%c %Z%z"),
4209 'Tue Jun 24 00:00:00 1941 CEST+0200')
4210 self.assertEqual(ldt.fold, 0)
4211 self.assertTrue(ldt.dst())
4212
4213 def test_vilnius_1941_toutc(self):
4214 Vilnius = Europe_Vilnius_1941()
4215
4216 ldt = datetime(1941, 6, 23, 22, 59, 59, tzinfo=Vilnius)
4217 gdt = ldt.astimezone(timezone.utc)
4218 self.assertEqual(gdt.strftime("%c %Z"),
4219 'Mon Jun 23 19:59:59 1941 UTC')
4220
4221 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius)
4222 gdt = ldt.astimezone(timezone.utc)
4223 self.assertEqual(gdt.strftime("%c %Z"),
4224 'Mon Jun 23 20:59:59 1941 UTC')
4225
4226 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius, fold=1)
4227 gdt = ldt.astimezone(timezone.utc)
4228 self.assertEqual(gdt.strftime("%c %Z"),
4229 'Mon Jun 23 21:59:59 1941 UTC')
4230
4231 ldt = datetime(1941, 6, 24, 0, tzinfo=Vilnius)
4232 gdt = ldt.astimezone(timezone.utc)
4233 self.assertEqual(gdt.strftime("%c %Z"),
4234 'Mon Jun 23 22:00:00 1941 UTC')
4235
4236
4237 def test_constructors(self):
4238 t = time(0, fold=1)
4239 dt = datetime(1, 1, 1, fold=1)
4240 self.assertEqual(t.fold, 1)
4241 self.assertEqual(dt.fold, 1)
4242 with self.assertRaises(TypeError):
4243 time(0, 0, 0, 0, None, 0)
4244
4245 def test_member(self):
4246 dt = datetime(1, 1, 1, fold=1)
4247 t = dt.time()
4248 self.assertEqual(t.fold, 1)
4249 t = dt.timetz()
4250 self.assertEqual(t.fold, 1)
4251
4252 def test_replace(self):
4253 t = time(0)
4254 dt = datetime(1, 1, 1)
4255 self.assertEqual(t.replace(fold=1).fold, 1)
4256 self.assertEqual(dt.replace(fold=1).fold, 1)
4257 self.assertEqual(t.replace(fold=0).fold, 0)
4258 self.assertEqual(dt.replace(fold=0).fold, 0)
4259 # Check that replacement of other fields does not change "fold".
4260 t = t.replace(fold=1, tzinfo=Eastern)
4261 dt = dt.replace(fold=1, tzinfo=Eastern)
4262 self.assertEqual(t.replace(tzinfo=None).fold, 1)
4263 self.assertEqual(dt.replace(tzinfo=None).fold, 1)
4264 # Check that fold is a keyword-only argument
4265 with self.assertRaises(TypeError):
4266 t.replace(1, 1, 1, None, 1)
4267 with self.assertRaises(TypeError):
4268 dt.replace(1, 1, 1, 1, 1, 1, 1, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04004269
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004270 def test_comparison(self):
4271 t = time(0)
4272 dt = datetime(1, 1, 1)
4273 self.assertEqual(t, t.replace(fold=1))
4274 self.assertEqual(dt, dt.replace(fold=1))
4275
4276 def test_hash(self):
4277 t = time(0)
4278 dt = datetime(1, 1, 1)
4279 self.assertEqual(hash(t), hash(t.replace(fold=1)))
4280 self.assertEqual(hash(dt), hash(dt.replace(fold=1)))
4281
4282 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4283 def test_fromtimestamp(self):
4284 s = 1414906200
4285 dt0 = datetime.fromtimestamp(s)
4286 dt1 = datetime.fromtimestamp(s + 3600)
4287 self.assertEqual(dt0.fold, 0)
4288 self.assertEqual(dt1.fold, 1)
4289
4290 @support.run_with_tz('Australia/Lord_Howe')
4291 def test_fromtimestamp_lord_howe(self):
4292 tm = _time.localtime(1.4e9)
4293 if _time.strftime('%Z%z', tm) != 'LHST+1030':
4294 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
4295 # $ TZ=Australia/Lord_Howe date -r 1428158700
4296 # Sun Apr 5 01:45:00 LHDT 2015
4297 # $ TZ=Australia/Lord_Howe date -r 1428160500
4298 # Sun Apr 5 01:45:00 LHST 2015
4299 s = 1428158700
4300 t0 = datetime.fromtimestamp(s)
4301 t1 = datetime.fromtimestamp(s + 1800)
4302 self.assertEqual(t0, t1)
4303 self.assertEqual(t0.fold, 0)
4304 self.assertEqual(t1.fold, 1)
4305
4306
4307 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4308 def test_timestamp(self):
4309 dt0 = datetime(2014, 11, 2, 1, 30)
4310 dt1 = dt0.replace(fold=1)
4311 self.assertEqual(dt0.timestamp() + 3600,
4312 dt1.timestamp())
4313
4314 @support.run_with_tz('Australia/Lord_Howe')
4315 def test_timestamp_lord_howe(self):
4316 tm = _time.localtime(1.4e9)
4317 if _time.strftime('%Z%z', tm) != 'LHST+1030':
4318 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
4319 t = datetime(2015, 4, 5, 1, 45)
4320 s0 = t.replace(fold=0).timestamp()
4321 s1 = t.replace(fold=1).timestamp()
4322 self.assertEqual(s0 + 1800, s1)
4323
4324
4325 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4326 def test_astimezone(self):
4327 dt0 = datetime(2014, 11, 2, 1, 30)
4328 dt1 = dt0.replace(fold=1)
4329 # Convert both naive instances to aware.
4330 adt0 = dt0.astimezone()
4331 adt1 = dt1.astimezone()
4332 # Check that the first instance in DST zone and the second in STD
4333 self.assertEqual(adt0.tzname(), 'EDT')
4334 self.assertEqual(adt1.tzname(), 'EST')
4335 self.assertEqual(adt0 + HOUR, adt1)
4336 # Aware instances with fixed offset tzinfo's always have fold=0
4337 self.assertEqual(adt0.fold, 0)
4338 self.assertEqual(adt1.fold, 0)
4339
4340
4341 def test_pickle_fold(self):
4342 t = time(fold=1)
4343 dt = datetime(1, 1, 1, fold=1)
4344 for pickler, unpickler, proto in pickle_choices:
4345 for x in [t, dt]:
4346 s = pickler.dumps(x, proto)
4347 y = unpickler.loads(s)
4348 self.assertEqual(x, y)
4349 self.assertEqual((0 if proto < 4 else x.fold), y.fold)
4350
4351 def test_repr(self):
4352 t = time(fold=1)
4353 dt = datetime(1, 1, 1, fold=1)
4354 self.assertEqual(repr(t), 'datetime.time(0, 0, fold=1)')
4355 self.assertEqual(repr(dt),
4356 'datetime.datetime(1, 1, 1, 0, 0, fold=1)')
4357
4358 def test_dst(self):
4359 # Let's first establish that things work in regular times.
4360 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
4361 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
4362 self.assertEqual(dt_summer.dst(), HOUR)
4363 self.assertEqual(dt_winter.dst(), ZERO)
4364 # The disambiguation flag is ignored
4365 self.assertEqual(dt_summer.replace(fold=1).dst(), HOUR)
4366 self.assertEqual(dt_winter.replace(fold=1).dst(), ZERO)
4367
4368 # Pick local time in the fold.
4369 for minute in [0, 30, 59]:
4370 dt = datetime(2002, 10, 27, 1, minute, tzinfo=Eastern2)
4371 # With fold=0 (the default) it is in DST.
4372 self.assertEqual(dt.dst(), HOUR)
4373 # With fold=1 it is in STD.
4374 self.assertEqual(dt.replace(fold=1).dst(), ZERO)
4375
4376 # Pick local time in the gap.
4377 for minute in [0, 30, 59]:
4378 dt = datetime(2002, 4, 7, 2, minute, tzinfo=Eastern2)
4379 # With fold=0 (the default) it is in STD.
4380 self.assertEqual(dt.dst(), ZERO)
4381 # With fold=1 it is in DST.
4382 self.assertEqual(dt.replace(fold=1).dst(), HOUR)
4383
4384
4385 def test_utcoffset(self):
4386 # Let's first establish that things work in regular times.
4387 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
4388 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
4389 self.assertEqual(dt_summer.utcoffset(), -4 * HOUR)
4390 self.assertEqual(dt_winter.utcoffset(), -5 * HOUR)
4391 # The disambiguation flag is ignored
4392 self.assertEqual(dt_summer.replace(fold=1).utcoffset(), -4 * HOUR)
4393 self.assertEqual(dt_winter.replace(fold=1).utcoffset(), -5 * HOUR)
4394
4395 def test_fromutc(self):
4396 # Let's first establish that things work in regular times.
4397 u_summer = datetime(2002, 10, 27, 6, tzinfo=Eastern2) - timedelta.resolution
4398 u_winter = datetime(2002, 10, 27, 7, tzinfo=Eastern2)
4399 t_summer = Eastern2.fromutc(u_summer)
4400 t_winter = Eastern2.fromutc(u_winter)
4401 self.assertEqual(t_summer, u_summer - 4 * HOUR)
4402 self.assertEqual(t_winter, u_winter - 5 * HOUR)
4403 self.assertEqual(t_summer.fold, 0)
4404 self.assertEqual(t_winter.fold, 0)
4405
4406 # What happens in the fall-back fold?
4407 u = datetime(2002, 10, 27, 5, 30, tzinfo=Eastern2)
4408 t0 = Eastern2.fromutc(u)
4409 u += HOUR
4410 t1 = Eastern2.fromutc(u)
4411 self.assertEqual(t0, t1)
4412 self.assertEqual(t0.fold, 0)
4413 self.assertEqual(t1.fold, 1)
4414 # The tricky part is when u is in the local fold:
4415 u = datetime(2002, 10, 27, 1, 30, tzinfo=Eastern2)
4416 t = Eastern2.fromutc(u)
4417 self.assertEqual((t.day, t.hour), (26, 21))
4418 # .. or gets into the local fold after a standard time adjustment
4419 u = datetime(2002, 10, 27, 6, 30, tzinfo=Eastern2)
4420 t = Eastern2.fromutc(u)
4421 self.assertEqual((t.day, t.hour), (27, 1))
4422
4423 # What happens in the spring-forward gap?
4424 u = datetime(2002, 4, 7, 2, 0, tzinfo=Eastern2)
4425 t = Eastern2.fromutc(u)
4426 self.assertEqual((t.day, t.hour), (6, 21))
4427
4428 def test_mixed_compare_regular(self):
4429 t = datetime(2000, 1, 1, tzinfo=Eastern2)
4430 self.assertEqual(t, t.astimezone(timezone.utc))
4431 t = datetime(2000, 6, 1, tzinfo=Eastern2)
4432 self.assertEqual(t, t.astimezone(timezone.utc))
4433
4434 def test_mixed_compare_fold(self):
4435 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
4436 t_fold_utc = t_fold.astimezone(timezone.utc)
4437 self.assertNotEqual(t_fold, t_fold_utc)
4438
4439 def test_mixed_compare_gap(self):
4440 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
4441 t_gap_utc = t_gap.astimezone(timezone.utc)
4442 self.assertNotEqual(t_gap, t_gap_utc)
4443
4444 def test_hash_aware(self):
4445 t = datetime(2000, 1, 1, tzinfo=Eastern2)
4446 self.assertEqual(hash(t), hash(t.replace(fold=1)))
4447 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
4448 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
4449 self.assertEqual(hash(t_fold), hash(t_fold.replace(fold=1)))
4450 self.assertEqual(hash(t_gap), hash(t_gap.replace(fold=1)))
4451
4452SEC = timedelta(0, 1)
4453
4454def pairs(iterable):
4455 a, b = itertools.tee(iterable)
4456 next(b, None)
4457 return zip(a, b)
4458
4459class ZoneInfo(tzinfo):
4460 zoneroot = '/usr/share/zoneinfo'
4461 def __init__(self, ut, ti):
4462 """
4463
4464 :param ut: array
4465 Array of transition point timestamps
4466 :param ti: list
4467 A list of (offset, isdst, abbr) tuples
4468 :return: None
4469 """
4470 self.ut = ut
4471 self.ti = ti
4472 self.lt = self.invert(ut, ti)
4473
4474 @staticmethod
4475 def invert(ut, ti):
4476 lt = (ut.__copy__(), ut.__copy__())
4477 if ut:
4478 offset = ti[0][0] // SEC
4479 lt[0][0] = max(-2**31, lt[0][0] + offset)
4480 lt[1][0] = max(-2**31, lt[1][0] + offset)
4481 for i in range(1, len(ut)):
4482 lt[0][i] += ti[i-1][0] // SEC
4483 lt[1][i] += ti[i][0] // SEC
4484 return lt
4485
4486 @classmethod
4487 def fromfile(cls, fileobj):
4488 if fileobj.read(4).decode() != "TZif":
4489 raise ValueError("not a zoneinfo file")
4490 fileobj.seek(32)
4491 counts = array('i')
4492 counts.fromfile(fileobj, 3)
4493 if sys.byteorder != 'big':
4494 counts.byteswap()
4495
4496 ut = array('i')
4497 ut.fromfile(fileobj, counts[0])
4498 if sys.byteorder != 'big':
4499 ut.byteswap()
4500
4501 type_indices = array('B')
4502 type_indices.fromfile(fileobj, counts[0])
4503
4504 ttis = []
4505 for i in range(counts[1]):
4506 ttis.append(struct.unpack(">lbb", fileobj.read(6)))
4507
4508 abbrs = fileobj.read(counts[2])
4509
4510 # Convert ttis
4511 for i, (gmtoff, isdst, abbrind) in enumerate(ttis):
4512 abbr = abbrs[abbrind:abbrs.find(0, abbrind)].decode()
4513 ttis[i] = (timedelta(0, gmtoff), isdst, abbr)
4514
4515 ti = [None] * len(ut)
4516 for i, idx in enumerate(type_indices):
4517 ti[i] = ttis[idx]
4518
4519 self = cls(ut, ti)
4520
4521 return self
4522
4523 @classmethod
4524 def fromname(cls, name):
4525 path = os.path.join(cls.zoneroot, name)
4526 with open(path, 'rb') as f:
4527 return cls.fromfile(f)
4528
4529 EPOCHORDINAL = date(1970, 1, 1).toordinal()
4530
4531 def fromutc(self, dt):
4532 """datetime in UTC -> datetime in local time."""
4533
4534 if not isinstance(dt, datetime):
4535 raise TypeError("fromutc() requires a datetime argument")
4536 if dt.tzinfo is not self:
4537 raise ValueError("dt.tzinfo is not self")
4538
4539 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
4540 + dt.hour * 3600
4541 + dt.minute * 60
4542 + dt.second)
4543
4544 if timestamp < self.ut[1]:
4545 tti = self.ti[0]
4546 fold = 0
4547 else:
4548 idx = bisect.bisect_right(self.ut, timestamp)
4549 assert self.ut[idx-1] <= timestamp
4550 assert idx == len(self.ut) or timestamp < self.ut[idx]
4551 tti_prev, tti = self.ti[idx-2:idx]
4552 # Detect fold
4553 shift = tti_prev[0] - tti[0]
4554 fold = (shift > timedelta(0, timestamp - self.ut[idx-1]))
4555 dt += tti[0]
4556 if fold:
4557 return dt.replace(fold=1)
4558 else:
4559 return dt
4560
4561 def _find_ti(self, dt, i):
4562 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
4563 + dt.hour * 3600
4564 + dt.minute * 60
4565 + dt.second)
4566 lt = self.lt[dt.fold]
4567 idx = bisect.bisect_right(lt, timestamp)
4568
4569 return self.ti[max(0, idx - 1)][i]
4570
4571 def utcoffset(self, dt):
4572 return self._find_ti(dt, 0)
4573
4574 def dst(self, dt):
4575 isdst = self._find_ti(dt, 1)
4576 # XXX: We cannot accurately determine the "save" value,
4577 # so let's return 1h whenever DST is in effect. Since
4578 # we don't use dst() in fromutc(), it is unlikely that
4579 # it will be needed for anything more than bool(dst()).
4580 return ZERO if isdst else HOUR
4581
4582 def tzname(self, dt):
4583 return self._find_ti(dt, 2)
4584
4585 @classmethod
4586 def zonenames(cls, zonedir=None):
4587 if zonedir is None:
4588 zonedir = cls.zoneroot
4589 for root, _, files in os.walk(zonedir):
4590 for f in files:
4591 p = os.path.join(root, f)
4592 with open(p, 'rb') as o:
4593 magic = o.read(4)
4594 if magic == b'TZif':
4595 yield p[len(zonedir) + 1:]
4596
4597 @classmethod
4598 def stats(cls, start_year=1):
4599 count = gap_count = fold_count = zeros_count = 0
4600 min_gap = min_fold = timedelta.max
4601 max_gap = max_fold = ZERO
4602 min_gap_datetime = max_gap_datetime = datetime.min
4603 min_gap_zone = max_gap_zone = None
4604 min_fold_datetime = max_fold_datetime = datetime.min
4605 min_fold_zone = max_fold_zone = None
4606 stats_since = datetime(start_year, 1, 1) # Starting from 1970 eliminates a lot of noise
4607 for zonename in cls.zonenames():
4608 count += 1
4609 tz = cls.fromname(zonename)
4610 for dt, shift in tz.transitions():
4611 if dt < stats_since:
4612 continue
4613 if shift > ZERO:
4614 gap_count += 1
4615 if (shift, dt) > (max_gap, max_gap_datetime):
4616 max_gap = shift
4617 max_gap_zone = zonename
4618 max_gap_datetime = dt
4619 if (shift, datetime.max - dt) < (min_gap, datetime.max - min_gap_datetime):
4620 min_gap = shift
4621 min_gap_zone = zonename
4622 min_gap_datetime = dt
4623 elif shift < ZERO:
4624 fold_count += 1
4625 shift = -shift
4626 if (shift, dt) > (max_fold, max_fold_datetime):
4627 max_fold = shift
4628 max_fold_zone = zonename
4629 max_fold_datetime = dt
4630 if (shift, datetime.max - dt) < (min_fold, datetime.max - min_fold_datetime):
4631 min_fold = shift
4632 min_fold_zone = zonename
4633 min_fold_datetime = dt
4634 else:
4635 zeros_count += 1
4636 trans_counts = (gap_count, fold_count, zeros_count)
4637 print("Number of zones: %5d" % count)
4638 print("Number of transitions: %5d = %d (gaps) + %d (folds) + %d (zeros)" %
4639 ((sum(trans_counts),) + trans_counts))
4640 print("Min gap: %16s at %s in %s" % (min_gap, min_gap_datetime, min_gap_zone))
4641 print("Max gap: %16s at %s in %s" % (max_gap, max_gap_datetime, max_gap_zone))
4642 print("Min fold: %16s at %s in %s" % (min_fold, min_fold_datetime, min_fold_zone))
4643 print("Max fold: %16s at %s in %s" % (max_fold, max_fold_datetime, max_fold_zone))
4644
4645
4646 def transitions(self):
4647 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
4648 shift = ti[0] - prev_ti[0]
4649 yield datetime.utcfromtimestamp(t), shift
4650
4651 def nondst_folds(self):
4652 """Find all folds with the same value of isdst on both sides of the transition."""
4653 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
4654 shift = ti[0] - prev_ti[0]
4655 if shift < ZERO and ti[1] == prev_ti[1]:
4656 yield datetime.utcfromtimestamp(t), -shift, prev_ti[2], ti[2]
4657
4658 @classmethod
4659 def print_all_nondst_folds(cls, same_abbr=False, start_year=1):
4660 count = 0
4661 for zonename in cls.zonenames():
4662 tz = cls.fromname(zonename)
4663 for dt, shift, prev_abbr, abbr in tz.nondst_folds():
4664 if dt.year < start_year or same_abbr and prev_abbr != abbr:
4665 continue
4666 count += 1
4667 print("%3d) %-30s %s %10s %5s -> %s" %
4668 (count, zonename, dt, shift, prev_abbr, abbr))
4669
4670 def folds(self):
4671 for t, shift in self.transitions():
4672 if shift < ZERO:
4673 yield t, -shift
4674
4675 def gaps(self):
4676 for t, shift in self.transitions():
4677 if shift > ZERO:
4678 yield t, shift
4679
4680 def zeros(self):
4681 for t, shift in self.transitions():
4682 if not shift:
4683 yield t
4684
4685
4686class ZoneInfoTest(unittest.TestCase):
4687 zonename = 'America/New_York'
4688
4689 def setUp(self):
Alexander Belopolsky611adf22016-07-26 12:23:16 -04004690 self.sizeof_time_t = sysconfig.get_config_var('SIZEOF_TIME_T')
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004691 if sys.platform == "win32":
4692 self.skipTest("Skipping zoneinfo tests on Windows")
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04004693 try:
4694 self.tz = ZoneInfo.fromname(self.zonename)
4695 except FileNotFoundError as err:
4696 self.skipTest("Skipping %s: %s" % (self.zonename, err))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004697
4698 def assertEquivDatetimes(self, a, b):
4699 self.assertEqual((a.replace(tzinfo=None), a.fold, id(a.tzinfo)),
4700 (b.replace(tzinfo=None), b.fold, id(b.tzinfo)))
4701
4702 def test_folds(self):
4703 tz = self.tz
4704 for dt, shift in tz.folds():
4705 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
4706 udt = dt + x
4707 ldt = tz.fromutc(udt.replace(tzinfo=tz))
4708 self.assertEqual(ldt.fold, 1)
4709 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
4710 self.assertEquivDatetimes(adt, ldt)
4711 utcoffset = ldt.utcoffset()
4712 self.assertEqual(ldt.replace(tzinfo=None), udt + utcoffset)
4713 # Round trip
4714 self.assertEquivDatetimes(ldt.astimezone(timezone.utc),
4715 udt.replace(tzinfo=timezone.utc))
4716
4717
4718 for x in [-timedelta.resolution, shift]:
4719 udt = dt + x
4720 udt = udt.replace(tzinfo=tz)
4721 ldt = tz.fromutc(udt)
4722 self.assertEqual(ldt.fold, 0)
4723
4724 def test_gaps(self):
4725 tz = self.tz
4726 for dt, shift in tz.gaps():
4727 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
4728 udt = dt + x
4729 udt = udt.replace(tzinfo=tz)
4730 ldt = tz.fromutc(udt)
4731 self.assertEqual(ldt.fold, 0)
4732 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
4733 self.assertEquivDatetimes(adt, ldt)
4734 utcoffset = ldt.utcoffset()
4735 self.assertEqual(ldt.replace(tzinfo=None), udt.replace(tzinfo=None) + utcoffset)
4736 # Create a local time inside the gap
4737 ldt = tz.fromutc(dt.replace(tzinfo=tz)) - shift + x
4738 self.assertLess(ldt.replace(fold=1).utcoffset(),
4739 ldt.replace(fold=0).utcoffset(),
4740 "At %s." % ldt)
4741
4742 for x in [-timedelta.resolution, shift]:
4743 udt = dt + x
4744 ldt = tz.fromutc(udt.replace(tzinfo=tz))
4745 self.assertEqual(ldt.fold, 0)
4746
4747 def test_system_transitions(self):
4748 if ('Riyadh8' in self.zonename or
4749 # From tzdata NEWS file:
4750 # The files solar87, solar88, and solar89 are no longer distributed.
4751 # They were a negative experiment - that is, a demonstration that
4752 # tz data can represent solar time only with some difficulty and error.
4753 # Their presence in the distribution caused confusion, as Riyadh
4754 # civil time was generally not solar time in those years.
4755 self.zonename.startswith('right/')):
4756 self.skipTest("Skipping %s" % self.zonename)
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04004757 tz = self.tz
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004758 TZ = os.environ.get('TZ')
4759 os.environ['TZ'] = self.zonename
4760 try:
4761 _time.tzset()
4762 for udt, shift in tz.transitions():
4763 if self.zonename == 'Europe/Tallinn' and udt.date() == date(1999, 10, 31):
4764 print("Skip %s %s transition" % (self.zonename, udt))
4765 continue
Alexander Belopolsky611adf22016-07-26 12:23:16 -04004766 if self.sizeof_time_t == 4 and udt.year >= 2037:
4767 print("Skip %s %s transition for 32-bit time_t" % (self.zonename, udt))
4768 continue
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004769 s0 = (udt - datetime(1970, 1, 1)) // SEC
4770 ss = shift // SEC # shift seconds
4771 for x in [-40 * 3600, -20*3600, -1, 0,
4772 ss - 1, ss + 20 * 3600, ss + 40 * 3600]:
4773 s = s0 + x
4774 sdt = datetime.fromtimestamp(s)
4775 tzdt = datetime.fromtimestamp(s, tz).replace(tzinfo=None)
4776 self.assertEquivDatetimes(sdt, tzdt)
4777 s1 = sdt.timestamp()
4778 self.assertEqual(s, s1)
4779 if ss > 0: # gap
4780 # Create local time inside the gap
4781 dt = datetime.fromtimestamp(s0) - shift / 2
4782 ts0 = dt.timestamp()
4783 ts1 = dt.replace(fold=1).timestamp()
4784 self.assertEqual(ts0, s0 + ss / 2)
4785 self.assertEqual(ts1, s0 - ss / 2)
4786 finally:
4787 if TZ is None:
4788 del os.environ['TZ']
4789 else:
4790 os.environ['TZ'] = TZ
4791 _time.tzset()
4792
4793
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04004794class ZoneInfoCompleteTest(unittest.TestSuite):
4795 def __init__(self):
4796 tests = []
4797 if is_resource_enabled('tzdata'):
4798 for name in ZoneInfo.zonenames():
4799 Test = type('ZoneInfoTest[%s]' % name, (ZoneInfoTest,), {})
4800 Test.zonename = name
4801 for method in dir(Test):
4802 if method.startswith('test_'):
4803 tests.append(Test(method))
4804 super().__init__(tests)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004805
4806# Iran had a sub-minute UTC offset before 1946.
4807class IranTest(ZoneInfoTest):
Alexander Belopolsky07e2a0a2016-07-30 11:41:02 -04004808 zonename = 'Asia/Tehran'
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004809
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04004810def load_tests(loader, standard_tests, pattern):
4811 standard_tests.addTest(ZoneInfoCompleteTest())
4812 return standard_tests
4813
4814
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004815if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -05004816 unittest.main()